diff --git a/.copier-answers.yml b/.copier-answers.yml index 5c84305..405ad4d 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: a740779 +_commit: 8677dea _src_path: https://github.com/ingadhoc/addons-repo-template.git description: Odoo Human Resources Addons is_private: false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 783d6e4..fc9d7d5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,312 +1,56 @@ -# Instrucciones para Copilot – Revisión de código Odoo (v18.0) +# Instrucciones para Copilot – Revisión de código Odoo ## Contexto -* El repositorio contiene **módulos Odoo preparados para Odoo 18** (rama `18.0`). -* El objetivo es **revisar cambios de código** y **sugerir mejoras seguras y relevantes**, sin caer en micro-comentarios. +Este repositorio contiene módulos Odoo. La versión objetivo está declarada en `__manifest__.py` de cada módulo. Las reglas específicas por dominio viven en `.github/instructions/*.instructions.md`, cada una con `applyTo:` que delimita a qué archivos aplica. ---- - -## Reglas generales (aplican a todo el código) +## Reglas globales (aplican a todo cambio) 1. **Responder siempre en español.** -2. Detectar y corregir **errores de tipeo u ortografía evidentes** en nombres de variables, métodos o comentarios (cuando sean claros). -3. No sugerir traducciones de docstrings o comentarios entre idiomas (no proponer pasar del inglés al español o viceversa). -4. No proponer agregar docstrings si el método no tiene uno. - - * Si ya existe un docstring, puede sugerirse un estilo básico acorde a PEP8, pero **no será un error** si faltan `return`, tipos o parámetros documentados. -5. No proponer cambios puramente estéticos (espacios, comillas simples vs dobles, orden de imports, etc.). -6. Mantener el feedback **muy conciso** en los PRs: priorizar pocos puntos claros, evitar párrafos largos y no repetir el contexto que ya está explicado en la descripción del PR. -7. Sobre traducciones: usar `_()` o `self.env._()` es indistinto; solo marcar si hay mensajes de error o textos no traducidos que deban serlo. - ---- - -## Revisión de modelos (`models/*.py`) – cuestiones generales - -* Verificar que: - - * Los campos (`fields.*`) tengan nombres claros, consistentes y no entren en conflicto con otros módulos. - * Las relaciones (`Many2one`, `One2many`, `Many2many`) estén bien definidas y referencien modelos válidos, con `ondelete` apropiado. - * Las constraints declaradas con `_sql_constraints` o `@api.constrains` mantengan la integridad esperada y mensajes claros. -* Sugerir uso de `@api.depends` si un campo compute carece de dependencias explícitas. -* Si se redefine un método de Odoo, asegurar que se llama correctamente `super()`, manteniendo el contrato original. -* Si hay lógica nueva, evitar loops costosos con búsquedas dentro de iteraciones; sugerir `mapped`, `filtered`, dominios vectorizados u otras formas más eficientes. - ---- - -## 🧾 Revisión del manifest (`__manifest__.py`) – reglas generales - -* Confirmar que todos los archivos usados (vistas, seguridad, datos, reportes, wizards) estén referenciados en el manifest. -* Verificar dependencias declaradas: que no falten módulos requeridos ni se declaren innecesarios. -* Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados. - ---- - -## Revisión de vistas XML (`views/*.xml`) – reglas generales - -* Confirmar que se usen herencias (`inherit_id`, `xpath`) en lugar de redefinir vistas completas sin necesidad. -* Validar que los campos referenciados existan en los modelos correspondientes. -* Evitar duplicar gran parte del `arch`; prioriza `xpath` específicos y claros. - -### Notas específicas Odoo 18 (vistas / UI) - -* Las vistas de lista usan el nuevo elemento `` en lugar de ``; si se ve código nuevo en 18 que sigue usando `` para listas estándar, sugiere adaptarlo cuando sea coherente con el resto del módulo. -* Muchas condiciones en vistas pueden escribirse con atributos declarativos (`invisible`, `readonly`, `required`) más simples que combinaciones complejas de `attrs`; sugiere simplificar cuando el diff haga la vista más compleja sin necesidad. - ---- - -## Seguridad y acceso – reglas generales - -* Verificar los archivos `ir.model.access.csv` para nuevos modelos: deben tener permisos mínimos necesarios. -* No proponer abrir acceso global sin justificación. -* Si se cambian `record rules`, revisar especialmente combinaciones multi-compañía y multi-website. - -### Seguridad y rendimiento del ORM - -* Reforzar las advertencias sobre **SQL crudo**: si el diff muestra `self.env.cr.execute("...%s..." % var)` u otras interpolaciones inseguras, recomendar reemplazarlo por dominios ORM (`search`, `browse`) o, si es inevitable, parametrizar la query para heredar sanitización y reglas de acceso. - * Ejemplo inseguro que debe marcarse: `self.env.cr.execute("SELECT * FROM res_partner WHERE email = '%s'" % email)`. - * Variante segura aceptable: `self.env.cr.execute("SELECT * FROM res_partner WHERE email = %s", (email,))` o, mejor aún, `self.env['res.partner'].search([('email', '=', email)])`. -* Señalar cualquier uso de `eval` o construcción manual de domains a partir de input de usuario (`eval(domain_string)`), proponiendo dominios expresados como listas de tuplas o mediante objetos `Domain`. - * Ejemplo inseguro: `records = self.env['res.partner'].search(eval("[('name','ilike','%s')]" % user_input))`. - * Forma segura: `records = self.env['res.partner'].search([('name', 'ilike', user_input)])`. -* Vigilar patrones ineficientes comunes: bucles que ejecutan `search`/`write` por registro, filtrados manuales tras `search([])` o cómputos que podrían resolverse con `search_count`, `mapped`, `filtered` o `browse` masivo. - * Ejemplo a señalar: `for partner_id in partner_ids: partner = self.env['res.partner'].search([('id', '=', partner_id)])`. - * Proponer `partners = self.env['res.partner'].browse(partner_ids)` y operar sobre el recordset completo. -* Para lecturas planas o exportaciones, preferir `search_fetch(fields=...)` para limitar columnas y reducir memoria. - * Caso ilustrativo: reemplazar listas armadas a mano con `result = self.env['res.partner'].search_fetch(domain=[('is_company', '=', True)], fields=['name', 'email', 'vat'])`. -* Recordar que los writes vectorizados (`recordset.write`) y las operaciones en lotes evitan locks prolongados y mejoran la trazabilidad de auditoría del ORM. - * Ejemplo recomendado: `partners.write({'comment': 'Actualizado masivamente'})` en lugar de iterar y escribir registro por registro. -* Tener en cuenta la **navegación de campos relacionales** en Odoo: acceder a campos encadenados como `m.fiscal_position_id.l10n_ar_tax_ids` es seguro incluso cuando `fiscal_position_id` está vacío (devuelve un recordset vacío). Por eso, expresiones como `not m.fiscal_position_id.l10n_ar_tax_ids` ya cubren el caso en que no haya posición fiscal y **no hace falta** añadir un chequeo previo separado sobre `fiscal_position_id`. -* Revisar accesos directos por índice en listas o recordsets, por ejemplo `lines[0].id`: si el conjunto está vacío puede lanzar `IndexError`. Copilot debe sugerir patrones más seguros (por ejemplo `if lines: first = lines[0]`) o, cuando sea posible, reescribir la lógica para trabajar sobre el recordset completo en lugar de un único elemento. - ---- - -## Cambios estructurales y scripts de migración – **cuestiones generales** - -Cuando el diff sugiera **cambios de estructura de datos**, **siempre evaluar** si corresponde proponer un **script de migración** en `migrations/` (pre/post/end). - -### Reglas generales de estructura de `migrations/` - -* La carpeta dentro de `migrations/` debe corresponder con la versión declarada en el manifest (p. ej. `migrations/18.0.4.0/`). -* Los scripts deben ser idempotentes, trabajar en lotes y registrar logs claros. - -### Ejemplos de cambios estructurales (actualizado con tus criterios) - -En estos casos **normalmente corresponde** proponer migración (salvo notas en contra): - -1. **Renombrar campos o modelos** - - * **Campos:** proponer migración **solo si el campo es almacenado** en base de datos: - * campos normales (`Char`, `Many2one`, `Boolean`, etc.), - * campos `compute` con `store=True`. - * Campos `compute` **sin** `store=True` no requieren script por el renombre en sí (son virtuales). - * **Modelos:** renombrar modelos **siempre** implica revisar migración (`ir.model`, `ir.model.data`, tablas relacionales, vistas, acciones…). - -2. **Cambiar tipos de campo** - - * Se considera cambio estructural cuando **cambia la representación en la base de datos** (p.ej. `Char → Many2one`, `Selection → Many2one`, `Integer → Monetary`, `Many2one → Many2many`, etc.). - * Cambios “compatibles” a nivel de PostgreSQL **no suelen requerir script**, por ejemplo: - * `Char → Text` o ajustes de tamaño de `Char`; - * cambios de precisión en `Float` sin cambio de semántica. - * Aun así, si el cambio implica lógica nueva (p.ej. pasar de `Boolean` a `Selection` con múltiples estados) puede requerir mapeo de datos. - -3. **Quitar campos para reestructurar información** - - * Por ejemplo, dividir un campo en varios (split) o fusionar varios en uno (merge). - * Siempre revisar si hay datos que deban preservarse antes de eliminar el campo original. - -4. **Agregar campos `compute` almacenados (`store=True`) con backfill** - - * Si el campo nuevo es `compute` y `store=True`, y se espera que tenga valor para **registros históricos**, conviene: - * Proponer **script `post`** que haga el backfill **en lotes**. - * Añadir una **advertencia explícita** cuando el modelo tiene muchos registros (p.ej. millones) para que el cálculo no se haga en una sola transacción que bloquee la tabla. - -5. **Cambiar dominios o valores de campos `selection`** - - * **Añadir nuevos valores de `selection`**: - En general **no requiere migración** si solo se agregan opciones nuevas y no se tocan las existentes. - * **Eliminar o renombrar keys existentes de `selection`**: - * Puede dejar valores históricos huérfanos o inválidos → proponer script que mapee `old_value → new_value` o que normalice registros antiguos. - * Mencionar que hay que tener en cuenta el comportamiento de campos relacionados (p.ej. un `Many2one` con `ondelete` específico) si el `selection` influye en lógica que crea o elimina registros. - * **Cambios de dominio** en campos relacionales (`Many2one`, `Many2many`): - * Si el nuevo dominio excluye valores usados históricamente, puede ser necesario limpiar o remapear datos para que no queden registros en estados imposibles. - * Recordar que el `ondelete` del campo define qué ocurre al eliminar registros apuntados; hay que respetarlo al limpiar datos. - -6. **Cambiar o añadir `_sql_constraints` (unique / index)** - - * Cambios en constraints `UNIQUE` o adición de nuevas constraints/índices pueden **fallar con datos existentes** (duplicados, valores nulos, etc.). - * Al menos, Copilot debe: - * emitir una **advertencia** sobre el riesgo de fallo en el upgrade, - * sugerir revisar datos previos (y, cuando se vea necesario, un **pre-script** que limpie duplicados o normalice datos antes de aplicar la constraint). - -7. **Cambios en `ir.model.data` / XML IDs** - - * Renombres de XML IDs (`module.name → module2.name2`) o cambios en `module` / `name` suelen requerir: - * script para actualizar referencias dependientes (acciones, vistas, menús, records en otros módulos), - * o uso de utilidades de upgrade. - * Caso especial: registros con `no_update="1"`: - * Si cambia solo texto/etiquetas menores, puede no hacer falta migración. - * **Si cambia el contenido lógico** (ej. campo `domain`, configuración, secuencias) y el registro tiene `no_update="1"`, debes **sugerir forzar el cambio**: - * vía script que actualice explícitamente los registros por su `xml_id`, - * o mediante un proceso de “force update” apropiado. - -8. **Cambios de reglas de acceso / propiedad** - - * Cambios profundos en `record rules` o en campos que determinan propiedad (company, website, owner…) pueden necesitar scripts para: - * recomputar propiedad, - * asignar company/website por defecto, - * o migrar datos entre reglas. - -> **Nota:** No se incluye en esta lista el caso “Añadir `required=True` a campos existentes sin default” como condición automática de migración; Copilot no debe sugerir script de migración **solo** por ese motivo, salvo que en el diff se vea claro que hay datos históricos incompatibles. - ---- - -## Scripts de migración en `migrations/`: pre / post / end (reglas generales) - -> **Objetivo:** preservar datos y mantener instalabilidad/actualizabilidad segura. - -- **pre**: Se ejecutan antes de actualizar el módulo. Útiles para preparar datos o estructuras que eviten fallos durante el upgrade. -- **post**: Se ejecutan justo después de actualizar el módulo. Ideales para recalcular datos, limpiar residuos o ajustar referencias tras el cambio. -- **end**: Se ejecutan al final de la actualización de todos los módulos. Indicados para tareas globales que dependen de múltiples módulos o para ajustes finales. - -### Mapeo de cambio → acción recomendada (actualizado) - -* **Rename de campo almacenado (mismo modelo)** - - * **Pre-script**: crear columna/alias temporal o copiar datos del campo viejo al nuevo antes de que Odoo toque el esquema, si el cambio puede romper constraints. - * **Post-script**: limpieza de residuos, recomputes de campos derivados si aplica. - -* **Renombrar modelo** - - * **Pre-script**: preparar mapeos en `ir.model` y `ir.model.data`, y ajustar referencias técnicas si es necesario. - * **Post-script**: re-enlazar vistas, acciones, menús, reglas y volver a chequear accesos. - -* **Eliminar campo y mover datos a otros campos (split/merge)** - - * **Pre-script**: copiar datos a los nuevos campos (cuando sea posible) antes de que el schema elimine la columna original. - * **Post-script**: normalizar referencias, recalcular computes, limpiar helpers. - -* **Agregar campo `compute` con `store=True`** - - * **Pre-script (opcional y solo en modelos muy grandes)**: crear columna en DB o preparar estructura para evitar locks largos. - * **Post-script (recomendado)**: backfill **en lotes** para poblar el valor almacenado; es importante para modelos con muchos registros. - -* **Cambiar tipo de campo con cambio real de representación** - - * **Pre-script**: crear columna temporal con el nuevo tipo y migrar datos (con conversión). - * **Post-script**: intercambiar/renombrar columnas, borrar la vieja, disparar recomputes si hace falta. - -* **Cambios en `selection` (eliminar/renombrar keys existentes)** - - * **Pre-script**: mapear valores antiguos → nuevos (tabla de mapeo) usando helpers como `change_field_selection_values()` cuando aplique. - * **Post-script**: validar que no quedan valores huérfanos y que las reglas de negocio siguen cumpliéndose. - * **Añadir keys nuevas**: **no proponer script** salvo que el diff muestre una migración masiva explícita de valores. - -* **Nuevas constraints `_sql_constraints` (unique) / índices** - - * **Pre-script (recomendado cuando haya riesgo)**: detectar y resolver duplicados o datos inconsistentes antes de crear la constraint. - * **Post-script**: crear el índice/constraint y, si procede, validar que no haya fallos. - -* **Cambios en registros XML con `no_update="1"`** - - * **Post-script**: actualizar esos registros por API (respetando `xml_id`) cuando el contenido lógico haya cambiado y no vaya a ser reaplicado por el upgrade normal. - -* **Cambios de reglas de acceso / multi-company / multi-website** - - * **Pre- o post-script** según el caso, para rellenar campos obligatorios (company, website, owner) y evitar que registros queden inaccesibles. - -> **Regla general:** si el cambio puede **romper durante el upgrade**, usa **pre-script**; si requiere **recalcular o reaplicar** después del código nuevo, usa **post-script**. Si se necesita una acción global al final, usa **end-script**. - ---- - -## Cobertura de tests automatizados – reglas generales - -* Cuando el diff introduzca **funcionalidad nueva no trivial** (nuevos métodos con lógica compleja, nuevos flujos de negocio, refactors grandes, nuevas APIs, etc.), revisar si existe cobertura de tests razonable para esos cambios. -* Si no se ve una cobertura clara, sugerir de forma **concreta y breve** qué tipo de test añadir (unitarios de modelo, tests de wizards, tours, pruebas sobre reportes, etc.), sin exigir una suite completa para cada cambio. -* Para cambios pequeños o puramente cosméticos (ajustes en textos, vistas simples, pequeñas correcciones) **no hace falta** proponer la creación de tests nuevos. - ---- - -## Convenciones de scripts en `migrations/` (generales) - -* Ubicación: `migrations//`. -* Nombres sugeridos: - - * `pre_.py` - * `post_.py` -* Requisitos: - - * Idempotentes (seguros si se ejecutan más de una vez). - * En lotes (`batch_size` razonable) para datasets grandes. - * Logs claros (uso de `_logger.info`). - * Manejo de transacciones cuando aplique (evitar locks largos). - * Documentar al inicio **qué suponen** y **qué garantizan**. - -**Esqueleto mínimo (ejemplo):** - -```python -# migrations//pre_rename_partner_ref.py -from odoo import api, SUPERUSER_ID - -def migrate(cr, registry): - env = api.Environment(cr, SUPERUSER_ID, {}) - partners = env['res.partner'].with_context(active_test=False).search([('old_ref', '!=', False)]) - for batch in range(0, len(partners), 500): - sub = partners[batch:batch+500] - for p in sub: - if not p.new_ref: - p.new_ref = p.old_ref -``` - -```python -# migrations//post_backfill_stored_amount_total.py -from odoo import api, SUPERUSER_ID - -def migrate(cr, registry): - env = api.Environment(cr, SUPERUSER_ID, {}) - Orders = env['sale.order'].with_context(active_test=False) - ids = Orders.search([]).ids - for i in range(0, len(ids), 200): - batch = Orders.browse(ids[i:i+200]) - # Forzar recompute del stored - batch._compute_amount_total() -``` - ---- - -## Checklist rápida para el review (general) - -| Categoría | Qué comprobar Copilot | -| ------------------ | -------------------------------------------------------------------------------------------------------- | -| Modelos | Relaciones válidas; constraints; uso adecuado de `@api.depends`; `super()` correcto | -| Vistas XML | Herencias correctas; campos válidos; adaptación a cambios de versión (p.ej. `` vs ``) | -| Seguridad | Accesos mínimos necesarios; reglas revisadas | -| Migraciones | **Si hay cambios estructurales, sugerir script en `migrations/` (pre/post/end)** y describir qué hace | -| Rendimiento / ORM | Evitar loops costosos; no SQL innecesario; aprovechar las optimizaciones del ORM de la versión | -| Ortografía & typos | Errores evidentes corregibles sin modificar idioma ni estilo | - ---- - -## Estilo del feedback (general) - -* Ser breve, claro y útil. Ejemplos: - - * “El campo `partner_id` no se encuentra referenciado en la vista.” - * “Este método redefine `write()` sin usar `super()`.” - * “Tip: hay un error ortográfico en el nombre del parámetro.” - * **Migración:** “Se renombra `old_ref` → `new_ref`: falta **pre-script** en `migrations/` para copiar valores antes del upgrade; añadir **post-script** para recompute del stored.” - -* Evitar explicaciones largas o reescrituras completas salvo que el cambio sea claro y necesario. -* Priorizar comentarios en forma de **lista corta de puntos** (3–7 ítems) y frases breves en lugar de bloques de texto extensos. - ---- - -## Resumen operativo para Copilot - -1. **Si hay cambio estructural (según la lista actualizada) → propone y describe script(s) de migración en `migrations/` (pre/post/end)**, con enfoque idempotente y en lotes. -2. Distingue entre: - - * **cuestiones generales** (válidas para cualquier versión), - * y **matices específicos de Odoo 18** (por ejemplo, uso de ``, passkeys, tours y comportamiento del framework). - -3. Mantén el feedback **concreto, breve y accionable**. \ No newline at end of file +2. Feedback **breve, concreto y accionable**. Lista corta de 3–7 puntos. Evitar párrafos largos y no repetir lo que ya dice la descripción del PR. +3. Corregir errores de tipeo u ortografía evidentes en nombres y comentarios (cuando sean claros). +4. No proponer traducciones de docstrings/comentarios entre idiomas. +5. No exigir docstrings en métodos que no los tienen. Si ya existe uno, PEP8 alcanza; falta de tipos o `return` **no es un error**. +6. No proponer cambios puramente estéticos (espacios, comillas, orden de imports). +7. Traducciones: `_()` y `self.env._()` son indistintos; solo marcar mensajes/textos al usuario que no estén envueltos. + +## Resumen operativo + +- **Si hay cambio estructural** (rename de campos almacenados, cambio de tipo, split/merge, nuevos `compute` con `store=True` con backfill, cambio de keys de `selection`, nuevas `UNIQUE`, cambios en `ir.model.data`/XML IDs) → **proponer script de migración** en `migrations//` con enfoque idempotente y en lotes. Ver `migrations.instructions.md`. +- **Si hay cambio en modelos** → aplicar `models.instructions.md`. +- **Si hay cambio en vistas XML** → `views.instructions.md`. +- **Si hay cambio en seguridad / ACL / `cr.execute` / `eval`** → `security.instructions.md`. +- **Si cambia `__manifest__.py`** → `manifest.instructions.md`. +- **Si el diff es grande y sensible a performance** → `performance.instructions.md`. +- **Si introduce funcionalidad no trivial sin tests** → `tests.instructions.md`. +- **Si hay texto al usuario sin `_()`** → `i18n.instructions.md`. + +## Versionado Odoo + +Cada módulo declara versión en `__manifest__.py`. Cuando hay diferencias relevantes entre v18 y v19, los archivos en `instructions/` marcan la regla como "Odoo 19+" o "Odoo 18". + +Cambios clave de Odoo 19 a tener en cuenta (detalle en cada `instructions.md` específica): +- `_sql_constraints` → `models.Constraint`, `models.Index`, `models.UniqueIndex`. +- `@api.one`/`@api.multi` eliminados; `@api.ondelete` para validación de borrado. +- `` → ``; `attrs={...}` → atributos directos (`invisible=`, `readonly=`). +- `t-esc` deprecado → `t-out`. +- `cr.execute(...)` crudo desaconsejado → clase `SQL` con `execute_query_dict()`. +- Dominios con clase `Domain` y operadores `&`, `|`, `~` sobre instancias. +- Crons: `_commit_progress(remaining=, processed=)` en lugar de `notify_progress`. +- `category_id` de `res.groups` → `privilege_id` + `res.groups.privilege`. + +## Estilo del feedback + +- Formato recomendado: `**categoría** · descripción concreta · sugerencia`. +- Un comentario por issue; no duplicar la misma observación en varios archivos. +- Preferir mencionar la regla concreta (ej. "queries parametrizadas") antes que la teoría. +- **Checklist rápida**: + +| Categoría | Qué comprobar | +|---|---| +| Modelos | Relaciones con `comodel_name`/`ondelete`; `@api.depends` correcto; `super()` preservado | +| Vistas XML | Herencias con `xpath` acotado; campos existentes; nada de redefinir vistas enteras | +| Seguridad | ACL mínimo; sin `cr.execute` con interpolación; sin `eval()` sobre input externo | +| Migraciones | Cambios estructurales → script idempotente en lotes | +| Rendimiento | Sin `search`/`write`/`create` en loop; `mapped`/`filtered`/`search_count`/`_read_group` | +| i18n | Textos al usuario envueltos en `_()`; no marcar nombres técnicos ni claves de dict | diff --git a/.github/instructions/i18n.instructions.md b/.github/instructions/i18n.instructions.md new file mode 100644 index 0000000..ed7fc7b --- /dev/null +++ b/.github/instructions/i18n.instructions.md @@ -0,0 +1,71 @@ +--- +applyTo: + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" + - "**/controllers/**/*.py" + - "**/report/**/*.py" + - "**/i18n/**/*.po" + - "**/i18n/**/*.pot" +--- + +# Revisión de internacionalización (i18n) + +## Marcar texto traducible + +- Todo texto que se muestra al usuario debe estar envuelto en `_()` o `self.env._()` (indistinto): + ```python + raise UserError(_("No se puede eliminar un pedido confirmado.")) + return {'warning': {'title': _("Atención"), 'message': _("Stock insuficiente.")}} + ``` +- Import típico: `from odoo import _, _lt` (usar `_lt` cuando el texto se define a nivel módulo/clase y la traducción se resuelve en runtime). + +## Qué marcar como issue + +- `raise UserError("...")` o `ValidationError("...")` con string literal. +- `return {'warning': {'message': "texto"}}`. +- Mensajes de `raise`, `notifications`, toast, `display_name` calculado, labels en wizards, títulos de acciones construidas dinámicamente. +- Textos en `_message_post` que muestran al usuario. + +## Qué NO marcar + +- Nombres técnicos de campos (`'partner_id'`, `'name'`). +- Claves de diccionarios (`'state': 'draft'`). +- Logs técnicos (`_logger.info("...")`) — no se traducen. +- Nombres de xml_ids. +- Cadenas en tests, comentarios, docstrings. +- `fields.Char(string="Name")`: el `string=` se recoge para i18n automáticamente, no requiere `_()`. + +## Uso correcto de `_()` + +- `_()` resuelve traducción **en el momento de la llamada** (runtime del idioma del usuario). +- `_lt()` (lazy translate) para strings definidas a nivel módulo; la traducción se resuelve al serializar, útil en selecciones y listas de constantes. +- No concatenar fragmentos traducibles con `+`: usar `%` o f-string sobre la cadena ya traducida: + ```python + # MAL (rompe traducción) + raise UserError(_("Error en ") + record.name) + # BIEN + raise UserError(_("Error en %s") % record.name) + ``` +- Evitar format con claves traducibles múltiples; preferir placeholders con nombre: + ```python + raise UserError(_("Falta %(field)s en %(model)s") % {'field': name, 'model': model}) + ``` + +## Archivos `.po` / `.pot` + +- No editar manualmente traducciones generadas por `odoo i18n export` salvo correcciones puntuales. +- Commits que solo tocan `.po` / `.pot` de exportación suelen ser benignos; no requieren tests ni script de migración. +- Si se agrega un idioma nuevo, verificar que esté listado en `i18n/` y que las cadenas base existan en `.pot`. + +## Convención del equipo (ADHOC) + +- Idioma destino principal: **español latinoamericano formal**. Evitar tuteo en mensajes de sistema ("usted" vs "tú"). +- Mantener consistencia terminológica: "pedido" (no "orden"), "contacto" (no "partner" en user-facing), "factura", etc. +- Placeholders (`%s`, `%(name)s`) deben mantenerse idénticos entre el mensaje original y la traducción. + +## Criterio de severidad + +- **Medio**: texto al usuario sin `_()`, aislado. +- **Bajo**: patrón que podría mejorarse (concatenación con `+`, falta de `_lt` en constante de módulo). +- No-issue: archivo `.po` autogenerado con cambios de exportación rutinarios. diff --git a/.github/instructions/manifest.instructions.md b/.github/instructions/manifest.instructions.md new file mode 100644 index 0000000..4444016 --- /dev/null +++ b/.github/instructions/manifest.instructions.md @@ -0,0 +1,48 @@ +--- +applyTo: + - "**/__manifest__.py" +--- + +# Revisión de `__manifest__.py` + +## Archivos referenciados + +- Todo archivo usado por el módulo (vistas, seguridad, datos, reportes, wizards, demo) debe estar listado en alguna de las claves del manifest (`data`, `demo`, `assets`). +- Si un archivo XML/CSV se borra del módulo, debe removerse del manifest; si se agrega uno nuevo, debe incluirse. +- Orden relativo importa: datos de seguridad antes de datos que los referencian; vistas después de sus modelos. + +## Dependencias (`depends`) + +- Deben listarse todos los módulos cuyos modelos/vistas/xml_ids se usan directamente. +- **No** declarar dependencias innecesarias (infla el árbol de instalación). +- Módulos de localización (`l10n_*`) solo cuando el módulo depende funcionalmente; no por conveniencia. + +## Versión + +- Formato `...` (ej. `19.0.1.0.0`). La serie (`19.0`, `18.0`) debe coincidir con la rama y la versión de Odoo target. +- **Regla obligatoria de versión**: cualquier cambio estructural que requiera script en `migrations/` debe **bumpear la versión** del módulo, y la carpeta bajo `migrations/` debe coincidir. +- Solo comentar la versión **una vez por revisión**, aunque haya múltiples archivos afectados. + +## Metadatos + +- `name`, `summary`, `description` deben estar definidos y ser consistentes. +- `author`, `license` presentes. En Adhoc, típicamente `"ADHOC SA"` y licencia según convención del repo. +- `category` coherente con el tipo de módulo. +- `installable: True` salvo que explícitamente esté siendo discontinuado. +- `application: True` solo para módulos que deben aparecer como aplicación raíz (no para sub-módulos). + +## Assets (bundles) + +- `assets` debe listar bundles correctos (`web.assets_backend`, `web.assets_frontend`, `web.report_assets_common`, `web.assets_tests`, etc.). +- Extensiones coherentes: `.js`, `.scss`, `.css`, `.xml` (OWL templates). +- Archivos borrados deben quitarse también de `assets`. + +## Hooks + +- `pre_init_hook`, `post_init_hook`, `uninstall_hook`, `post_load`: si están declarados, verificar que apunten a funciones existentes en el módulo (`from . import hooks` o similar). +- Los hooks deben ser idempotentes y no dependientes de datos demo. + +## Demo data + +- Datos de demo en la key `demo`, **no** mezclados con `data`. +- Al introducir funcionalidad nueva que se beneficia de casos visibles, considerar agregar demo; al introducir módulo de configuración, no es necesario. diff --git a/.github/instructions/migrations.instructions.md b/.github/instructions/migrations.instructions.md new file mode 100644 index 0000000..9557bb2 --- /dev/null +++ b/.github/instructions/migrations.instructions.md @@ -0,0 +1,59 @@ +--- +applyTo: + - "**/migrations/**/*.py" + - "**/__manifest__.py" + - "**/models/**/*.py" +--- + +# Revisión de scripts de migración + +> Si el diff introduce cambio estructural en un modelo, **siempre** evaluar si corresponde proponer script en `migrations//`. + +## Cuándo proponer script + +1. **Rename de campo almacenado** (`Char`, `Many2one`, etc. o `compute` con `store=True`). **No** si es `compute` sin store. +2. **Rename de modelo**: siempre. Toca `ir.model`, `ir.model.data`, tablas relacionales, vistas, acciones. +3. **Cambio de tipo de campo** con cambio real en DB (`Char→Many2one`, `Selection→Many2one`, `Many2one→Many2many`). Cambios compatibles (`Char→Text`, ajustes de `Float`) no requieren script. +4. **Split/merge de campos**. +5. **Nuevo `compute` con `store=True`** que aplique a registros históricos → post-script de backfill en lotes. Advertir si el modelo tiene millones de registros. +6. **Cambio en keys de `selection`**: renombrar/eliminar existentes → script que mapee `old → new`. Agregar nuevas keys **no** requiere script. +7. **Cambio de dominio** en relacional que excluya valores usados históricamente → limpiar/remapear. +8. **Nueva `UNIQUE`/índice** (`_sql_constraints` o `models.Constraint`): pre-script que resuelva duplicados antes de crear la constraint. +9. **Cambios en `ir.model.data` / XML IDs** (rename `module.name → module2.name2`): script para actualizar referencias. +10. **Registros con `noupdate="1"`** cuyo contenido lógico cambia: forzar update por `xml_id`. +11. **Cambios en reglas de acceso / multi-company / multi-website**: rellenar campos obligatorios, recomputar ownership. + +> **No** proponer script solo por `required=True` nuevo sin default, salvo que el diff evidencie datos históricos incompatibles. + +## Pre / Post / End + +- **pre**: antes del update. Preparar datos/esquemas para evitar fallos. +- **post**: después. Recalcular, limpiar, ajustar referencias. +- **end**: al final del upgrade global. Tareas cross-módulo o finales. + +Regla: **rompe durante el upgrade → pre**; **recalcula después → post**; **global al final → end**. + +## Mapeo cambio → acción + +- **Rename campo almacenado** → pre: copiar datos viejo→nuevo. Post: cleanup + recomputes. +- **Rename modelo** → pre: mapear `ir.model`/`ir.model.data`. Post: re-enlazar vistas, acciones, menús, reglas. +- **Split/merge** → pre: copiar a nuevos campos antes de que el schema borre el viejo. Post: normalizar/recompute. +- **`compute` nuevo con `store=True`** → post: backfill en lotes (pre opcional en modelos grandes para preparar columna). +- **Cambio de tipo con conversión** → pre: columna temporal + conversión. Post: swap/rename/borrar vieja. +- **`selection` (remove/rename keys)** → pre: mapeo `old → new` (usar `change_field_selection_values` si aplica). Post: validar consistencia. +- **Nueva `UNIQUE`** → pre: resolver duplicados. Post: crear índice si aplica. +- **`noupdate="1"` con cambio lógico** → post: update por `xml_id`. + +## Convenciones + +- Ubicación: `migrations//` (ej. `migrations/19.0.1.0/`). Versión debe coincidir con `__manifest__.py`. +- Nombres: `pre_.py`, `post_.py`, `end_.py`. +- **Idempotentes**: seguros ante re-ejecución. +- **En lotes** (`batch_size` razonable) para datasets grandes. +- Logs claros (`_logger.info`); comentario al inicio documentando supuestos y garantías. +- Evitar transacciones muy largas; `env.cr.commit()` controlado o helpers de progreso. + +## Versión del manifest + +- Al introducir cambio estructural, **bumpear** versión en `__manifest__.py` para que el script corra (ej. `19.0.1.0 → 19.0.2.0`). +- La carpeta bajo `migrations/` debe coincidir con la nueva versión. diff --git a/.github/instructions/models.instructions.md b/.github/instructions/models.instructions.md new file mode 100644 index 0000000..af27593 --- /dev/null +++ b/.github/instructions/models.instructions.md @@ -0,0 +1,68 @@ +--- +applyTo: + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" + - "**/report/**/*.py" +--- + +# Revisión de modelos Python + +## Relaciones y campos + +- `Many2one`/`One2many`/`Many2many` deben declarar `comodel_name` y `ondelete` apropiado. Evitar `ondelete='cascade'` sin justificación. +- Nombres de campos claros, consistentes, sin conflictos con campos heredados. +- `required=True` sin `default` **solo** si no hay datos históricos que puedan romperse. Si los hay, proponer `default` o migración. +- Campos `compute` con `store=True` que dependen de datos históricos pueden necesitar backfill (ver `migrations.instructions.md`). + +## Decoradores `@api.*` + +- `@api.depends` debe listar **todas** las dependencias reales, incluidas las dotted (`@api.depends('partner_id.email')`). +- `@api.constrains` **no** acepta dotted paths, solo nombres simples. +- `@api.onchange` no debe escribir a BD ni modificar campos computados. +- Evitar decoradores obsoletos: `@api.one`, `@api.multi` (Odoo 13+ no los acepta). +- **Odoo 18+**: para prevenir borrado usar `@api.ondelete(at_uninstall=False)` en vez de sobreescribir `unlink`. +- `@api.model` solo cuando el método no depende de `self` como recordset. +- `@api.model_create_multi` para métodos `create` que aceptan lista de dicts (obligatorio en Odoo 17+). + +## Herencia y `super()` + +- Métodos redefinidos deben llamar `super()` salvo que el contrato diga lo contrario. Preservar el tipo/shape del retorno. +- `_name` + `_inherit` juntos solo cuando se busca crear modelo nuevo (multi-table inheritance); marcar si no hay razón clara. +- No sobrescribir `create`/`write`/`unlink` solo para side effects triviales; preferir `@api.depends`, `@api.constrains` o `@api.ondelete`. + +## Constraints e índices + +- **Odoo 19+**: usar `models.Constraint(...)`, `models.Index(...)`, `models.UniqueIndex(...)` como declarativas a nivel de clase, en vez de `_sql_constraints`. Si el diff ya toca constraints, sugerir migrar a la nueva API. +- Mensajes de constraint deben ser traducibles (`_("...")`). +- Añadir `UNIQUE` sobre tabla con datos existentes puede fallar; ver `migrations.instructions.md`. + +## ORM seguro y eficiente + +- Evitar `search` dentro de loops → usar dominio con `in` sobre ids o `_read_group`. +- Evitar `write`/`create`/`unlink` uno a uno en loops → vectorizar sobre recordset. +- `create` en Odoo 17+: preferir lista de dicts `create([{...}, {...}])`. +- `mapped`, `filtered`, `search_count`, `search_fetch` antes que recorrer en Python. +- Navegación relacional segura: `rec.partner_id.email` devuelve falso si `partner_id` vacío; no duplicar el check. +- Acceso por índice (`recordset[0]`) puede lanzar `IndexError`; guardar con `if rec: ...` o rediseñar para operar sobre el recordset completo. +- Evitar `sudo()` amplio/innecesario en métodos de negocio; justificar cada uso. +- En Odoo 19, `cr.execute` crudo desaconsejado → usar clase `SQL` con `execute_query_dict()`. Si hay `cr.execute` con interpolación (`%`, f-string, `.format`) → bloqueante, ver `security.instructions.md`. + +## Nombres y estilo + +- Métodos privados prefijo `_` (sigue siendo la convención estándar; ya bloquea RPC por sí solo). `@api.private` **no** es un reemplazo del prefijo: es para el caso de excepción de un método sin `_` (API pública existente, o método interno del ORM) que necesita bloquearse de RPC sin renombrarlo. Ver docstring de `private` en `odoo/orm/decorators.py`. +- Métodos muy largos (>50 líneas) → sugerir split. +- Comparaciones booleanas: `if x:` / `if not x:` (no `== True` / `== False`). +- `else` después de `return` innecesario. +- Imports no utilizados deben removerse. + +## Dominios + +- En Odoo 19 es válido `Domain('field', 'op', 'value')` y combinar con `&`, `|`, `~`. No marcar como error. +- `Domain` permite uso en `filtered`: no hace falta convertir a lista. +- Nunca construir dominios como strings y pasarlos por `eval` (ver `security.instructions.md`). + +## Selecciones + +- Agregar nuevos values a un `selection` **no** requiere migración. +- Renombrar/eliminar keys existentes → proponer script que mapee `old → new` (ver `migrations.instructions.md`). diff --git a/.github/instructions/performance.instructions.md b/.github/instructions/performance.instructions.md new file mode 100644 index 0000000..e2e66e4 --- /dev/null +++ b/.github/instructions/performance.instructions.md @@ -0,0 +1,82 @@ +--- +applyTo: + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" + - "**/controllers/**/*.py" + - "**/report/**/*.py" +--- + +# Revisión de rendimiento (ORM) + +## Anti-patrones que bloquean performance + +- **Search en loop** → N+1 queries. Reemplazar por una sola `search` con dominio `in` sobre ids, o `_read_group` / `search_fetch`. + ```python + # MAL + for order in orders: + payments = self.env['payment'].search([('order_id', '=', order.id)]) + # BIEN + payments = self.env['payment'].search([('order_id', 'in', orders.ids)]) + ``` +- **Create/write/unlink en loop** → múltiples roundtrips a DB. Vectorizar: + ```python + # MAL + for vals in data: + self.env['res.partner'].create(vals) + # BIEN (Odoo 17+) + self.env['res.partner'].create(data) # lista de dicts + ``` +- **`search([])` + filtrado en Python** → traer todos los records. Usar dominio preciso. +- **`mapped` en loop** sobre recordsets grandes → preferir una única `.mapped('field')` fuera del loop. + +## `@api.depends` afinado + +- Listar todas las dependencias **reales**, incluidas las dotted: `@api.depends('partner_id.email')` para evitar consultas extra. +- No listar campos ajenos al compute (dispara recomputes innecesarios). +- Evitar depender de campos no almacenados en cadenas largas. + +## Agregados + +- Para sumar/contar preferir `read_group` / `_read_group` / `formatted_read_group` (Odoo 17+) antes que iterar + `sum`/`len`. +- `search_count(domain)` en vez de `len(search(domain))`. +- `browse(ids)` en lugar de re-buscar cuando ya se tienen ids. + +## Relacionales + +- **N+1 por navegación**: si un `@api.depends` dispara muchas lecturas, ajustar dependencias o prefetch. +- `mapped('campo_relacional.subcampo')` agrupa lecturas y usa prefetch; preferir a loops manuales. +- `filtered_domain(domain)` para filtrados con mismo idioma que `search`. + +## Cron y jobs largos + +- **Odoo 19**: usar `self.env['ir.cron']._commit_progress(remaining=N)` / `_commit_progress(processed=M)` en crons en lugar de `notify_progress` / commits manuales ad hoc. +- Procesar en **lotes** (`batch_size` razonable, p. ej. 500–1000) y commitear por lote. +- Logs con `_logger.info` para observabilidad. + +## Computes y store + +- `store=True` sobre `compute` implica backfill en historia → ver `migrations.instructions.md`. +- `compute` sin store se reevalúa por read; si se accede repetidas veces en un loop, cachear localmente. +- `write` dentro de un compute → anti-patrón, genera recursión o recomputes encadenados. + +## Transacciones + +- `flush()` explícito solo cuando se requiere forzar la orden de escritura antes de leer. No usar en loops. +- `env.cr.commit()` en crons o scripts de migración, pero nunca dentro de lógica transaccional de usuario. +- `invalidate_cache` solo si hay razón concreta (modificación externa por SQL directo). + +## Vistas XML relacionadas (cross-reference) + +- Filtros en listas grandes sobre campos no indexados → sugerir `index=True` en el modelo. +- Columnas de lista que nunca se muestran: `column_invisible="1"` (evita cargar valores). Ver `views.instructions.md`. + +## Cuándo NO optimizar + +- Loops sobre recordsets pequeños y acotados (< ~20 elementos) donde la claridad gana a la micro-optimización. +- Código de setup/install que corre una única vez. +- Para diffs chicos y acotados, evitar proponer reescrituras masivas — preferir marcar la regla para futuras iteraciones. + +## Beneficios indirectos + +- Mantenerse dentro del ORM hereda controles de acceso, auditoría, reglas multi-compañía y prefetch automático. Queries crudas pierden todo eso. diff --git a/.github/instructions/security.instructions.md b/.github/instructions/security.instructions.md new file mode 100644 index 0000000..56f097e --- /dev/null +++ b/.github/instructions/security.instructions.md @@ -0,0 +1,62 @@ +--- +applyTo: + - "**/security/**" + - "**/controllers/**/*.py" + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" +--- + +# Revisión de seguridad + +## ACL y reglas de acceso + +- Modelo nuevo debe tener fila en `security/ir.model.access.csv` con permisos **mínimos necesarios**. No abrir `perm_unlink` o `perm_write` si no se justifica. +- Campos sensibles (datos personales, flags de configuración, credenciales) deben restringirse por `groups="..."`. +- `record rules` (`ir.rule`) nuevas deben cubrir multi-compañía cuando el modelo tiene `company_id`. Verificar reglas globales vs por grupo. +- **Odoo 19**: `res.groups.category_id` fue reemplazado por `privilege_id` + `res.groups.privilege`; al crear grupos usar la nueva estructura. + +## SQL injection + +- **Bloqueante**: `self.env.cr.execute("... '%s' ..." % var)` o con f-string/`.format`. Toda variable debe pasar como parámetro: + ```python + self.env.cr.execute("SELECT id FROM res_partner WHERE name = %s", (name,)) + ``` +- Preferir dominio ORM: `self.env['res.partner'].search([('name', '=', name)])`. +- **Odoo 19**: usar clase `SQL` con `execute_query_dict()` para consultas seguras; marcar si se ve `cr.execute` crudo. + +## Ejecución arbitraria y deserialización + +- `eval()`, `exec()`: nunca sobre input del usuario. +- Dominios construidos como string y pasados por `eval` → bloqueante. Usar lista de tuplas o `Domain(...)`. +- `safe_eval` permitido solo sobre contextos controlados; marcar si viene de parámetros de request. +- `pickle.loads`, `yaml.load` (sin `SafeLoader`), `marshal`: prohibidos con data no confiable. + +## Bypass de reglas + +- `sudo()` en controllers/wizards: cada uso requiere justificación explícita. Evitar `sudo()` amplio a nivel de método. +- `with_user(SUPERUSER_ID)` sólo para operaciones de sistema documentadas. +- Accesos multi-compañía sin `company_id` explícito: riesgo de leakage; exigir scoping. + +## Controllers HTTP + +- `auth='public'` con escritura o acceso a datos sensibles → riesgo. Evaluar si debería ser `auth='user'` o `auth='portal'`. +- `@http.route(..., csrf=False)` solo para endpoints no-UI (webhooks, APIs) y con autenticación alternativa; marcar si se desactiva sin justificación. +- `browse(int(request.params.get('id')))`: validar pertenencia del registro al usuario actual antes de operar. +- Input del usuario que llega a SQL, filesystem o shell → ver secciones específicas. + +## Filesystem y comandos + +- `subprocess.*` con `shell=True` → bloqueante. Pasar args como lista. +- Paths construidos con input del usuario sin validar → path traversal. Usar `werkzeug.utils.secure_filename` o equivalente. +- URLs descargadas con input del usuario → riesgo SSRF; validar esquema y host permitido. + +## Sensibles específicas Odoo 19 + +- Integraciones IA, VOIP, WhatsApp, Equity/ESG: cambios acá pueden requerir migración de tokens/ownership. Revisar con atención y sugerir script si aplica (ver `migrations.instructions.md`). + +## Criterio de severidad + +- **Bloqueante** (BLOCKER): SQL injection, eval sobre input, shell=True, deserialización insegura, `auth='public'` con efectos secundarios graves. +- **Alto** (HIGH): `sudo()` sin justificación, bypass de ACL, record rules faltantes. +- **Medio** (MEDIUM): falta `groups` en campos sensibles, `noupdate` sin considerar consecuencias. diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 0000000..34d8128 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,64 @@ +--- +applyTo: + - "**/tests/**/*.py" + - "**/models/**/*.py" + - "**/wizards/**/*.py" + - "**/wizard/**/*.py" + - "**/controllers/**/*.py" +--- + +# Revisión de cobertura de tests + +## Cuándo sugerir tests + +Sugerir agregar tests cuando el diff introduce **funcionalidad no trivial**: + +- Métodos nuevos con lógica de negocio (cálculos, validaciones, transiciones de estado). +- Nuevos flujos/wizards completos. +- Refactors amplios de código existente (especialmente si cambia firma de métodos públicos). +- Nuevas APIs/endpoints de controladores. +- Cambios en reportes que alteran la salida. +- Overrides de `create`/`write`/`unlink` con side effects. + +## Cuándo NO sugerir + +- Cambios puramente cosméticos (textos, vistas simples, ajustes de estilo). +- Correcciones menores sin cambio de comportamiento. +- Solo traducciones / solo documentación. +- Renombres de variables. + +## Tipo de test apropiado + +- **Unitario de modelo** (`TransactionCase` / `TestCase`): validar métodos, constraints, computes, onchanges. +- **Wizard test**: instanciar wizard, setear campos, disparar acción, assert resultado. +- **HttpCase**: controladores, rutas, autenticación, respuesta. +- **Tour** (`odoo.tests.common.HttpCase` + tour JS): flujos de UI críticos, especialmente en OWL components. +- **Reporte**: generar reporte contra data conocida y comparar output. + +## Calidad del test + +- `setUp` preparando datos mínimos; preferir factory methods o datos de demo. +- Assertions concretas: no `assertTrue(result)` si se puede `assertEqual(result, expected)`. +- Decoradores apropiados: `@tagged('post_install', '-at_install')` para tests que dependen de módulos dependientes. +- Evitar dependencias del orden de ejecución entre tests; cada test debe ser independiente. +- Si el test crea registros con datos predecibles, usar ids/xml_ids estables para poder referenciarlos. + +## Patrones a marcar como issue + +- Test nuevo sin `assertEqual` / `assertRaises` / similar → no valida nada. +- `try: ... except: pass` en tests → oculta fallos. +- Tests que dependen de la hora del sistema sin `freeze_time` / `mute_logger` donde aplica. +- Tests que modifican `noupdate` records sin restaurar estado. + +## Criterio de suficiencia + +- No exigir una suite completa por cada cambio. +- Una sugerencia concreta y breve es suficiente: "Para este método de cálculo, podría agregarse un test unitario que cubra el caso X." (sin diseñar la suite entera). +- Si el módulo ya tiene una carpeta `tests/` con cobertura previa similar, sugerir seguir el mismo estilo. + +## En PRs que SÍ agregan tests + +- Verificar que el test realmente cubra el diff (no solo código alrededor). +- Que no haga mocks innecesarios del ORM (regla del equipo: preferir tests de integración sobre mocks de BD). +- Que se ejecute: nombre `test_*.py`, clase `Test*`, método `test_*`. +- `__init__.py` en `tests/` importa el nuevo archivo. diff --git a/.github/instructions/views.instructions.md b/.github/instructions/views.instructions.md new file mode 100644 index 0000000..304409d --- /dev/null +++ b/.github/instructions/views.instructions.md @@ -0,0 +1,60 @@ +--- +applyTo: + - "**/views/**/*.xml" + - "**/reports/**/*.xml" + - "**/data/**/*.xml" +--- + +# Revisión de vistas XML y QWeb + +## Herencia + +- Usar `inherit_id` + `xpath` específico en vez de redefinir la vista entera. +- `xpath` debe apuntar a un elemento único y estable: preferir `//field[@name='...']` o `//group[@name='...']` antes que índices de `child::`. +- Evitar `position="replace"` cuando `position="attributes"` o `position="after"/"before"/"inside"` alcanza. +- No duplicar grandes bloques de `arch`: heredar y sobreescribir lo mínimo necesario. + +## Campos referenciados + +- Todo `` debe existir en el modelo correspondiente (y ser accesible por el usuario). +- Campos usados en atributos como `invisible="..."`, `readonly="..."`, `required="..."` también deben estar declarados en la vista (si no, agregar con `invisible="1"`). + +## Atributos dinámicos (Odoo 17+) + +- `attrs="{'invisible': [...]}"` **deprecado**. Usar atributos directos: `invisible="field == 'done'"`, `readonly="state in ['done','cancel']"`, `required="type_id"`. +- Expresiones en atributos usan sintaxis Python sobre los campos disponibles del registro actual. +- En listas (``): para campos que nunca se muestran, usar `column_invisible="1"` en vez de `invisible="1"` (evita cargar valores innecesariamente). + +## `` vs `` (Odoo 19) + +- Odoo 19 usa `` en vez de `` como tag de lista. +- Atributos frecuentes: `editable="bottom"`, `multi_edit="1"`, `decoration-*`, `optional="show|hide"` en fields. +- Si el diff introduce `` en módulo v19 → marcar como cambio obligatorio a ``. + +## Kanban y QWeb + +- **Odoo 19+**: templates kanban usan `t-name="card"` (antes `t-name="kanban-box"`). +- `t-esc` deprecado → usar `t-out` para escribir valores (aplica a todas las versiones recientes). +- `t-options-widget` sólo sobre campos; no abusar. + +## Búsquedas y filtros + +- Filtros de búsqueda sobre campos no indexados en datasets grandes → sugerir `index=True` en el field o filtro alternativo. +- `` debe tener `name` único para poder heredarse. + +## Acciones y menús (cuando vengan en el mismo diff) + +- `ir.actions.act_window` debe declarar `res_model`; `view_mode` consistente con vistas existentes. +- Menús heredados con `parent_id` correcto; evitar duplicación de `sequence`. +- Nuevos menús deben tener permisos coherentes (grupo o reglas ACL). + +## Datos XML + +- `` nuevos deben tener `id` con convención `module__`. +- Usar `noupdate="1"` con cuidado: si más adelante cambia el contenido lógico, requiere script de migración forzando el update por `xml_id`. +- No mezclar datos de demo con datos funcionales (carpetas `data/` vs `demo/` y declaración en manifest). + +## Reportes QWeb + +- Templates deben heredar estilos base (`web.external_layout` o similar) en vez de duplicar CSS inline. +- `t-call` para layouts; `t-field` para renderizar valores con su widget; `t-out`/`t-esc` ya no es necesario si se usa `t-field`. diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index baa05db..a4f0356 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,28 +18,46 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: + - + name: Block sensitive file changes from fork PRs + if: >- + github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + changed=$(gh api --paginate \ + "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \ + --jq '.[].filename') + if echo "$changed" | grep -qE '^(\.github/workflows/|\.pre-commit-config\.yaml$)'; then + echo "::error::Fork PRs may not modify workflows or the pre-commit config. Blocked for security." + exit 1 + fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.ref }} + allow-unsafe-pr-checkout: true - id: setup-python name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.10" cache: "pip" - name: Pre-commit cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ~/.cache/pre-commit key: pre-commit|${{ steps.setup-python.outputs.python-version }}|${{ hashFiles('.pre-commit-config.yaml') }} - id: precommit name: Pre-commit - uses: pre-commit/action@v3.0.1 + run: | + pip install pre-commit + pre-commit run --all-files --show-diff-on-failure --color=always - name: Create commit status if: github.event_name == 'pull_request_target' diff --git a/.gitignore b/.gitignore index 59c9908..a3c9903 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,9 @@ coverage.xml # Sphinx documentation docs/_build/ +# Vscode +.vscode/ + ### macOS ### # General .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c4be55f..4539e69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: - id: check-executables-have-shebangs - id: check-merge-conflict args: ['--assume-in-merge'] - exclude: '\.rst$' + exclude: '\.(rst|md)$' - id: check-symlinks - id: check-xml - id: check-yaml