coati-payroll 0.0.4__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of coati-payroll might be problematic. Click here for more details.

coati_payroll/cli.py CHANGED
@@ -39,10 +39,10 @@ from flask.cli import with_appcontext
39
39
  # <-------------------------------------------------------------------------> #
40
40
  # Local modules
41
41
  # <-------------------------------------------------------------------------> #
42
- from coati_payroll.model import db, Usuario
42
+ from coati_payroll.model import db, Usuario, PluginRegistry
43
43
  from coati_payroll.auth import proteger_passwd
44
44
  from coati_payroll.log import log
45
- from coati_payroll.plugin_manager import discover_installed_plugins, load_plugin_module
45
+ from coati_payroll.plugin_manager import discover_installed_plugins, load_plugin_module, sync_plugin_registry
46
46
 
47
47
 
48
48
  # Global context to store CLI options
@@ -68,7 +68,7 @@ def output_result(ctx, message, data=None, success=True):
68
68
  click.echo(f"{symbol} {message}")
69
69
 
70
70
 
71
- class PluginsCommand(click.MultiCommand):
71
+ class PluginsCommand(click.Group):
72
72
  def list_commands(self, cli_ctx):
73
73
  try:
74
74
  return [p.plugin_id for p in discover_installed_plugins()]
@@ -86,12 +86,9 @@ class PluginsCommand(click.MultiCommand):
86
86
 
87
87
  return click.Command(name, callback=lambda: _missing())
88
88
 
89
- @click.group(name=name)
89
+ @click.group(name=name, help=f"Gestión del plugin '{name}'")
90
90
  def plugin_group():
91
- """Empty group function that serves as a container for plugin subcommands.
92
-
93
- Subcommands (init, update) are dynamically added below.
94
- """
91
+ """Grupo de comandos del plugin específico."""
95
92
  pass
96
93
 
97
94
  @plugin_group.command("init")
@@ -128,10 +125,191 @@ class PluginsCommand(click.MultiCommand):
128
125
  output_result(ctx, f"Plugin '{name}' update failed: {exc}", None, False)
129
126
  raise click.ClickException(str(exc))
130
127
 
128
+ @plugin_group.command("demo_data")
129
+ @with_appcontext
130
+ @pass_context
131
+ def plugin_demo_data(ctx):
132
+ """Carga datos de demostración para pruebas automáticas."""
133
+ # Permitir alias: demo_data o load_demo_data
134
+ demo_fn = getattr(module, "demo_data", None)
135
+ if demo_fn is None or not callable(demo_fn):
136
+ demo_fn = getattr(module, "load_demo_data", None)
137
+ if demo_fn is None or not callable(demo_fn):
138
+ raise click.ClickException("Plugin does not provide callable 'demo_data()' or 'load_demo_data()'")
139
+
140
+ try:
141
+ demo_fn()
142
+ db.create_all()
143
+ output_result(ctx, f"Plugin '{name}' demo data loaded")
144
+ except Exception as exc:
145
+ log.exception("Plugin demo_data failed")
146
+ output_result(ctx, f"Plugin '{name}' demo data failed: {exc}", None, False)
147
+ raise click.ClickException(str(exc))
148
+
149
+ @plugin_group.command("enable")
150
+ @with_appcontext
151
+ @pass_context
152
+ def plugin_enable(ctx):
153
+ """Habilita el plugin en el registro (active=True)."""
154
+ try:
155
+ sync_plugin_registry()
156
+ record = db.session.execute(db.select(PluginRegistry).filter_by(plugin_id=name)).scalars().first()
157
+ if record is None:
158
+ raise click.ClickException("Plugin no registrado en la base de datos")
159
+ if not record.installed:
160
+ raise click.ClickException("Plugin no está instalado en el entorno")
161
+ record.active = True
162
+ db.session.commit()
163
+ output_result(ctx, f"Plugin '{name}' habilitado. Reinicie la app para cargar blueprints.")
164
+ except click.ClickException:
165
+ raise
166
+ except Exception as exc:
167
+ db.session.rollback()
168
+ output_result(ctx, f"No se pudo habilitar el plugin: {exc}", None, False)
169
+ raise click.ClickException(str(exc))
170
+
171
+ @plugin_group.command("disable")
172
+ @with_appcontext
173
+ @pass_context
174
+ def plugin_disable(ctx):
175
+ """Deshabilita el plugin en el registro (active=False)."""
176
+ try:
177
+ sync_plugin_registry()
178
+ record = db.session.execute(db.select(PluginRegistry).filter_by(plugin_id=name)).scalars().first()
179
+ if record is None:
180
+ raise click.ClickException("Plugin no registrado en la base de datos")
181
+ record.active = False
182
+ db.session.commit()
183
+ output_result(ctx, f"Plugin '{name}' deshabilitado")
184
+ except click.ClickException:
185
+ raise
186
+ except Exception as exc:
187
+ db.session.rollback()
188
+ output_result(ctx, f"No se pudo deshabilitar el plugin: {exc}", None, False)
189
+ raise click.ClickException(str(exc))
190
+
191
+ def _get_plugin_metadata(plugin_id: str) -> dict:
192
+ meta = {"plugin_id": plugin_id}
193
+ try:
194
+ # versión detectada por distribución instalada
195
+ discovered = {p.plugin_id: p for p in discover_installed_plugins()}
196
+ if plugin_id in discovered:
197
+ meta["version"] = discovered[plugin_id].version
198
+ except Exception:
199
+ pass
200
+ try:
201
+ mod = load_plugin_module(plugin_id)
202
+ info = getattr(mod, "PLUGIN_INFO", None) or getattr(mod, "INFO", None)
203
+ if isinstance(info, dict):
204
+ meta.update(
205
+ {
206
+ "description": info.get("description"),
207
+ "maintainer": info.get("maintainer"),
208
+ "contact": info.get("contact"),
209
+ "version": info.get("version", meta.get("version")),
210
+ }
211
+ )
212
+ else:
213
+ meta.setdefault("version", getattr(mod, "__version__", meta.get("version")))
214
+ meta["description"] = meta.get("description") or (
215
+ mod.__doc__.strip() if getattr(mod, "__doc__", None) else None
216
+ )
217
+ meta["maintainer"] = getattr(mod, "MAINTAINER", None)
218
+ meta["contact"] = getattr(mod, "CONTACT", None)
219
+ except Exception:
220
+ # no importa si el módulo falla, usamos lo disponible
221
+ pass
222
+ try:
223
+ rec = db.session.execute(db.select(PluginRegistry).filter_by(plugin_id=plugin_id)).scalars().first()
224
+ if rec:
225
+ meta["installed"] = rec.installed
226
+ meta["active"] = rec.active
227
+ meta["distribution_name"] = rec.distribution_name
228
+ except Exception:
229
+ pass
230
+ return meta
231
+
232
+ @plugin_group.command("status")
233
+ @with_appcontext
234
+ @pass_context
235
+ def plugin_status(ctx):
236
+ """Muestra el estado del plugin (installed/active/version)."""
237
+ try:
238
+ sync_plugin_registry()
239
+ meta = _get_plugin_metadata(name)
240
+ if ctx.json_output:
241
+ output_result(ctx, f"Estado del plugin '{name}'", meta, True)
242
+ else:
243
+ click.echo(f"Estado del plugin '{name}':")
244
+ click.echo(f" Installed: {meta.get('installed', False)}")
245
+ click.echo(f" Active: {meta.get('active', False)}")
246
+ click.echo(f" Version: {meta.get('version', 'desconocida')}\n")
247
+ except Exception as exc:
248
+ output_result(ctx, f"No se pudo obtener estado: {exc}", None, False)
249
+ raise click.ClickException(str(exc))
250
+
251
+ @plugin_group.command("version")
252
+ @with_appcontext
253
+ @pass_context
254
+ def plugin_version(ctx):
255
+ """Muestra la versión del plugin."""
256
+ meta = _get_plugin_metadata(name)
257
+ ver = meta.get("version") or "desconocida"
258
+ if ctx.json_output:
259
+ output_result(ctx, f"Versión del plugin '{name}'", {"version": ver}, True)
260
+ else:
261
+ click.echo(ver)
262
+
263
+ @plugin_group.command("info")
264
+ @with_appcontext
265
+ @pass_context
266
+ def plugin_info(ctx):
267
+ """Muestra información del plugin (descripción y enlaces)."""
268
+ meta = _get_plugin_metadata(name)
269
+ if ctx.json_output:
270
+ output_result(ctx, f"Información del plugin '{name}'", meta, True)
271
+ else:
272
+ click.echo(f"Info del plugin '{name}':")
273
+ if meta.get("description"):
274
+ click.echo(f" Description: {meta['description']}")
275
+ if meta.get("distribution_name"):
276
+ click.echo(f" Package: {meta['distribution_name']}")
277
+ if meta.get("version"):
278
+ click.echo(f" Version: {meta['version']}")
279
+ click.echo(f" Installed: {meta.get('installed', False)}")
280
+ click.echo(f" Active: {meta.get('active', False)}")
281
+
282
+ @plugin_group.command("maintainer")
283
+ @with_appcontext
284
+ @pass_context
285
+ def plugin_maintainer(ctx):
286
+ """Muestra el maintainer del plugin (según metadatos)."""
287
+ meta = _get_plugin_metadata(name)
288
+ maint = meta.get("maintainer") or "desconocido"
289
+ if ctx.json_output:
290
+ output_result(ctx, f"Maintainer del plugin '{name}'", {"maintainer": maint}, True)
291
+ else:
292
+ click.echo(maint)
293
+
294
+ # Alias por compatibilidad: 'mantainer' (mal escrito)
295
+ plugin_group.add_command(plugin_maintainer, "mantainer")
296
+
297
+ @plugin_group.command("contact")
298
+ @with_appcontext
299
+ @pass_context
300
+ def plugin_contact(ctx):
301
+ """Muestra el contacto del plugin (correo/URL si disponible)."""
302
+ meta = _get_plugin_metadata(name)
303
+ contact = meta.get("contact") or "no disponible"
304
+ if ctx.json_output:
305
+ output_result(ctx, f"Contacto del plugin '{name}'", {"contact": contact}, True)
306
+ else:
307
+ click.echo(contact)
308
+
131
309
  return plugin_group
132
310
 
133
311
 
134
- plugins = PluginsCommand(name="plugins")
312
+ plugins = PluginsCommand(name="plugins", help="Gestión de plugins instalados")
135
313
 
136
314
 
137
315
  # ============================================================================
coati_payroll/version.py CHANGED
@@ -15,4 +15,4 @@
15
15
  Data model for the payroll module.
16
16
  """
17
17
 
18
- __version__ = "0.0.4"
18
+ __version__ = "0.0.5"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: coati-payroll
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: A jurisdiction-agnostic payroll calculation engine.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -2,7 +2,7 @@ coati_payroll/__init__.py,sha256=UMMb03475dPNvlnvP4TuyNH0GAmL1FjPi5ND92-igbc,161
2
2
  coati_payroll/app.py,sha256=PPn5U2ViKU8yRiP0OMPkozq4CRWxp9Un27R_jAAkQ_g,3502
3
3
  coati_payroll/audit_helpers.py,sha256=0BDspyO5eQLRrdUCUcu5xe7F--2a-8OOAcejLJ4CcAg,27556
4
4
  coati_payroll/auth.py,sha256=v8yzxQnDZy3kbJnt9MWikyDJNwgCSkZpvfwARUnDh4s,4436
5
- coati_payroll/cli.py,sha256=iOukTddIvlCBFDr5nhrmgmd3xJueBzC_alD4VlmaMKU,39074
5
+ coati_payroll/cli.py,sha256=fptyskL-5NKuk51CZeHNCp54nAVkrdyztmQ75RPvATM,47424
6
6
  coati_payroll/config.py,sha256=dM3kcUsW6_gR1Vopdb_28fWxmhUi7lkj30kJfSU8zrY,9420
7
7
  coati_payroll/demo_data.py,sha256=pE7DUwZWq3jHCwt6vO_kW0sIuMFJmu9u1pqKUGWF9Ag,31437
8
8
  coati_payroll/enums.py,sha256=TEJ8Yxy-0UPsQUSDs7pbZgOg-VoqFOx8KnY0lTh3KzA,9211
@@ -23,7 +23,7 @@ coati_payroll/schema_validator.py,sha256=wa2X7VrXrEA6OzybycHdMD7smKqeF6Vp7oNSmV7
23
23
  coati_payroll/security.py,sha256=EYARqQI6llhfczpGamaaJdFF0fLBHn0-2dWcXy6kEO0,3040
24
24
  coati_payroll/system_reports.py,sha256=b0rLFUsGt-KYZPWh4tSpBeGqdJBOYfIO3cDuIAFJMIg,20406
25
25
  coati_payroll/vacation_service.py,sha256=iMgE7CcCfd5ytCWknyokFlphfYrnmAu6jtlPIPzgpHQ,17600
26
- coati_payroll/version.py,sha256=aqfm1bhjy_BsDh1EQE3b2Vg5y1t-PHtwei8VVpiUTaw,649
26
+ coati_payroll/version.py,sha256=r0fZ5Gv_R-WFge_67fVpFnR6bRtG-0y3B-HYxUoReNI,649
27
27
  coati_payroll/formula_engine/__init__.py,sha256=Nixn2nFtOk6PGZpnXCwRLnb-Pid_lz6qPRIv7j2P3HM,2461
28
28
  coati_payroll/formula_engine/data_sources.py,sha256=_UapWZiyWUqADnafpcd8936Vpg3Av_R2wxbvUEEi9Dc,30785
29
29
  coati_payroll/formula_engine/engine.py,sha256=f3a7mM8ckiGtE6RMK9ByEZdsllSGUsBLi19WxreDYsE,8897
@@ -235,9 +235,9 @@ coati_payroll/vistas/planilla/services/novedad_service.py,sha256=MYxbiqVrwzBbBTX
235
235
  coati_payroll/vistas/planilla/services/planilla_service.py,sha256=xpqxD6XLdTMRJfbPhvpWSYW7P6IcHKZgW6Ahg7kthk4,1195
236
236
  coati_payroll/vistas/planilla/validators/__init__.py,sha256=K-WnUGZaMYX-2DcduB-yMssuxUlztMCQKuGfqK67Vsk,754
237
237
  coati_payroll/vistas/planilla/validators/planilla_validators.py,sha256=WH1cyY1D7XPSb3MupNszofT3-YvgqBfp8vFDpo8hDA8,1680
238
- coati_payroll-0.0.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
239
- coati_payroll-0.0.4.dist-info/METADATA,sha256=amPPCuRO4XyFJxriif1Knnj4-9uaAxY5pxsyVV-wA5g,22236
240
- coati_payroll-0.0.4.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
241
- coati_payroll-0.0.4.dist-info/entry_points.txt,sha256=GtZVGVYEFlpeSs7eHYh701VG7ftRRrZ54fsyFmbeZZc,54
242
- coati_payroll-0.0.4.dist-info/top_level.txt,sha256=wRaRlWHJnSoqktbTT1XJUkPNgKnR7VS75Ytyl8JJYPY,14
243
- coati_payroll-0.0.4.dist-info/RECORD,,
238
+ coati_payroll-0.0.5.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
239
+ coati_payroll-0.0.5.dist-info/METADATA,sha256=UObvF57B8HfWBK15iF5lms84zfH6RaFmv6R8WVvBUNE,22236
240
+ coati_payroll-0.0.5.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
241
+ coati_payroll-0.0.5.dist-info/entry_points.txt,sha256=GtZVGVYEFlpeSs7eHYh701VG7ftRRrZ54fsyFmbeZZc,54
242
+ coati_payroll-0.0.5.dist-info/top_level.txt,sha256=wRaRlWHJnSoqktbTT1XJUkPNgKnR7VS75Ytyl8JJYPY,14
243
+ coati_payroll-0.0.5.dist-info/RECORD,,