From 7420b7f23c25e6f46fe9a9c4d8da5a10e4aa0e73 Mon Sep 17 00:00:00 2001 From: Matias Peralta Date: Mon, 13 Oct 2025 17:47:03 -0300 Subject: [PATCH 01/24] [FIX] price_security_sale_margin: hide margin and margin_percent for price-restricted users closes ingadhoc/product#787 Signed-off-by: Franco Leyes --- price_security_sale_margin/models/sale_order.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/price_security_sale_margin/models/sale_order.py b/price_security_sale_margin/models/sale_order.py index e7c6bf3dd..fc232771d 100644 --- a/price_security_sale_margin/models/sale_order.py +++ b/price_security_sale_margin/models/sale_order.py @@ -22,15 +22,15 @@ def _get_view(self, view_id=None, view_type="form", **options): modifiers["readonly"] = True node.set("modifiers", json.dumps(modifiers)) if self.env.user.has_group("price_security.group_only_view_sale_price"): - invisible_fields = ( - arch.xpath("//field[@name='purchase_price']") - + arch.xpath("//field[@name='margin']") - + arch.xpath("//field[@name='margin_percent']") - + arch.xpath("//field[@name='margin_percent']/..") + invisible_fields = arch.xpath( + "//field[@name='purchase_price']" + "|//field[@name='order_line']//field[@name='margin']" + "|//field[@name='order_line']//field[@name='margin_percent']" + "|//div[@class='d-flex float-end']" ) for node in invisible_fields: - node.set("column_invisible", "1") + node.set("invisible", "1") modifiers = json.loads(node.get("modifiers") or "{}") - modifiers["column_invisible"] = True + modifiers["invisible"] = True node.set("modifiers", json.dumps(modifiers)) return arch, view From 0724eb651f27e5650099a64289a96871f136734a Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:46:26 -0300 Subject: [PATCH 02/24] [ADD] Copilot instructions --- .github/copilot-instructions.md | 256 ++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..8f7117363 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,256 @@ + +# Instrucciones para Copilot – Revisión de código Odoo (v18.0) + +## Contexto + +* El repositorio contiene **módulos Odoo** compatibles con la versión **v18.0** (o versiones compatibles cercanas). +* El objetivo es **revisar cambios de código** y **sugerir mejoras seguras y relevantes**, sin hacer revisiones excesivamente estrictas. + +--- + +## Reglas generales + +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.). + +--- + +## Revisión de modelos (`models/*.py`) + +* 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. + * Las constraints declaradas con `_sql_constraints` o `@api.constrains` mantengan la integridad esperada. + * NOTA: En v19 se definen con `models.Constraint` + * Los índices tradicionalmente se definían en `_sql_constraints = [('unique_name', 'UNIQUE(name)', 'mensaje')]`. + * NOTA: En v19 usar `models.Index("campo")` para índices normales y `models.UniqueIndex("campo", "mensaje")` para únicos. +* 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` u otras formas más eficientes. + +--- + +## 🧾 Revisión del manifest (`__manifest__.py`) + +* 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. +* **Regla de versión (obligatoria):** + Siempre que el diff incluya **modificaciones en**: + + * definición de campos o modelos (`models/*.py`), + * vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`), + * seguridad (`security/*.csv`, `security/*.xml`), + **y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`). + * Cambios funcionales mínimos → **patch** (`x.y.Z`). + * Cambios de esquema o de compatibilidad → **minor** (`x.Y.0`). + * Cambios disruptivos (breaking changes) → **major** (`X.0.0`). + +--- + +## Revisión de vistas XML (`views/*.xml`) + +* Confirmar que uses herencias (`inherit_id`, `xpath`) efectivamente, no redefiniciones completas innecesarias. +* Validar que los campos referenciados en la vista existan en los modelos correspondientes. +* Atento a cambios en versiones nuevas de Odoo: + + * En Odoo 18, el elemento `` fue reemplazado por `` en vistas de tipo lista. + * Odoo 18 simplificó atributos condicionales: `attrs`/`states` pueden reemplazarse por condiciones directas (`invisible="..."`, `readonly="..."`) cuando aplique. +* Sugerir no duplicar vistas ni redefinir todo el `arch` si puede hacerse con `xpath`. + +--- + +## Seguridad y acceso + +* 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 agregan nuevos modelos o campos de control de acceso, **recordar el bump de versión** (ver sección de manifest). + +--- + +## Detección de cambios estructurales (esquema / datos) + +Cuando el diff sugiera **cambios de estructura de datos**, **siempre proponer** un **script de migración** en la carpeta `scripts/`, usando pre/post/end según corresponda (ver mapeo más abajo) **y recordar el bump de versión**. +Ejemplos de cambios estructurales: + +* Renombrar campos o modelos. +* Cambiar tipos de campo (e.g. `Char → Many2one`, `Selection → Many2one`, `Float → Monetary`). +* Quitar campos para reestructurar información en otros (split/merge). +* Agregar campos `compute` **almacenados** (`store=True`) que requieren backfill. +* Cambiar dominios/valores de `selection` (añadir/eliminar/renombrar keys). +* Añadir `required=True` a campos existentes sin default en datos históricos. +* Cambiar o añadir `_sql_constraints` (unique/index) que puedan fallar con datos existentes. +* Cambios en `ir.model.data`/XML IDs (renombres, `no_update="1"`, cambios de `module`/`name`). +* Cambios de reglas de acceso que requieran recalcular propiedad/propagación. + +--- + +## Scripts de migración en `scripts/`: pre / post / end + +> **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 + +* **Rename de campo (mismo modelo)** + + * **Pre-script**: copiar datos del campo viejo al nuevo (o crear alias temporal) para no perder datos tras el upgrade. + * **Post-script**: limpieza de residuos, recomputes si aplica. + +* **Eliminar campo y mover datos a otros campos (split/merge)** + + * **Pre-script**: crear campos destino (si es viable vía SQL/DDL) y migrar datos intermedios. + * **Post-script**: normalizar referencias, recalcular computes, borrar helpers. + +* **Cambios en registros XML con `no_update="1"`** + + * **Post-script**: usar **force upgrade** (reaplicar datos) o actualizar esos registros por API (respetando `xml_id`) para reflejar cambios. + +* **Agregar campo `compute` con `store=True`** + + * **Pre-script (opcional si alto volumen/incidencia)**: crear columna en DB para evitar lock prolongado en upgrade. + * **Post-script**: backfill **en lotes** (batch) para poblar el valor almacenado. + +* **Cambiar tipo de campo** + + * **Pre-script**: crear columna temporal con tipo nuevo y migrar datos (con conversión). + * **Post-script**: swap/renombrar columnas, borrar columna vieja, recomputes. + +* **Cambios en `selection` (renombre/elim./nuevo valor default)** + + * **Pre-script**: mapear valores antiguos → nuevos (tabla de mapeo). + * **Post-script**: validar que no quedan valores huérfanos. + +* **Agregar `required=True` a campo existente** + + * **Pre-script**: asignar default consistente a registros históricos (en lote) o rellenar desde lógica derivada. + * **Post-script**: constraint check. + +* **Nuevas `_sql_constraints` (unique) / índices** + + * **Pre-script**: detectar y resolver duplicados o inconsistencias. + * **Post-script**: crear índice/constraint y verificar. + +* **Renombrar modelo** + + * **Pre-script**: crear `ir.model.data`/mapeos, migrar `model` en `ir.model.data` y tablas rel. + * **Post-script**: re-enlazar vistas, acciones, reglas y volver a chequear accesos. + +* **Cambios en XML IDs o modularización** + + * **Pre-script**: preparar mapeo `old_xmlid → new_xmlid`. + * **Post-script**: actualizar referencias dependientes; si está marcado `no_update`, aplicar actualización manual. + +> **Regla general:** si el cambio puede **romper durante el upgrade**, prepara **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**. + +--- + +## Convenciones de scripts en `scripts/` + +* Ubicación: `scripts/` +* 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 +# scripts/pre_18.0_rename_partner_ref.py +from odoo import api, SUPERUSER_ID + +def migrate(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + # Ejemplo: copiar datos de 'old_ref' a 'new_ref' antes del upgrade + 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 +# scripts/post_18.0_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 + +| 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 ``) | +| Manifest | **Bump de versión obligatorio** si hay cambios en modelos/vistas/seguridad/datos; archivos referenciados | +| Seguridad | Accesos mínimos necesarios; reglas revisadas | +| Migraciones | **Si hay cambios estructurales, exigir script en `scripts/` (pre/post/end)** y describir qué hace | +| Rendimiento / ORM | Evitar loops costosos; no SQL innecesario; aprovechar mejoras de v18.0 | +| Ortografía & typos | Errores evidentes corregibles sin modificar idioma ni estilo | + +--- + +## Heurística práctica para el bump de versión + +* **SI** el diff toca cualquiera de: `models/`, `views/`, `data/`, `report/`, `security/`, `wizards/` + **Y** `__manifest__.py` no cambia `version` → **Sugerir bump**. +* **SI** hay scripts `scripts/pre_*.py` o `scripts/post_*.py` nuevos → **Sugerir al menos minor bump**. +* **SI** hay cambios que rompen compatibilidad (renombres, tipos, required sin default) → **Sugerir minor/major** según impacto. + +--- + +## Casos adicionales a cubrir (sugiere migración si aplica) + +* Introducción de **nuevos defaults** que dependen de datos existentes. +* Cambio en **nombres técnicos** de vistas/acciones/menús (asegurar que `xml_id` no cambie o mapearlo). +* **Indexaciones** nuevas (agregar índices en post para minimizar locks; validar cardinalidad). +* Normalización de **monedas/impuestos** (migrar valores legacy; recalcular montos). +* Cambios en **multi-company** o **multi-website** (poblar valores por compañía/sitio). +* Ajustes en **traducciones** críticas de `selection` (asegurar mapping por key, no por etiqueta traducida). + +--- + +## Estilo del feedback + +* 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()`.” + * “En v18.0, `` ya no se usa; reemplazar por ``.” + * “Tip: hay un error ortográfico en el nombre del parámetro.” + * **Bump + migración:** “Se renombra `old_ref` → `new_ref`: falta **bump de versión** y **pre-script** en `scripts/` 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. + +--- + +## Resumen operativo para Copilot + +1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.** +2. **Si hay cambio estructural → propone y describe script(s) de migración en `scripts/` (pre/post/inline),** con enfoque idempotente y en lotes. +3. Mantén el feedback **concreto, breve y accionable**. From 02e256fc4fb104fe89eb89ad5730467879b039fd Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:54:27 -0300 Subject: [PATCH 03/24] [UPD] Copilot instructions From 8017db343221f07cbb226d869693232b98845132 Mon Sep 17 00:00:00 2001 From: Lara Date: Thu, 23 Oct 2025 09:42:52 -0300 Subject: [PATCH 04/24] [FIX] prodcut_catalog_tree: add context validation in product catalog compute methods closes ingadhoc/product#792 Signed-off-by: matiasperalta1 --- product_catalog_tree/models/product_product.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index e0db1c95b..426087ebc 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -35,6 +35,10 @@ def write(self, vals): def _compute_catalog_values(self): res_model = self._context.get("product_catalog_order_model") order_id = self._context.get("order_id") + + if not res_model or not order_id: + return + order = self.env[res_model].browse(order_id) order_line_info = order.with_company(order.company_id)._get_product_catalog_order_line_info( product_ids=self.ids @@ -46,6 +50,10 @@ def _compute_catalog_values(self): def _inverse_catalog_values(self, product_catalog_qty): res_model = self._context.get("product_catalog_order_model") order_id = self._context.get("order_id") + + if not res_model or not order_id: + return + order = self.env[res_model].browse(order_id) for rec in self: order.with_company(order.company_id)._update_order_line_info(rec.id, product_catalog_qty) From e5ec0b4d80fcfc8140e3544efd6f34e9bc82cb8d Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Fri, 24 Oct 2025 14:41:07 -0300 Subject: [PATCH 05/24] [UPD] Copilot instructions --- .github/copilot-instructions.md | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8f7117363..9aebbbb94 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -43,13 +43,11 @@ * **Regla de versión (obligatoria):** Siempre que el diff incluya **modificaciones en**: - * definición de campos o modelos (`models/*.py`), - * vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`), + * definición de campos o modelos (`models/*.py`, `wizards/*.py`), + * vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`, `wizards/*.xml`), * seguridad (`security/*.csv`, `security/*.xml`), **y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`). - * Cambios funcionales mínimos → **patch** (`x.y.Z`). - * Cambios de esquema o de compatibilidad → **minor** (`x.Y.0`). - * Cambios disruptivos (breaking changes) → **major** (`X.0.0`). + * Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados. --- @@ -75,11 +73,12 @@ ## Detección de cambios estructurales (esquema / datos) -Cuando el diff sugiera **cambios de estructura de datos**, **siempre proponer** un **script de migración** en la carpeta `scripts/`, usando pre/post/end según corresponda (ver mapeo más abajo) **y recordar el bump de versión**. +Cuando el diff sugiera **cambios de estructura de datos**, **siempre proponer** un **script de migración** en la carpeta `migrations/`, usando pre/post/end según corresponda (ver mapeo más abajo) **y recordar el bump de versión**. Ejemplos de cambios estructurales: +* Carpeta dentro de `migrations/` debe ser la versión correspondiente en el manifest (e.g. `migrations/18.0.5.0/`). * Renombrar campos o modelos. -* Cambiar tipos de campo (e.g. `Char → Many2one`, `Selection → Many2one`, `Float → Monetary`). +* Cambiar tipos de campo (e.g. `Char → Many2one`, `Selection → Many2one`, etc.). * Quitar campos para reestructurar información en otros (split/merge). * Agregar campos `compute` **almacenados** (`store=True`) que requieren backfill. * Cambiar dominios/valores de `selection` (añadir/eliminar/renombrar keys). @@ -153,13 +152,13 @@ Ejemplos de cambios estructurales: --- -## Convenciones de scripts en `scripts/` +## Convenciones de scripts en `migrations/` -* Ubicación: `scripts/` +* Ubicación: `migrations/` * Nombres sugeridos: - * `pre__.py` - * `post__.py` + * `pre_.py` + * `post_.py` * Requisitos: * Idempotentes (seguros si se ejecutan más de una vez). @@ -171,7 +170,7 @@ Ejemplos de cambios estructurales: **Esqueleto mínimo (ejemplo):** ```python -# scripts/pre_18.0_rename_partner_ref.py +# migrations/18.0.4.0/pre_rename_partner_ref.py from odoo import api, SUPERUSER_ID def migrate(cr, registry): @@ -186,7 +185,7 @@ def migrate(cr, registry): ``` ```python -# scripts/post_18.0_backfill_stored_amount_total.py +# migrations/18.0.4.0/post_backfill_stored_amount_total.py from odoo import api, SUPERUSER_ID def migrate(cr, registry): @@ -209,7 +208,7 @@ def migrate(cr, registry): | Vistas XML | Herencias correctas; campos válidos; adaptación a cambios de versión (p.ej. `` vs ``) | | Manifest | **Bump de versión obligatorio** si hay cambios en modelos/vistas/seguridad/datos; archivos referenciados | | Seguridad | Accesos mínimos necesarios; reglas revisadas | -| Migraciones | **Si hay cambios estructurales, exigir script en `scripts/` (pre/post/end)** y describir qué hace | +| Migraciones | **Si hay cambios estructurales, exigir script en `migrations/` (pre/post/end)** y describir qué hace | | Rendimiento / ORM | Evitar loops costosos; no SQL innecesario; aprovechar mejoras de v18.0 | | Ortografía & typos | Errores evidentes corregibles sin modificar idioma ni estilo | @@ -219,7 +218,7 @@ def migrate(cr, registry): * **SI** el diff toca cualquiera de: `models/`, `views/`, `data/`, `report/`, `security/`, `wizards/` **Y** `__manifest__.py` no cambia `version` → **Sugerir bump**. -* **SI** hay scripts `scripts/pre_*.py` o `scripts/post_*.py` nuevos → **Sugerir al menos minor bump**. +* **SI** hay scripts `migrations/pre_*.py` o `migrations/post_*.py` nuevos → **Sugerir al menos minor bump**. * **SI** hay cambios que rompen compatibilidad (renombres, tipos, required sin default) → **Sugerir minor/major** según impacto. --- @@ -241,9 +240,8 @@ def migrate(cr, registry): * “El campo `partner_id` no se encuentra referenciado en la vista.” * “Este método redefine `write()` sin usar `super()`.” - * “En v18.0, `` ya no se usa; reemplazar por ``.” * “Tip: hay un error ortográfico en el nombre del parámetro.” - * **Bump + migración:** “Se renombra `old_ref` → `new_ref`: falta **bump de versión** y **pre-script** en `scripts/` para copiar valores antes del upgrade; añadir **post-script** para recompute del stored.” + * **Bump + migración:** “Se renombra `old_ref` → `new_ref`: falta **bump de versión** y **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. @@ -252,5 +250,5 @@ def migrate(cr, registry): ## Resumen operativo para Copilot 1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.** -2. **Si hay cambio estructural → propone y describe script(s) de migración en `scripts/` (pre/post/inline),** con enfoque idempotente y en lotes. +2. **Si hay cambio estructural → propone y describe script(s) de migración en `migrations/` (pre/post/end),** con enfoque idempotente y en lotes. 3. Mantén el feedback **concreto, breve y accionable**. From 0f7fbb6eb993c95206c4832b426a7aebdee1040f Mon Sep 17 00:00:00 2001 From: Matias Peralta Date: Mon, 27 Oct 2025 14:29:48 -0300 Subject: [PATCH 06/24] [FIX] product_catalog_tree: filters fixed closes ingadhoc/product#797 Signed-off-by: lav-adhoc --- product_catalog_tree/__manifest__.py | 2 +- product_catalog_tree/models/product_catalog_mixin.py | 2 ++ product_catalog_tree/views/product_product_views.xml | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/product_catalog_tree/__manifest__.py b/product_catalog_tree/__manifest__.py index acd7b5611..4b8dcf930 100644 --- a/product_catalog_tree/__manifest__.py +++ b/product_catalog_tree/__manifest__.py @@ -19,7 +19,7 @@ ############################################################################## { "name": "Product Catalog Tree", - "version": "18.0.1.2.0", + "version": "18.0.1.3.0", "category": "Products", "sequence": 14, "summary": "", diff --git a/product_catalog_tree/models/product_catalog_mixin.py b/product_catalog_tree/models/product_catalog_mixin.py index ee908952f..9b5bad48a 100644 --- a/product_catalog_tree/models/product_catalog_mixin.py +++ b/product_catalog_tree/models/product_catalog_mixin.py @@ -8,5 +8,7 @@ class ProductCatalogMixin(models.AbstractModel): def action_add_from_catalog(self): action = super().action_add_from_catalog() tree_view_id = self.env.ref("product_catalog_tree.product_view_tree_catalog").id + search_view_id = self.env.ref("product.product_search_form_view").id action["views"] = [(tree_view_id, "list")] + action["views"] + action["search_view_id"] = (search_view_id, "search") return action diff --git a/product_catalog_tree/views/product_product_views.xml b/product_catalog_tree/views/product_product_views.xml index 6ed8b1036..bdb777ab5 100644 --- a/product_catalog_tree/views/product_product_views.xml +++ b/product_catalog_tree/views/product_product_views.xml @@ -24,4 +24,16 @@ + + product.product.search.form.supplier + product.product + + + + + + + + From 093f762c14a01251f34c020a35ffcfcf27c46f7e Mon Sep 17 00:00:00 2001 From: Juan Ignacio Carreras Date: Fri, 24 Oct 2025 13:18:05 +0000 Subject: [PATCH 07/24] [IMP]product_catalog_tree:recompute line in PO closes ingadhoc/product#794 Signed-off-by: ced-adhoc --- product_catalog_tree/models/product_product.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index 426087ebc..c326b0fd7 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -56,8 +56,18 @@ def _inverse_catalog_values(self, product_catalog_qty): order = self.env[res_model].browse(order_id) for rec in self: + # Actualizar la información de la línea de orden order.with_company(order.company_id)._update_order_line_info(rec.id, product_catalog_qty) + # Si la cantidad es mayor a 0, recalcular precios y descuentos + if product_catalog_qty > 0: + order_lines = order.order_line.filtered(lambda line: line.product_id.id == rec.id) + if order_lines: + # Tomamos la última línea en caso de que haya varias + order_line = order_lines[-1] + # Ejecutar el método que recalcula linea + order_line._compute_price_unit_and_date_planned_and_name() + def increase_quantity(self): for rec in self: rec._inverse_catalog_values(rec.product_catalog_qty + 1) From 7dd5b63f676ca5cefe39010ed061a86be16b6ba6 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Carreras Date: Thu, 6 Nov 2025 18:31:54 +0000 Subject: [PATCH 08/24] [FIX]product_catalog_tree:Revert of commit closes ingadhoc/product#812 Signed-off-by: Franco Leyes --- product_catalog_tree/models/product_product.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index c326b0fd7..30dd0cf30 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -59,15 +59,6 @@ def _inverse_catalog_values(self, product_catalog_qty): # Actualizar la información de la línea de orden order.with_company(order.company_id)._update_order_line_info(rec.id, product_catalog_qty) - # Si la cantidad es mayor a 0, recalcular precios y descuentos - if product_catalog_qty > 0: - order_lines = order.order_line.filtered(lambda line: line.product_id.id == rec.id) - if order_lines: - # Tomamos la última línea en caso de que haya varias - order_line = order_lines[-1] - # Ejecutar el método que recalcula linea - order_line._compute_price_unit_and_date_planned_and_name() - def increase_quantity(self): for rec in self: rec._inverse_catalog_values(rec.product_catalog_qty + 1) From b40ee646c5e70851e924fd1b7fb20315d5a55edb Mon Sep 17 00:00:00 2001 From: Lara Date: Wed, 12 Nov 2025 11:46:49 -0300 Subject: [PATCH 09/24] [FIX] product_catalog_tree: fix in product catalog fields closes ingadhoc/product#815 Signed-off-by: matiasperalta1 --- product_catalog_tree/models/product_product.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index 30dd0cf30..8521eb699 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -37,6 +37,8 @@ def _compute_catalog_values(self): order_id = self._context.get("order_id") if not res_model or not order_id: + self.product_catalog_qty = 0 + self.product_catalog_price = 0 return order = self.env[res_model].browse(order_id) From 55d48fc7af7c552dab9ab0741b51121a55f96dc6 Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:42:44 -0300 Subject: [PATCH 10/24] [UPD] Copilot instructions --- .github/copilot-instructions.md | 252 ++++++++++++++++++++------------ 1 file changed, 159 insertions(+), 93 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9aebbbb94..83d1263a6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,14 +1,13 @@ - # Instrucciones para Copilot – Revisión de código Odoo (v18.0) ## Contexto -* El repositorio contiene **módulos Odoo** compatibles con la versión **v18.0** (o versiones compatibles cercanas). -* El objetivo es **revisar cambios de código** y **sugerir mejoras seguras y relevantes**, sin hacer revisiones excesivamente estrictas. +* 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. --- -## Reglas generales +## Reglas generales (aplican a todo el código) 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). @@ -20,23 +19,20 @@ --- -## Revisión de modelos (`models/*.py`) +## 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. - * Las constraints declaradas con `_sql_constraints` o `@api.constrains` mantengan la integridad esperada. - * NOTA: En v19 se definen con `models.Constraint` - * Los índices tradicionalmente se definían en `_sql_constraints = [('unique_name', 'UNIQUE(name)', 'mensaje')]`. - * NOTA: En v19 usar `models.Index("campo")` para índices normales y `models.UniqueIndex("campo", "mensaje")` para únicos. + * 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` u otras formas más eficientes. +* 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`) +## 🧾 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. @@ -46,50 +42,131 @@ * definición de campos o modelos (`models/*.py`, `wizards/*.py`), * vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`, `wizards/*.xml`), * seguridad (`security/*.csv`, `security/*.xml`), - **y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`). - * Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados. + + **y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`). +* Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados. --- -## Revisión de vistas XML (`views/*.xml`) +## 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. -* Confirmar que uses herencias (`inherit_id`, `xpath`) efectivamente, no redefiniciones completas innecesarias. -* Validar que los campos referenciados en la vista existan en los modelos correspondientes. -* Atento a cambios en versiones nuevas de Odoo: +### Notas específicas Odoo 18 (vistas / UI) - * En Odoo 18, el elemento `` fue reemplazado por `` en vistas de tipo lista. - * Odoo 18 simplificó atributos condicionales: `attrs`/`states` pueden reemplazarse por condiciones directas (`invisible="..."`, `readonly="..."`) cuando aplique. -* Sugerir no duplicar vistas ni redefinir todo el `arch` si puede hacerse con `xpath`. +* 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 +## 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 agregan nuevos modelos o campos de control de acceso, **recordar el bump de versión** (ver sección de manifest). +* 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. --- -## Detección de cambios estructurales (esquema / datos) +## 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) **y recordar el bump de versión**. + +### 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)** -Cuando el diff sugiera **cambios de estructura de datos**, **siempre proponer** un **script de migración** en la carpeta `migrations/`, usando pre/post/end según corresponda (ver mapeo más abajo) **y recordar el bump de versión**. -Ejemplos de cambios estructurales: + * 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). -* Carpeta dentro de `migrations/` debe ser la versión correspondiente en el manifest (e.g. `migrations/18.0.5.0/`). -* Renombrar campos o modelos. -* Cambiar tipos de campo (e.g. `Char → Many2one`, `Selection → Many2one`, etc.). -* Quitar campos para reestructurar información en otros (split/merge). -* Agregar campos `compute` **almacenados** (`store=True`) que requieren backfill. -* Cambiar dominios/valores de `selection` (añadir/eliminar/renombrar keys). -* Añadir `required=True` a campos existentes sin default en datos históricos. -* Cambiar o añadir `_sql_constraints` (unique/index) que puedan fallar con datos existentes. -* Cambios en `ir.model.data`/XML IDs (renombres, `no_update="1"`, cambios de `module`/`name`). -* Cambios de reglas de acceso que requieran recalcular propiedad/propagación. +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:** hemos eliminado explícitamente de 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 `scripts/`: pre / post / end +## Scripts de migración en `migrations/`: pre / post / end (reglas generales) > **Objetivo:** preservar datos y mantener instalabilidad/actualizabilidad segura. @@ -97,64 +174,59 @@ Ejemplos de cambios estructurales: - **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 +### Mapeo de cambio → acción recomendada (actualizado) -* **Rename de campo (mismo modelo)** +* **Rename de campo almacenado (mismo modelo)** - * **Pre-script**: copiar datos del campo viejo al nuevo (o crear alias temporal) para no perder datos tras el upgrade. - * **Post-script**: limpieza de residuos, recomputes si aplica. + * **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. -* **Eliminar campo y mover datos a otros campos (split/merge)** +* **Renombrar modelo** - * **Pre-script**: crear campos destino (si es viable vía SQL/DDL) y migrar datos intermedios. - * **Post-script**: normalizar referencias, recalcular computes, borrar helpers. + * **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. -* **Cambios en registros XML con `no_update="1"`** +* **Eliminar campo y mover datos a otros campos (split/merge)** - * **Post-script**: usar **force upgrade** (reaplicar datos) o actualizar esos registros por API (respetando `xml_id`) para reflejar cambios. + * **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 si alto volumen/incidencia)**: crear columna en DB para evitar lock prolongado en upgrade. - * **Post-script**: backfill **en lotes** (batch) para poblar el valor almacenado. - -* **Cambiar tipo de campo** + * **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. - * **Pre-script**: crear columna temporal con tipo nuevo y migrar datos (con conversión). - * **Post-script**: swap/renombrar columnas, borrar columna vieja, recomputes. +* **Cambiar tipo de campo con cambio real de representación** -* **Cambios en `selection` (renombre/elim./nuevo valor default)** + * **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. - * **Pre-script**: mapear valores antiguos → nuevos (tabla de mapeo). - * **Post-script**: validar que no quedan valores huérfanos. +* **Cambios en `selection` (eliminar/renombrar keys existentes)** -* **Agregar `required=True` a campo existente** + * **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. - * **Pre-script**: asignar default consistente a registros históricos (en lote) o rellenar desde lógica derivada. - * **Post-script**: constraint check. +* **Nuevas constraints `_sql_constraints` (unique) / índices** -* **Nuevas `_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. - * **Pre-script**: detectar y resolver duplicados o inconsistencias. - * **Post-script**: crear índice/constraint y verificar. - -* **Renombrar modelo** +* **Cambios en registros XML con `no_update="1"`** - * **Pre-script**: crear `ir.model.data`/mapeos, migrar `model` en `ir.model.data` y tablas rel. - * **Post-script**: re-enlazar vistas, acciones, reglas y volver a chequear accesos. + * **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 en XML IDs o modularización** +* **Cambios de reglas de acceso / multi-company / multi-website** - * **Pre-script**: preparar mapeo `old_xmlid → new_xmlid`. - * **Post-script**: actualizar referencias dependientes; si está marcado `no_update`, aplicar actualización manual. + * **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**, prepara **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**. +> **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**. --- -## Convenciones de scripts en `migrations/` +## Convenciones de scripts en `migrations/` (generales) -* Ubicación: `migrations/` +* Ubicación: `migrations//`. * Nombres sugeridos: * `pre_.py` @@ -170,12 +242,11 @@ Ejemplos de cambios estructurales: **Esqueleto mínimo (ejemplo):** ```python -# migrations/18.0.4.0/pre_rename_partner_ref.py +# migrations//pre_rename_partner_ref.py from odoo import api, SUPERUSER_ID def migrate(cr, registry): env = api.Environment(cr, SUPERUSER_ID, {}) - # Ejemplo: copiar datos de 'old_ref' a 'new_ref' antes del upgrade 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] @@ -185,7 +256,7 @@ def migrate(cr, registry): ``` ```python -# migrations/18.0.4.0/post_backfill_stored_amount_total.py +# migrations//post_backfill_stored_amount_total.py from odoo import api, SUPERUSER_ID def migrate(cr, registry): @@ -200,7 +271,7 @@ def migrate(cr, registry): --- -## Checklist rápida para el review +## Checklist rápida para el review (general) | Categoría | Qué comprobar Copilot | | ------------------ | -------------------------------------------------------------------------------------------------------- | @@ -208,33 +279,22 @@ def migrate(cr, registry): | Vistas XML | Herencias correctas; campos válidos; adaptación a cambios de versión (p.ej. `` vs ``) | | Manifest | **Bump de versión obligatorio** si hay cambios en modelos/vistas/seguridad/datos; archivos referenciados | | Seguridad | Accesos mínimos necesarios; reglas revisadas | -| Migraciones | **Si hay cambios estructurales, exigir script en `migrations/` (pre/post/end)** y describir qué hace | -| Rendimiento / ORM | Evitar loops costosos; no SQL innecesario; aprovechar mejoras de v18.0 | +| 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 | --- -## Heurística práctica para el bump de versión +## Heurística práctica para el bump de versión (general) * **SI** el diff toca cualquiera de: `models/`, `views/`, `data/`, `report/`, `security/`, `wizards/` **Y** `__manifest__.py` no cambia `version` → **Sugerir bump**. * **SI** hay scripts `migrations/pre_*.py` o `migrations/post_*.py` nuevos → **Sugerir al menos minor bump**. -* **SI** hay cambios que rompen compatibilidad (renombres, tipos, required sin default) → **Sugerir minor/major** según impacto. +* **SI** hay cambios que rompen compatibilidad (renombres, cambios de tipo con impacto, limpieza masiva de datos) → **Sugerir minor/major** según impacto. --- -## Casos adicionales a cubrir (sugiere migración si aplica) - -* Introducción de **nuevos defaults** que dependen de datos existentes. -* Cambio en **nombres técnicos** de vistas/acciones/menús (asegurar que `xml_id` no cambie o mapearlo). -* **Indexaciones** nuevas (agregar índices en post para minimizar locks; validar cardinalidad). -* Normalización de **monedas/impuestos** (migrar valores legacy; recalcular montos). -* Cambios en **multi-company** o **multi-website** (poblar valores por compañía/sitio). -* Ajustes en **traducciones** críticas de `selection` (asegurar mapping por key, no por etiqueta traducida). - ---- - -## Estilo del feedback +## Estilo del feedback (general) * Ser breve, claro y útil. Ejemplos: @@ -247,8 +307,14 @@ def migrate(cr, registry): --- -## Resumen operativo para Copilot +## Resumen operativo para Copilot (v18) 1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.** -2. **Si hay cambio estructural → propone y describe script(s) de migración en `migrations/` (pre/post/end),** con enfoque idempotente y en lotes. -3. Mantén el feedback **concreto, breve y accionable**. +2. **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. +3. 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). +4. Mantén el feedback **concreto, breve y accionable**. + +[^odoo18]: Resumen basado en la documentación oficial de Odoo 18 Release Notes y artículos técnicos que analizan sus mejoras de rendimiento y UX. \ No newline at end of file From 923ade281f50c58357734d41ff6e377ea9bcd711 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Carreras Date: Tue, 18 Nov 2025 18:48:22 +0000 Subject: [PATCH 11/24] [IMP]product_replenishment_cost:no more force_company closes ingadhoc/product#819 Signed-off-by: Filoquin adhoc --- .../models/product_template.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/product_replenishment_cost/models/product_template.py b/product_replenishment_cost/models/product_template.py index 6b130b34e..7a2c7c440 100644 --- a/product_replenishment_cost/models/product_template.py +++ b/product_replenishment_cost/models/product_template.py @@ -5,7 +5,6 @@ import logging from odoo import _, api, fields, models -from odoo.exceptions import ValidationError from odoo.tools import float_compare _logger = logging.getLogger(__name__) @@ -44,7 +43,7 @@ class ProductTemplate(models.Model): replenishment_base_cost = fields.Float( digits="Product Price", tracking=True, - help="Replanishment Cost expressed in 'Replenishment Base Cost " "Currency'.", + help="Replanishment Cost expressed in 'Replenishment Base Cost Currency'.", ) replenishment_base_cost_currency_id = fields.Many2one( "res.currency", @@ -111,12 +110,6 @@ def cron_update_cost_from_replenishment_cost(self, limit=None, company_ids=None, tampoco podemos usar get_param porque justamente no se va a refrescar el valor """ - # allow force_company for backward compatibility - force_company = self._context.get("force_company", False) - if force_company and company_ids: - raise ValidationError( - _("The argument 'company_ids' and the key 'force_company' on the context can't be used together") - ) # buscamos cual fue el ultimo registro actualziado parameter_name = "product_replenishment_cost.last_updated_record_id" last_updated_param = self.env["ir.config_parameter"].sudo().search([("key", "=", parameter_name)], limit=1) @@ -126,10 +119,8 @@ def cron_update_cost_from_replenishment_cost(self, limit=None, company_ids=None, domain = [("id", ">", int(last_updated_param.value))] records = self.with_context(prefetch_fields=False).search(domain, order="id asc") - # use company_ids or force_company or search for all companies - if force_company: - company_ids = [force_company] - elif not company_ids: + # use company_ids or search for all companies + if not company_ids: company_ids = self.env["res.company"].search([]).ids for company_id in company_ids: From b3ddff59e1934b26556b190aa3145a6ccf32f892 Mon Sep 17 00:00:00 2001 From: ADHOC - Bot Date: Fri, 21 Nov 2025 15:08:21 -0300 Subject: [PATCH 12/24] [I18N] Update translation terms from Transifex adhoc-odoo-18-0 --- product_replenishment_cost/i18n/es.po | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/product_replenishment_cost/i18n/es.po b/product_replenishment_cost/i18n/es.po index c50ba6194..3c570ae08 100644 --- a/product_replenishment_cost/i18n/es.po +++ b/product_replenishment_cost/i18n/es.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0+e\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-03 05:15+0000\n" +"POT-Creation-Date: 2025-11-21 10:29+0000\n" "PO-Revision-Date: 2024-11-08 12:54+0000\n" "Last-Translator: ced_adhoc, 2025\n" "Language-Team: Spanish (https://app.transifex.com/adhoc/teams/46451/es/)\n" @@ -287,11 +287,6 @@ msgstr "Productos sin regla de reposición" msgid "Purchase Order Line" msgstr "Línea de pedido de compra" -#. module: product_replenishment_cost -#: model:ir.model.fields,field_description:product_replenishment_cost.field_product_replenishment_cost_rule__rating_ids -msgid "Ratings" -msgstr "" - #. module: product_replenishment_cost #: model:ir.model.fields,help:product_replenishment_cost.field_product_product__replenishment_base_cost #: model:ir.model.fields,help:product_replenishment_cost.field_product_template__replenishment_base_cost @@ -434,16 +429,6 @@ msgstr "Lista de precios de proveedor" msgid "Supplierinfo" msgstr "Información del Proveedor" -#. module: product_replenishment_cost -#. odoo-python -#: code:addons/product_replenishment_cost/models/product_template.py:0 -msgid "" -"The argument 'company_ids' and the key 'force_company' on the context can't " -"be used together" -msgstr "" -"El argumento 'company_ids' y la clave 'force_company'  en el contexto no " -"pueden usarse juntos" - #. module: product_replenishment_cost #: model_terms:ir.ui.view,arch_db:product_replenishment_cost.view_update_from_replenishment_cost_wizard_form msgid "" From a1cfadbc7f8ad4c96cb3f9d3bf45a01bf9f6d581 Mon Sep 17 00:00:00 2001 From: Franco Leyes Date: Thu, 20 Nov 2025 15:29:27 +0000 Subject: [PATCH 13/24] [IMP] *: Add macOS specific files to ignore list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes ingadhoc/product#822 Related: ingadhoc/sale#1471 Related: ingadhoc/stock#809 Related: ingadhoc/odoo-academic#329 Related: ingadhoc/miscellaneous#303 Related: ingadhoc/purchase#281 Related: ingadhoc/partner#137 Signed-off-by: Andrés Zacchino --- .gitignore | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.gitignore b/.gitignore index a6d076d8a..59c990894 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,36 @@ coverage.xml # Sphinx documentation docs/_build/ + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud From 0f36826d7e220b49581d7bab4770359d6e6d86ec Mon Sep 17 00:00:00 2001 From: Celina Devigili Date: Wed, 19 Nov 2025 11:35:36 -0300 Subject: [PATCH 14/24] [FIX] product_replenishment_cost_mrp: adapt module to subcontracting closes ingadhoc/product#821 Signed-off-by: Juan Carreras --- product_replenishment_cost_mrp/README.rst | 5 ++- .../__manifest__.py | 6 ++- .../models/product_template.py | 36 +++++++++++++++- .../report/mrp_report_bom_structure.py | 41 ++++++++++++++++--- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/product_replenishment_cost_mrp/README.rst b/product_replenishment_cost_mrp/README.rst index 2e4d00c09..8137cd2d9 100644 --- a/product_replenishment_cost_mrp/README.rst +++ b/product_replenishment_cost_mrp/README.rst @@ -14,8 +14,9 @@ Integration between Replenishment Cost and Manufacture ====================================================== -#. Agregado de método para cálculo de costo de reposición utilizando el costo de reposición de la LdM. -#. Modifica el reporte de "Estructura de Lista de Materiales" para que utilice el costo de reposición. +#. Add a method to compute the replenishment cost using the BoM's replenishment cost. +#. Modify the "Bill of Materials Structure" report to use the replenishment cost +#. Support for replenishment cost calculation in subcontracted BOMs, adding the supplier/subcontractor cost to the components' cost Installation ============ diff --git a/product_replenishment_cost_mrp/__manifest__.py b/product_replenishment_cost_mrp/__manifest__.py index 83b8ee4a1..701f10d88 100644 --- a/product_replenishment_cost_mrp/__manifest__.py +++ b/product_replenishment_cost_mrp/__manifest__.py @@ -8,6 +8,10 @@ "product_replenishment_cost", "mrp", ], - "data": ["views/mrp_bom_views.xml", "views/product_template_views.xml", "report/mrp_report_bom_structure.xml"], + "data": [ + "views/mrp_bom_views.xml", + "views/product_template_views.xml", + "report/mrp_report_bom_structure.xml", + ], "installable": True, } diff --git a/product_replenishment_cost_mrp/models/product_template.py b/product_replenishment_cost_mrp/models/product_template.py index 324c46f85..5ffb29154 100644 --- a/product_replenishment_cost_mrp/models/product_template.py +++ b/product_replenishment_cost_mrp/models/product_template.py @@ -46,7 +46,11 @@ def _compute_replenishment_cost(self): rec.update({"replenishment_base_cost_on_currency": 0.0, "replenishment_cost": 0.0}) continue # el explode es para product.product, tomamos la primer variante - result, result2 = bom.explode(rec.with_context(active_test=rec.active).product_variant_ids[0], 1) + product_variant = rec.with_context(active_test=rec.active).product_variant_ids[:1] + if not product_variant: + rec.update({"replenishment_base_cost_on_currency": 0.0, "replenishment_cost": 0.0}) + continue + result, result2 = bom.explode(product_variant, 1) for sbom, sbom_data in result2: sbom_rep_cost = ( sbom.product_id.uom_id._compute_price( @@ -57,6 +61,36 @@ def _compute_replenishment_cost(self): price += sbom.product_id.product_tmpl_id.currency_id._convert( sbom_rep_cost, product_currency, company, date, round=False ) + + # Add subcontracting cost if bom type is 'subcontract' + if bom.type == "subcontract": + # Look for the seller/subcontractor set in the BOM + product = rec.product_variant_ids[:1] + if product: + seller = product._select_seller( + quantity=1, uom_id=bom.product_uom_id, params={"subcontractor_ids": bom.subcontractor_ids} + ) + else: + # If no product variant, look for sellers in the template + seller = rec.seller_ids.filtered(lambda s: s.partner_id in bom.subcontractor_ids)[:1] + + if seller: + if bom.product_uom_id.ratio == 0: + raise ValueError( + _( + "El ratio de la unidad de medida del producto '%s' en el BOM es cero. " + "Esto provocaría una división por cero. Verifique la configuración de la UoM." + ) + % bom.display_name + ) + # Calculate the subcontracting cost + ratio_uom_seller = seller.product_uom.ratio / bom.product_uom_id.ratio + subcontract_price = seller.currency_id._convert( + seller.price, product_currency, company, date, round=False + ) + # Add the subcontract price to the total price + price += subcontract_price / ratio_uom_seller + # NO implementamos total va a ser borrado. Ver si implementamos mas adelante (tener en cuenta convertir # moneda) # if bom.routing_id: diff --git a/product_replenishment_cost_mrp/report/mrp_report_bom_structure.py b/product_replenishment_cost_mrp/report/mrp_report_bom_structure.py index eb51168e5..d28532135 100644 --- a/product_replenishment_cost_mrp/report/mrp_report_bom_structure.py +++ b/product_replenishment_cost_mrp/report/mrp_report_bom_structure.py @@ -4,6 +4,31 @@ class ReportReplenishmentBomStructure(models.AbstractModel): _inherit = "report.mrp.report_bom_structure" + @api.model + def _get_subcontracting_line(self, bom, seller, level, bom_quantity): + """Override to convert seller price to the proper currency""" + res = super()._get_subcontracting_line(bom, seller, level, bom_quantity) + currency = self.env.context.get("force_currency") or self.env.company.currency_id + + # Convert the seller price to the proper currency + if bom.product_uom_id.ratio == 0: + raise ValueError( + "El ratio de la unidad de medida del producto en el BOM es cero. " + "Esto provocaría una división por cero. Verifique la configuración de la UoM." + ) + ratio_uom_seller = seller.product_uom.ratio / bom.product_uom_id.ratio + price = seller.currency_id._convert(seller.price, currency, self.env.company, fields.Date.today(), round=True) + res.update( + { + "prod_cost": price / ratio_uom_seller * bom_quantity, + "bom_cost": price / ratio_uom_seller * bom_quantity, + "currency": currency, + "currency_id": currency.id, + } + ) + + return res + @api.model def _get_bom_data( self, @@ -22,7 +47,7 @@ def _get_bom_data( ): """Here we use the replenishment cost for the uom unit""" if not self.env.context.get("force_currency"): - self = self.with_context(force_currency=product.currency_id) + self = self.with_context(force_currency=product.currency_id if product else bom.product_tmpl_id.currency_id) res = super(ReportReplenishmentBomStructure, self)._get_bom_data( bom, warehouse, @@ -43,6 +68,9 @@ def _get_bom_data( current_quantity = line_qty if bom_line: current_quantity = bom_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id) or 0 + + # Only update prod_cost (costo del producto final), not bom_cost + # bom_cost is automatically calculated as: components + operations + subcontracting if not is_minimized: if product: price = product.uom_id._compute_price(product.replenishment_cost, bom.product_uom_id) * current_quantity @@ -79,10 +107,13 @@ def _get_component_data( parent_bom, parent_product, warehouse, bom_line, line_quantity, level, index, product_info, ignore_stock ) currency = self.env.context.get("force_currency") or self.env.company.currency_id - price = ( - bom_line.product_id.uom_id._compute_price(bom_line.product_id.replenishment_cost, bom_line.product_uom_id) - * line_quantity - ) + + # Use replenishment_cost, if not defined use standard_price as fallback + component_cost = bom_line.product_id.replenishment_cost + if not component_cost: + component_cost = bom_line.product_id.standard_price + + price = bom_line.product_id.uom_id._compute_price(component_cost, bom_line.product_uom_id) * line_quantity price = bom_line.product_id.currency_id._convert( price, currency, self.env.company, fields.Date.today(), round=True ) From cddeac9272cc97cec41cdedfab8bfffd8b73841f Mon Sep 17 00:00:00 2001 From: Virginia Date: Wed, 17 Dec 2025 14:07:31 -0300 Subject: [PATCH 15/24] Update project.toml from template --- .copier-answers.yml | 2 +- pyproject.toml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 4c666a1bb..fff1dfe73 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: d46567f +_commit: 2f2f7c4 _src_path: https://github.com/ingadhoc/addons-repo-template.git description: '' is_private: false diff --git a/pyproject.toml b/pyproject.toml index 9f837a8cb..9b15bb049 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,9 +27,9 @@ ignore = [ [tool.ruff.lint.pycodestyle] # line-length is set in [tool.ruff], and it's used by the formatter # in case the formatted can't autofix the line length, it will be reported as an error -# only if it exceeds the max-line-length set here. We use 999 to effectively disable +# only if it exceeds the max-line-length set here. We use 320 (max available value) to disable # this check. -max-line-length = 999 +max-line-length = 320 [tool.ruff.lint.isort] combine-as-imports = true @@ -46,6 +46,7 @@ known-third-party = [ "urllib2", "yaml", ] +section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] [tool.ruff.lint.mccabe] max-complexity = 20 From 3a67ba9d878e98d990f76ebb599644d65ef8ad26 Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:28:02 -0300 Subject: [PATCH 16/24] [UPD] Copilot instructions --- .github/copilot-instructions.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 83d1263a6..c985deec2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -16,6 +16,7 @@ * 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. --- @@ -83,6 +84,8 @@ * 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. --- @@ -162,7 +165,7 @@ En estos casos **normalmente corresponde** proponer migración (salvo notas en c * asignar company/website por defecto, * o migrar datos entre reglas. -> **Nota:** hemos eliminado explícitamente de 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. +> **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. --- @@ -224,6 +227,14 @@ En estos casos **normalmente corresponde** proponer migración (salvo notas en c --- +## 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//`. @@ -304,10 +315,11 @@ def migrate(cr, registry): * **Bump + migración:** “Se renombra `old_ref` → `new_ref`: falta **bump de versión** y **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 (v18) +## Resumen operativo para Copilot 1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.** 2. **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. From f12fe068e09077589667cf4869864a846f50967d Mon Sep 17 00:00:00 2001 From: Camila Vives Date: Tue, 16 Dec 2025 15:29:21 +0000 Subject: [PATCH 17/24] [FIX] product_catalog_tree: clear context when updating order line info When using catalog tree from invoices a context with UI keys is passed and that produces errors when creating account move lines. This commit clears the context when calling the order method to avoid such issues. closes ingadhoc/product#837 Signed-off-by: rov-adhoc --- product_catalog_tree/models/product_product.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index 8521eb699..a8759dd4c 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -59,7 +59,10 @@ def _inverse_catalog_values(self, product_catalog_qty): order = self.env[res_model].browse(order_id) for rec in self: # Actualizar la información de la línea de orden - order.with_company(order.company_id)._update_order_line_info(rec.id, product_catalog_qty) + # Call the order method with a cleared context to avoid errors on creating move line + order.with_company(order.company_id).with_context(clear_context=True)._update_order_line_info( + rec.id, product_catalog_qty + ) def increase_quantity(self): for rec in self: From 3f07911d96165251622f800cfdf98892c9f0a73f Mon Sep 17 00:00:00 2001 From: Camila Vives Date: Fri, 19 Dec 2025 19:15:24 +0000 Subject: [PATCH 18/24] [FIX] prodct_catalog_tree: clear all context before update order --- product_catalog_tree/models/product_product.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index a8759dd4c..fc2a47ef6 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -60,9 +60,7 @@ def _inverse_catalog_values(self, product_catalog_qty): for rec in self: # Actualizar la información de la línea de orden # Call the order method with a cleared context to avoid errors on creating move line - order.with_company(order.company_id).with_context(clear_context=True)._update_order_line_info( - rec.id, product_catalog_qty - ) + order.with_company(order.company_id).with_context(**{})._update_order_line_info(rec.id, product_catalog_qty) def increase_quantity(self): for rec in self: From bb23eba14e3d80711640ff6cfc05bc237dd71212 Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Thu, 16 Oct 2025 15:13:21 -0300 Subject: [PATCH 19/24] [IMP] backport from https://github.com/odoo/odoo/commit/41d8fa352a7baec887210ea7870b475a10f2f39d closes ingadhoc/product#789 Signed-off-by: Nicolas Mac Rouillon (ADV) --- product_ux/__manifest__.py | 2 +- product_ux/models/product_product.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/product_ux/__manifest__.py b/product_ux/__manifest__.py index b9622cdcd..84bd8ba59 100644 --- a/product_ux/__manifest__.py +++ b/product_ux/__manifest__.py @@ -19,7 +19,7 @@ ############################################################################## { "name": "Product UX", - "version": "18.0.1.1.0", + "version": "18.0.1.2.0", "category": "Products", "sequence": 14, "summary": "", diff --git a/product_ux/models/product_product.py b/product_ux/models/product_product.py index ca78b7c5b..f26b32fcd 100644 --- a/product_ux/models/product_product.py +++ b/product_ux/models/product_product.py @@ -3,13 +3,26 @@ # directory ############################################################################## from odoo import api, fields, models +from odoo.tools import create_index class ProductProduct(models.Model): _inherit = "product.product" + _order = "default_code, name, id" + + def init(self): + super().init() + create_index( + self.env.cr, + indexname="is_favorite_idx", + tablename="product_product", + expressions=["is_favorite"], + where="is_favorite IS TRUE", + ) active = fields.Boolean(tracking=True) pricelist_price = fields.Float(compute="_compute_product_pricelist_price", digits="Product Price") + is_favorite = fields.Boolean(related="product_tmpl_id.is_favorite", readonly=True, store=True) @api.depends_context("pricelist", "quantity", "uom", "date", "no_variant_attributes_price_extra") def _compute_product_pricelist_price(self): From 97a66c3c1815fd341e5595479b0566b55867e77d Mon Sep 17 00:00:00 2001 From: Franco Leyes Date: Wed, 24 Dec 2025 20:05:31 +0000 Subject: [PATCH 20/24] [FIX] product_catalog_tree: add warehouse location filter for sale orders closes ingadhoc/product#843 Signed-off-by: matiasperalta1 --- product_catalog_tree/models/product_catalog_mixin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/product_catalog_tree/models/product_catalog_mixin.py b/product_catalog_tree/models/product_catalog_mixin.py index 9b5bad48a..2277539c5 100644 --- a/product_catalog_tree/models/product_catalog_mixin.py +++ b/product_catalog_tree/models/product_catalog_mixin.py @@ -11,4 +11,9 @@ def action_add_from_catalog(self): search_view_id = self.env.ref("product.product_search_form_view").id action["views"] = [(tree_view_id, "list")] + action["views"] action["search_view_id"] = (search_view_id, "search") + # add warehouse location filter if order has warehouse + context = action.get("context", {}) + if "order_id" in context and self._name == "sale.order": + if self._fields.get("warehouse_id") and self.warehouse_id: + action["context"]["search_default_location_id"] = self.warehouse_id.lot_stock_id.id return action From c85dc7fd7f93c47c03c055de81bbadee332c97dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Scarafia?= Date: Mon, 15 Dec 2025 20:49:39 +0000 Subject: [PATCH 21/24] [FIX] _cost_sale_margin:: do not recompute on rep cost change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit el depnds este era terrible a nivel performance (traslada todos los depends de rep cost) y además funcionalmente no es deseado, cambios en costo (por cualquier motivo), no deberían recomputar los costos existentes closes ingadhoc/product#847 X-original-commit: a2442ca3002988bfbe46d6916d9abfffb190bb43 Signed-off-by: Juan José Scarafía --- .../models/sale_order_line.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/product_replenishment_cost_sale_margin/models/sale_order_line.py b/product_replenishment_cost_sale_margin/models/sale_order_line.py index f49f6c146..642dc2425 100644 --- a/product_replenishment_cost_sale_margin/models/sale_order_line.py +++ b/product_replenishment_cost_sale_margin/models/sale_order_line.py @@ -1,10 +1,9 @@ -from odoo import api, fields, models +from odoo import fields, models class SaleOrderLine(models.Model): _inherit = "sale.order.line" - @api.depends("product_id.replenishment_cost") def _compute_purchase_price(self): super()._compute_purchase_price() for line in self.filtered("product_id"): From eec15981ded8a63cad4f46681043f08a534cea7b Mon Sep 17 00:00:00 2001 From: adhoc-cicd-bot <116299102+adhoc-cicd-bot@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:18:23 -0300 Subject: [PATCH 22/24] [UPD] Copilot instructions --- .github/copilot-instructions.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c985deec2..a30ad3cff 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -17,6 +17,7 @@ * 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. --- @@ -38,13 +39,7 @@ * 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. * **Regla de versión (obligatoria):** - Siempre que el diff incluya **modificaciones en**: - - * definición de campos o modelos (`models/*.py`, `wizards/*.py`), - * vistas o datos XML (`views/*.xml`, `data/*.xml`, `report/*.xml`, `wizards/*.xml`), - * seguridad (`security/*.csv`, `security/*.xml`), - - **y el `__manifest__.py` no incrementa `version`, sugerir el bump de versión** (por ejemplo, `1.0.0 → 1.0.1`). + Solo sugerir bump de versión si el `__manifest__.py` no incrementa `version` y se modificó la estructura de un modelo, una vista, o algún record .xml (ej. cambios en definición de campos, vistas XML, datos XML, seguridad). * Solo hacerlo una vez por revisión, aunque haya múltiples archivos afectados. --- @@ -288,7 +283,7 @@ def migrate(cr, registry): | ------------------ | -------------------------------------------------------------------------------------------------------- | | 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 ``) | -| Manifest | **Bump de versión obligatorio** si hay cambios en modelos/vistas/seguridad/datos; archivos referenciados | +| Manifest | **Bump de versión obligatorio** si hay cambios estructurales en modelos/vistas/records .xml; archivos referenciados | | 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 | @@ -298,7 +293,7 @@ def migrate(cr, registry): ## Heurística práctica para el bump de versión (general) -* **SI** el diff toca cualquiera de: `models/`, `views/`, `data/`, `report/`, `security/`, `wizards/` +* **SI** el diff modifica la estructura de un modelo, una vista, o algún record .xml (ej. cambios en definición de campos, vistas XML, datos XML, seguridad) **Y** `__manifest__.py` no cambia `version` → **Sugerir bump**. * **SI** hay scripts `migrations/pre_*.py` o `migrations/post_*.py` nuevos → **Sugerir al menos minor bump**. * **SI** hay cambios que rompen compatibilidad (renombres, cambios de tipo con impacto, limpieza masiva de datos) → **Sugerir minor/major** según impacto. @@ -321,12 +316,10 @@ def migrate(cr, registry): ## Resumen operativo para Copilot -1. **Detecta cambios en modelos/vistas/seguridad/datos → exige bump de `version` en `__manifest__.py`.** +1. **Detecta cambios estructurales en modelos, vistas o records .xml → exige bump de `version` en `__manifest__.py` si no está incrementada.** 2. **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. 3. 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). -4. Mantén el feedback **concreto, breve y accionable**. - -[^odoo18]: Resumen basado en la documentación oficial de Odoo 18 Release Notes y artículos técnicos que analizan sus mejoras de rendimiento y UX. \ No newline at end of file +4. Mantén el feedback **concreto, breve y accionable**. \ No newline at end of file From 8632232e9ad782293c2291074b0076e3e7fd1cef Mon Sep 17 00:00:00 2001 From: Virginia Date: Thu, 29 Jan 2026 16:41:56 -0300 Subject: [PATCH 23/24] Update project.toml from template --- .copier-answers.yml | 2 +- .github/workflows/pre-commit.yml | 7 ++++++- .pre-commit-config.yaml | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index fff1dfe73..74b700afe 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: 2f2f7c4 +_commit: a740779 _src_path: https://github.com/ingadhoc/addons-repo-template.git description: '' is_private: false diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 349c52d82..baa05dbf9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,8 +6,13 @@ name: pre-commit on: push: - branches: "[0-9][0-9].0" + branches: + - "1[8-9].0" + - "[2-9][0-9].0" pull_request_target: + branches: + - "1[8-9].0*" + - "[2-9][0-9].0*" jobs: pre-commit: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fc269814a..c4be55ffa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,6 +29,8 @@ repos: - id: check-docstring-first - id: check-executables-have-shebangs - id: check-merge-conflict + args: ['--assume-in-merge'] + exclude: '\.rst$' - id: check-symlinks - id: check-xml - id: check-yaml From b0b047d8a984da22aac728c768c1de6d88242269 Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Wed, 18 Feb 2026 15:41:59 -0300 Subject: [PATCH 24/24] [ADD] product_catalog_tree_custom: New module --- product_catalog_tree_custom/__init__.py | 0 product_catalog_tree_custom/__manifest__.py | 19 ++++++++ .../views/product_catalog_list_views.xml | 46 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 product_catalog_tree_custom/__init__.py create mode 100644 product_catalog_tree_custom/__manifest__.py create mode 100644 product_catalog_tree_custom/views/product_catalog_list_views.xml diff --git a/product_catalog_tree_custom/__init__.py b/product_catalog_tree_custom/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/product_catalog_tree_custom/__manifest__.py b/product_catalog_tree_custom/__manifest__.py new file mode 100644 index 000000000..272e8b390 --- /dev/null +++ b/product_catalog_tree_custom/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Product Catalog Tree Custom", + "version": "18.0.1.0.0", + "category": "Product", + "summary": "Customizations for product catalog tree view", + "author": "ADHOC SA", + "license": "LGPL-3", + "depends": [ + "product_catalog_tree", + "website_sale", + "product_price_taxes_included", + "product_brand", + ], + "data": [ + "views/product_catalog_list_views.xml", + ], + "installable": True, + "application": False, +} diff --git a/product_catalog_tree_custom/views/product_catalog_list_views.xml b/product_catalog_tree_custom/views/product_catalog_list_views.xml new file mode 100644 index 000000000..96069c06c --- /dev/null +++ b/product_catalog_tree_custom/views/product_catalog_list_views.xml @@ -0,0 +1,46 @@ + + + + + gg.product.view.list.catalog.ext + product.product + + extension + 50 + + + + + hide + + + + + hide + + + + + show + + + + + show + + + + show + + + + show + + + + show + + + + +