[REF] stock_picking_batch_collect: rename and drop the aplicoop dependency
"custom" said nothing about what the module does. It is really about collecting goods into baskets: the extra detailed-operation columns, the is_collected flag, the Product Summary tab, the per-company validation restrictions and the Basket Assembly operator view all serve that one job. Rename it accordingly. Invert the dependency while at it. A generic warehouse addon was dragging in an entire eCommerce application, and the whole coupling was a single field: stock.move.line.home_delivery, related to picking_id.home_delivery. Everything else was already duck-typed. website_sale_aplicoop now depends on this module and injects its own consumer group columns into these views. This removes duplicated logic rather than relocating it: stock.picking .batch_consumer_group_id re-derived from sale_id a value aplicoop already stored as stock.picking.consumer_group_id, and the duplicate carried no @api.depends, so it never recomputed reliably. The batch transfers list now shows the stored field, which is sortable and groupable. The two aplicoop tests that probed information_schema for the res_company batch_* columns can drop that guard: a real dependency guarantees them. Renaming an addon is not something a migrations/ script can do, since a renamed addon is a brand new module to Odoo and its migration scripts never run. A pre_init_hook does it instead: it fires on install after the Python is imported but before registry.load(), which is the window where remapping ir_model_data makes Odoo reuse the existing tables and columns. is_collected, the summary line table and the company settings all survive untouched. Two details the hook has to get right: - ir_model_constraint.module and ir_model_relation.module are integer FKs with ON DELETE CASCADE, so they must be repointed before the old module row is deleted or the bookkeeping goes with it. - Deleting an ir_model_data row does not cascade to the record it points at. Artifacts handed over to aplicoop only need the xmlid dropped, but artifacts that disappear need the record deleted too, or the field survives as an orphan manual field and the view as a custom view referencing it. Verified against a restored copy of the dev database: 50 xmlids moved, 19 summary lines and 5 collected move lines preserved, no orphans, both test suites green, and the module installs cleanly on a database without website_sale_aplicoop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
704f0def1b
commit
ef1283be7c
42 changed files with 926 additions and 709 deletions
|
|
@ -135,22 +135,36 @@ La página inicial renderiza la primera página server-side. El botón "Cargar m
|
|||
|
||||
| Cron | Frecuencia | Acción |
|
||||
| ---- | ---------- | ------ |
|
||||
| `_cron_confirm_group_orders` | Diario (medianoche) | Confirma sale.orders pasado el cutoff; crea lotes de picking agrupados por consumer_group + pickup_date |
|
||||
| `_cron_confirm_group_orders` | Diario (medianoche) | Confirma sale.orders pasado el cutoff; crea lotes de picking por pedido de grupo, uno por tipo de operación (`_create_picking_batches`) |
|
||||
| `_cron_update_dates` | Diario | Recalcula `delivery_date` y `pickup_date` en órdenes activas |
|
||||
|
||||
## Dependencias
|
||||
|
||||
Según `__manifest__.py`:
|
||||
|
||||
```text
|
||||
website_sale_aplicoop
|
||||
├── website_sale (Odoo core)
|
||||
├── website_sale_stock (Odoo core)
|
||||
├── payment (Odoo core)
|
||||
├── product (Odoo core)
|
||||
├── sale (Odoo core)
|
||||
├── product_main_seller (OCA)
|
||||
├── product_sale_price_from_pricelist (custom)
|
||||
│ └── product_pricelist_total_margin (custom)
|
||||
│ └── product_price_category (OCA)
|
||||
└── stock_picking_batch_custom (custom)
|
||||
├── stock (Odoo core)
|
||||
├── account (Odoo core)
|
||||
├── stock_picking_batch (Odoo core)
|
||||
├── stock_picking_batch_collect (custom)
|
||||
│ ├── stock_move_manual_quantity (custom)
|
||||
│ └── stock_picking_batch (Odoo core)
|
||||
├── product_get_price_helper (OCA)
|
||||
└── l10n_es_partner (OCA)
|
||||
```
|
||||
|
||||
La dependencia sobre `stock_picking_batch_collect` es deliberada y va en este
|
||||
sentido: ese addon es genérico de almacén y no sabe nada de grupos de consumo.
|
||||
Es `website_sale_aplicoop` quien inyecta en sus vistas las columnas
|
||||
**Consumer Group** y **Home Delivery** (ver `views/stock_picking_batch_views.xml`
|
||||
y `models/stock_move_line_extension.py`).
|
||||
|
||||
## Instalación y actualización
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{ # noqa: B018
|
||||
"name": "Website Sale - Aplicoop",
|
||||
"version": "18.0.1.16.0",
|
||||
"version": "18.0.1.17.0",
|
||||
"category": "Website/Sale",
|
||||
"summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders",
|
||||
"author": "Odoo Community Association (OCA), Criptomart",
|
||||
|
|
@ -18,6 +18,9 @@
|
|||
"sale",
|
||||
"stock",
|
||||
"stock_picking_batch",
|
||||
# Owns the batch collect views into which we inject the consumer group
|
||||
# columns (home_delivery, consumer_group_id).
|
||||
"stock_picking_batch_collect",
|
||||
"account",
|
||||
"product_get_price_helper",
|
||||
"l10n_es_partner",
|
||||
|
|
@ -43,6 +46,7 @@
|
|||
"views/product_template_views.xml",
|
||||
"views/sale_order_views.xml",
|
||||
"views/stock_picking_views.xml",
|
||||
"views/stock_picking_batch_views.xml",
|
||||
"views/portal_templates.xml",
|
||||
"views/load_from_history_templates.xml",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
from . import group_order # noqa: F401
|
||||
from . import group_order_slot # noqa: F401
|
||||
from . import js_translations # noqa: F401
|
||||
from . import payment_transaction # noqa: F401
|
||||
from . import product_category_extension # noqa: F401
|
||||
from . import product_extension # noqa: F401
|
||||
from . import res_config_settings # noqa: F401
|
||||
from . import res_partner_extension # noqa: F401
|
||||
from . import sale_order_extension # noqa: F401
|
||||
from . import stock_move_line_extension # noqa: F401
|
||||
from . import stock_picking_extension # noqa: F401
|
||||
from . import website # noqa: F401
|
||||
from . import js_translations # noqa: F401
|
||||
|
|
|
|||
28
website_sale_aplicoop/models/stock_move_line_extension.py
Normal file
28
website_sale_aplicoop/models/stock_move_line_extension.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2026 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import fields
|
||||
from odoo import models
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
"""Surface the consumer group data on the batch operation lines.
|
||||
|
||||
The picking already carries these values as stored related fields, so the
|
||||
move line only has to hop one step further. Chaining off ``picking_id``
|
||||
instead of re-deriving from ``sale_id`` keeps a single source of truth.
|
||||
"""
|
||||
|
||||
_inherit = "stock.move.line"
|
||||
|
||||
home_delivery = fields.Boolean(
|
||||
related="picking_id.home_delivery",
|
||||
string="Home Delivery",
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
consumer_group_id = fields.Many2one(
|
||||
related="picking_id.consumer_group_id",
|
||||
string="Consumer Group",
|
||||
readonly=True,
|
||||
)
|
||||
|
|
@ -14,39 +14,16 @@ class TestMultiCompanyGroupOrder(TransactionCase):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Crear dos compañías
|
||||
# Crear dos compañías. `stock_picking_batch_collect` es dependencia del
|
||||
# módulo, así que los campos de restricción de lote siempre existen.
|
||||
company_model = self.env["res.company"]
|
||||
|
||||
# Compatibilidad con esquemas legacy: columna NOT NULL presente sin campo ORM.
|
||||
self.env.cr.execute("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'res_company'
|
||||
AND column_name IN ('batch_summary_restriction_scope', 'batch_detailed_restriction_scope')
|
||||
""")
|
||||
existing_columns = {row[0] for row in self.env.cr.fetchall()}
|
||||
if (
|
||||
"batch_summary_restriction_scope" in existing_columns
|
||||
and "batch_summary_restriction_scope" not in company_model._fields
|
||||
):
|
||||
self.env.cr.execute(
|
||||
"ALTER TABLE res_company ALTER COLUMN batch_summary_restriction_scope SET DEFAULT 'processed'"
|
||||
)
|
||||
if (
|
||||
"batch_detailed_restriction_scope" in existing_columns
|
||||
and "batch_detailed_restriction_scope" not in company_model._fields
|
||||
):
|
||||
self.env.cr.execute(
|
||||
"ALTER TABLE res_company ALTER COLUMN batch_detailed_restriction_scope SET DEFAULT 'processed'"
|
||||
)
|
||||
|
||||
def _company_vals(name):
|
||||
vals = {"name": name}
|
||||
if "batch_summary_restriction_scope" in company_model._fields:
|
||||
vals["batch_summary_restriction_scope"] = "processed"
|
||||
if "batch_detailed_restriction_scope" in company_model._fields:
|
||||
vals["batch_detailed_restriction_scope"] = "processed"
|
||||
return vals
|
||||
return {
|
||||
"name": name,
|
||||
"batch_summary_restriction_scope": "processed",
|
||||
"batch_detailed_restriction_scope": "processed",
|
||||
}
|
||||
|
||||
self.company1 = company_model.create(_company_vals("Company 1"))
|
||||
self.company2 = company_model.create(_company_vals("Company 2"))
|
||||
|
|
|
|||
|
|
@ -14,39 +14,16 @@ class TestGroupOrderRecordRules(TransactionCase):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# Crear dos compañías
|
||||
# Crear dos compañías. `stock_picking_batch_collect` es dependencia del
|
||||
# módulo, así que los campos de restricción de lote siempre existen.
|
||||
company_model = self.env["res.company"]
|
||||
|
||||
# Compatibilidad con esquemas legacy: columna NOT NULL presente sin campo ORM.
|
||||
self.env.cr.execute("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'res_company'
|
||||
AND column_name IN ('batch_summary_restriction_scope', 'batch_detailed_restriction_scope')
|
||||
""")
|
||||
existing_columns = {row[0] for row in self.env.cr.fetchall()}
|
||||
if (
|
||||
"batch_summary_restriction_scope" in existing_columns
|
||||
and "batch_summary_restriction_scope" not in company_model._fields
|
||||
):
|
||||
self.env.cr.execute(
|
||||
"ALTER TABLE res_company ALTER COLUMN batch_summary_restriction_scope SET DEFAULT 'processed'"
|
||||
)
|
||||
if (
|
||||
"batch_detailed_restriction_scope" in existing_columns
|
||||
and "batch_detailed_restriction_scope" not in company_model._fields
|
||||
):
|
||||
self.env.cr.execute(
|
||||
"ALTER TABLE res_company ALTER COLUMN batch_detailed_restriction_scope SET DEFAULT 'processed'"
|
||||
)
|
||||
|
||||
def _company_vals(name):
|
||||
vals = {"name": name}
|
||||
if "batch_summary_restriction_scope" in company_model._fields:
|
||||
vals["batch_summary_restriction_scope"] = "processed"
|
||||
if "batch_detailed_restriction_scope" in company_model._fields:
|
||||
vals["batch_detailed_restriction_scope"] = "processed"
|
||||
return vals
|
||||
return {
|
||||
"name": name,
|
||||
"batch_summary_restriction_scope": "processed",
|
||||
"batch_detailed_restriction_scope": "processed",
|
||||
}
|
||||
|
||||
self.company1 = company_model.create(_company_vals("Company 1"))
|
||||
self.company2 = company_model.create(_company_vals("Company 2"))
|
||||
|
|
|
|||
40
website_sale_aplicoop/views/stock_picking_batch_views.xml
Normal file
40
website_sale_aplicoop/views/stock_picking_batch_views.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Consumer group columns on the batch operator screen (Basket Assembly) -->
|
||||
<record id="view_move_line_batch_operator_consumer_group" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.batch.operator.consumer.group</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<field name="inherit_id" ref="stock_picking_batch_collect.view_move_line_batch_operator"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='picking_partner_id']" position="after">
|
||||
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
||||
<field name="consumer_group_id" readonly="1" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Consumer group columns on the batch detailed operations list -->
|
||||
<record id="view_move_line_tree_batch_consumer_group" model="ir.ui.view">
|
||||
<field name="name">stock.move.line.list.batch.consumer.group</field>
|
||||
<field name="model">stock.move.line</field>
|
||||
<field name="inherit_id" ref="stock_picking_batch_collect.view_move_line_tree_inherit_batch_custom"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='product_default_code']" position="after">
|
||||
<field name="home_delivery" readonly="1" widget="boolean_toggle" optional="show"/>
|
||||
<field name="consumer_group_id" readonly="1" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Consumer group on the transfers list of a batch -->
|
||||
<record id="view_stock_picking_batch_picking_tree_consumer_group" model="ir.ui.view">
|
||||
<field name="name">stock.picking.batch.picking.tree.consumer.group</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock_picking_batch.stock_picking_view_batch_tree_ref"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="consumer_group_id" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue