addons-cm/website_sale_aplicoop/README_DEV.md
GitHub Copilot ef1283be7c [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>
2026-08-27 14:26:47 +02:00

8.3 KiB
Raw Blame History

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_sale se puede deshabilitar aparte con website_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 (06) 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 (de product_main_seller)
  • sequence en product.category — orden de visualización en la tienda web

sale.order

  • group_order_id — enlace a la orden de grupo
  • consumer_group_id — propagado desde group_order_id
  • pickup_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

La lógica en _get_products_for_group_order() aplica en este orden:

  1. Whitelist: unión de product_ids + productos de category_ids (recursivo) + productos de supplier_ids (por main_seller_id)
  2. Blacklist: se restan excluded_product_ids, productos con main_seller_id en excluded_supplier_ids, y productos en excluded_category_ids (recursivo)
  3. 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, cron
  • test_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