addons-cm/website_sale_aplicoop/README_DEV.md
GitHub Copilot f2194c5367 [ADD] website_sale_disable_cart: extract cart restriction from website_sale_aplicoop
The "shop as a read-only catalog" behaviour lived inside website_sale_aplicoop,
so every site that wanted the eskaera flow also lost the standard cart. It now
ships as its own installable addon, with the redirect target configurable
instead of hardcoded to /eskaera.

- website_sale_disable_cart: hides the cart UI (12 header styles + the product
  card quick-add) and overrides the standard cart endpoints. Redirect URL is
  configurable in Website settings (default /shop); only site-internal paths are
  accepted, so a misconfigured value cannot turn the shop into an open redirect
  nor loop back into a disabled route.
- Fixes carried over from the original code: /shop/cart/quantity is the Odoo 18
  path (it was /shop/cart_quantity, which never matched), the boxed, sidebar and
  sales two/three/four headers were not covered (the cart link stayed visible on
  them), and the routes now override the standard methods instead of registering
  duplicate ones.
- website_sale_aplicoop 18.0.1.12.0: drops the view file and the four redirect
  routes; installing it no longer touches the standard shop.

Upgrade order matters: update website_sale_aplicoop first, then install
website_sale_disable_cart in a second Odoo run — both use the same XPaths and
obsolete records are only cleaned up at the end of a run.

Tests: 8/8 in website_sale_disable_cart, aplicoop unaffected (its 2 failures
predate this change). Verified live on a DB clone: /shop/cart returns 303 to the
configured URL and no cart markup remains on /shop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 11:34:59 +02:00

199 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
## 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: <https://git.criptomart.net/criptomart/addons-cm>
- Changelog: [CHANGELOG.md](CHANGELOG.md)