# 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; tienda estándar de `website_sale` deshabilitada - 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 (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/` | Tienda de la orden (con lazy loading) | | `GET /eskaera//load-page?page=N` | Carga AJAX de página de productos | | `POST /eskaera//add` | Añadir producto al carrito | | `POST /eskaera//confirm` | Confirmar carrito (sale.order en draft) | | `POST /eskaera/clear-cart` | Limpiar carrito actual | Las rutas `/cart` y `/shop` de `website_sale` están redirigidas a `/eskaera`. **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: 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 agrupados por consumer_group + pickup_date | | `_cron_update_dates` | Diario | Recalcula `delivery_date` y `pickup_date` en órdenes activas | ## Dependencias ```text website_sale_aplicoop ├── website_sale (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) ``` ## Instalación y actualización ```bash 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): ```bash docker-compose exec -T odoo odoo -d odoo --update website_sale_aplicoop --stop-after-init ``` ## Tests ```bash 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 ```bash # 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](../docs/TRANSLATIONS.md) para convenciones. ## Repositorio - Repo: - Changelog: [CHANGELOG.md](CHANGELOG.md)