"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>
8.3 KiB
Website Sale - Aplicoop
Versión: 18.0.1.9.0 | Licencia: AGPL-3 | Autor: Criptomart SL
Sistema de pedidos colaborativos para grupos de consumo. Reemplaza el legacy Aplicoop con una solución moderna integrada en Odoo 18.
Resumen de funcionalidades
- Gestión completa de órdenes de grupo (draft → confirmed → collected → invoiced → completed)
- Carrito separado por grupo de consumo (la tienda estándar de
website_salese puede deshabilitar aparte conwebsite_sale_disable_cart) - Control de catálogo por listas de inclusión (proveedores, categorías, productos) y exclusión (blacklists)
- Cron diario de auto-confirmación + creación automática de lotes de picking
- Paginación de productos (lazy loading) para cargas rápidas
- Soporte multilingüe: ES, EU, PT, GL, CA, FR, IT
- Restricción de acceso portal por grupo de consumo
Modelos
group.order
Orden de grupo central. Contiene toda la lógica de ciclo de vida.
Campos principales:
| Campo | Tipo | Descripción |
|---|---|---|
name |
Char | Identificador de la orden |
consumer_group_id |
Many2one → res.partner |
Grupo de consumo |
state |
Selection | draft / confirmed / collected / invoiced / completed / cancelled |
cutoff_date |
Datetime | Hasta cuándo se pueden añadir productos |
pickup_date |
Date | Día de recogida para los socios |
delivery_date |
Date | Fecha de entrega (almacenada, calculada por cron) |
product_ids |
Many2many | Productos incluidos directamente |
category_ids |
Many2many | Categorías incluidas (y sus subcategorías) |
supplier_ids |
Many2many | Proveedores incluidos (por main_seller_id) |
excluded_product_ids |
Many2many | Blacklist de productos |
excluded_supplier_ids |
Many2many | Blacklist de proveedores |
excluded_category_ids |
Many2many | Blacklist de categorías (recursiva) |
allow_home_delivery |
Boolean | Permite envío a domicilio además de recogida |
Métodos clave:
_get_products_for_group_order()— catálogo efectivo aplicando whitelist + blacklist_get_products_paginated(page, per_page)— paginación para lazy loading_cron_confirm_group_orders()— auto-confirma órdenes pasado el cutoff y crea lotes de picking_cron_update_dates()— recalcula fechas de entrega/recogida diariamente
group.order.slot (en desarrollo)
Franjas horarias de recogida para una orden de grupo.
| Campo | Tipo | Descripción |
|---|---|---|
group_order_id |
Many2one | Orden de grupo |
weekday |
Selection (0–6) | Día de la semana (0 = lunes) |
start_hour / end_hour |
Float | Horario en formato decimal (9.5 = 09:30) |
sequence |
Integer | Orden de visualización |
Extensiones de modelos core
product.template
main_seller_id— proveedor principal (deproduct_main_seller)sequenceenproduct.category— orden de visualización en la tienda web
sale.order
group_order_id— enlace a la orden de grupoconsumer_group_id— propagado desdegroup_order_idpickup_slot_label— etiqueta legible de la franja de recogida
stock.picking
- Extensión para agrupación por grupo de consumo en lotes de picking
Controladores
Todos los endpoints viven bajo /eskaera/:
| Ruta | Descripción |
|---|---|
GET /eskaera |
Lista de órdenes de grupo activas del usuario |
GET /eskaera/<order_id> |
Tienda de la orden (con lazy loading) |
GET /eskaera/<order_id>/load-page?page=N |
Carga AJAX de página de productos |
POST /eskaera/<order_id>/add |
Añadir producto al carrito |
POST /eskaera/<order_id>/confirm |
Confirmar carrito (sale.order en draft) |
POST /eskaera/clear-cart |
Limpiar carrito actual |
Las rutas estándar de website_sale (/shop y su carrito) no se tocan desde
este módulo. Para desactivar el carrito estándar y redirigirlo a /eskaera,
instala website_sale_disable_cart y configura la URL de redirección.
Seguridad portal: los usuarios solo ven órdenes de su consumer_group_id. La regla rule_group_order_company_read incluye guardia user.share.
Templates QWeb
| Template | Descripción |
|---|---|
eskaera_list |
Lista de órdenes activas |
eskaera_shop |
Página de la tienda (incluye eskaera_shop_products) |
eskaera_shop_products |
Grid de productos (reutilizable por lazy loading + initial render) |
eskaera_cart |
Sidebar del carrito |
eskaera_checkout |
Confirmación del pedido |
load_from_history |
Cargar productos de una orden histórica |
JavaScript (website_sale.js)
Clase GroupOrderShop (extiende publicWidget.Widget):
_attachEventListeners()— inicializa todos los listeners_attachLoadMoreListener()— botón "Cargar más" para lazy loading (AJAX)_onAddToCart()— añadir al carrito con feedback visual_onClearCart()— limpiar carrito_onDeliveryToggle()— toggle envío a domicilio / recogida
Control del catálogo
La lógica en _get_products_for_group_order() aplica en este orden:
- Whitelist: unión de
product_ids+ productos decategory_ids(recursivo) + productos desupplier_ids(pormain_seller_id) - Blacklist: se restan
excluded_product_ids, productos conmain_seller_idenexcluded_supplier_ids, y productos enexcluded_category_ids(recursivo) - La blacklist tiene prioridad absoluta sobre cualquier fuente de inclusión
Lazy loading
Configurable desde Ajustes > Website > Shop:
website_sale_aplicoop.lazy_loading_enabled(default:True)website_sale_aplicoop.products_per_page(default:20)
La página inicial renderiza la primera página server-side. El botón "Cargar más" hace peticiones AJAX al endpoint /load-page que devuelve HTML parcial con el template eskaera_shop_products.
Cron jobs
| Cron | Frecuencia | Acción |
|---|---|---|
_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:
website_sale_aplicoop
├── website_sale (Odoo core)
├── website_sale_stock (Odoo core)
├── payment (Odoo core)
├── product (Odoo core)
├── sale (Odoo core)
├── 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
docker-compose exec -T odoo odoo -d odoo -u website_sale_aplicoop --stop-after-init
Para migraciones con cambios de esquema (e.g., nuevas columnas):
docker-compose exec -T odoo odoo -d odoo --update website_sale_aplicoop --stop-after-init
Tests
docker-compose exec -T odoo odoo -d odoo --test-enable --stop-after-init -u website_sale_aplicoop
Los tests están en tests/:
test_group_order.py— ciclo de vida básico, fechas, crontest_product_discovery.py— whitelist/blacklist (productos, proveedores, categorías)test_confirm_eskaera.py— integración: confirmación de carrito y generación de sale.order
Traducciones
# Exportar .pot
docker-compose exec -T odoo odoo -d odoo \
-i website_sale_aplicoop \
--i18n-export=/tmp/website_sale_aplicoop.pot \
--stop-after-init
# Actualizar .po existentes
cd website_sale_aplicoop/i18n
for lang in es eu; do
msgmerge -U ${lang}.po ../website_sale_aplicoop.pot
done
Ver docs/TRANSLATIONS.md para convenciones.
Repositorio
- Repo: https://git.criptomart.net/criptomart/addons-cm
- Changelog: CHANGELOG.md