Aplicoop desde el repo de kidekoop

This commit is contained in:
snt 2026-02-11 15:32:11 +01:00
parent 69917d1ec2
commit 7cff89e418
93 changed files with 313992 additions and 0 deletions

View file

@ -0,0 +1,13 @@
Group orders (*eskaera*) are a business model for collaborative consumption where groups of users collectively purchase products within defined time windows. This module was created to replace the legacy Aplicoop application, providing:
**Business Value:**
- Streamlined group purchasing workflows within Odoo's standard sales framework
- Flexible scheduling to accommodate different group shopping patterns (daily, weekly, biweekly, monthly)
- Clear separation between temporary shopping carts and permanent sales orders
- Support for multiple groups with different suppliers, products, and categories
**Use Cases:**
- Cooperative grocery purchasing groups
- Bulk order consolidation for community members
- Time-limited promotional campaigns with group participation
- Multi-location organizations with shared procurement

View file

@ -0,0 +1,32 @@
# Contributors
This module has been developed and is maintained by the following contributors:
## Authors
* **Criptomart** (https://criptomart.net)
- Project lead and main development
## Contributions
Special thanks to all contributors who have helped improve this module through:
* Code contributions
* Bug reports and fixes
* Documentation improvements
* Translation support
* Testing and feedback
## Historical References
This module was inspired by the original **Aplicoop** project:
* https://sourceforge.net/projects/aplicoop/
* Original creators: Ekaitz Mendiluze, Joseba Legarreta, and other contributors
The original Aplicoop project served as a pioneering solution for collaborative consumption group orders, and this module brings its functionality to the modern Odoo platform.
## License Attribution
All original code is Copyright © 2025 Criptomart and licensed under AGPL-3.
For contributions to be accepted, contributors must agree to license their code under AGPL-3 as well.

View file

@ -0,0 +1,9 @@
This module was developed by Criptomart in 2025 as a modernization of the Aplicoop application, integrating collaborative consumption group order management directly into Odoo's website sales framework.
## Additional Contributions
The implementation follows OCA standards for:
- Code quality and testing (26 passing tests)
- Documentation structure and multilingual support
- Security and access control

View file

@ -0,0 +1,12 @@
This module replaces the legacy Aplicoop application with a modern, scalable solution for managing collaborative consumption group orders (*eskaera* in Basque) within Odoo's standard website sales framework.
## Features
- **Group Order Management**: Create and manage group orders (eskaera) with customizable state transitions (draft → open → closed/cancelled)
- **Weekly Activity Filtering**: Automatically filter active orders for the current week based on start/end dates and time windows
- **Flexible Scheduling**: Support for optional start/end dates to define order availability windows
- **Cutoff Day Support**: Define weekly cutoff days for group orders to control when purchases can be made
- **Product Association**: Link products to specific group orders through Many2many relationships
- **Partner Group Association**: Link partners (users) to groups via Many2many relationships for group-based shopping
- **i18n Support**: Full internationalization with translations for 7 languages (Spanish, French, Catalan, Basque, Galician, Italian, Portuguese)
- **OCA Compliant**: AGPL-3.0 licensed, follows OCA standards for documentation, testing, and code structure

View file

@ -0,0 +1,123 @@
## [18.0.1.0.3] - 2025-12-19
### Added
- Centralised product discovery API on `group.order`: `_get_products_for_group_order(order_id)`.
- Backward-compatible delegation on `product.product._get_products_for_group_order`.
- Controller `AplicoopWebsiteSale` now delegates discovery to `group.order` and sanitises supplier display.
### Changed
- Moved discovery responsibility from `product.product` to `group.order` (single responsibility).
- Updated `eskaera_shop`, `add_to_eskaera_cart` and `confirm_eskaera` to use centralised discovery.
### Fixed
- Avoided runtime AttributeError when discovery function was not present by providing a canonical implementation.
- Ensured product discovery priority remains: explicit products → categories → suppliers.
- Fixed a regression where discovery returned only one association branch; discovery now
returns the UNION of products from `product_ids`, `category_ids` and
`supplier_ids` to avoid dropping valid products when multiple associations exist.
Note: this change documents and prevents a repeated mistake where a single
fallback branch hid products from other association fields.
### Tests
- `website_sale_aplicoop` test-suite: 63 tests, 0 failures after the refactor (commit 4b15207).
### Security
- Kept portal surface minimal (no `res.partner` records exposed); controller only injects supplier name/city for display.
### I18N
- Regenerated translation template (`.pot`) using an addon-only export to avoid collecting unrelated Odoo strings.
- Added `docs/I18N_EXPORT_PITFALL.md` explaining the common export pitfall and safe export workflow (export to `/tmp`, restrict `--addons-path`, use `msgmerge`).
- Added `tools/filter_pot_by_module.py` to filter POT files by module references and applied it to reduce the POT to addon-only entries (~168 entries).
- Updated and committed cleaned `.po` files for all supported languages (es, pt, gl, ca, eu, fr, it).
## [18.0.1.0.2] - 2025-12-14
### Added
- Multi-company support with company_id field on group.order
- Company validation constraint to ensure groups belong to the same company
- Multi-company filtering in get_active_orders_for_week() method
- company_id field on res.partner for user-group relationships
- 9 new test cases for multi-company scenarios (test_multi_company.py)
- Post-migration script to assign default company to existing group.order records
### Changed
- group.order now respects allowed_company_ids context for data isolation
- get_active_orders_for_week() filters by company context when applicable
- group_ids domain now validates company relationships
### Fixed
- Ensured multi-company data isolation between different organizations
## [18.0.1.0.1] - 2025-12-14
### Added
- Product discovery with 3-level fallback system (direct → categories → suppliers)
- Search functionality by product name and description in eskaera_shop
- Dynamic category filtering dropdown on shopping page
- Product thumbnail images with fallback icons in base64 encoding
- Comprehensive logging for debugging group order flow
- Logging of cutoff day, pickup day, and order dates in eskaera_shop
- 7 new test cases for product discovery scenarios (test_eskaera_shop.py)
- Criptomart branding with logo.svg
- Professional HTML documentation in static/description/index.html
### Changed
- Refactored product lookup to support three discovery methods
- Updated confirm_eskaera to accept products from any discovery method
- Improved error handling in checkout flow
- Enhanced template rendering with conditional field validation
- Optimized product search with regex and case-insensitive matching
### Fixed
- Fixed products not appearing when assigned via categories or suppliers (discovered via fallback)
- Fixed translation errors in 7 languages (es, fr, ca, eu, gl, it, pt) - order type labels
- Removed obsolete website_sale.py model file causing ImportError
- Fixed checkout validation that blocked orders with category/supplier-discovered products
- Fixed QWebException when start_date is optional by adding t-if validation
- Fixed duplicate sale.order creation from double event binding on confirm button
- Corrected product supplier relationship in tests (use seller_ids instead of supplier_id)
### Deprecated
- N/A
### Removed
- Obsolete models/website_sale.py file (functionality migrated to controllers)
- Duplicate event listener on confirm button in website_templates.xml
- Fallback confirmCheckout() function from template (handled by JS)
### Security
- Maintained CSRF protection on all POST routes
- Input validation on JSON payloads in confirm_eskaera
- Access control validation for group membership
## [18.0.1.0.0-beta] - 2025-12-13
### Added
- Initial beta release of Website Sale - Aplicoop module
- Complete group order management system (draft → open → closed/cancelled)
- Flexible scheduling with start/end dates and optional time windows
- Cutoff day support for weekly order management
- Product and supplier associations
- Group-based user relationships
- Full i18n support for 7 languages (ES, FR, CA, EU, GL, IT, PT)
- 26 passing tests covering model logic and business rules
- OCA-compliant documentation structure
- AGPL-3.0 licensing
### Changed
- N/A (initial release)
### Fixed
- N/A (initial release)
### Deprecated
- N/A (initial release)
### Removed
- N/A (initial release)
### Security
- Access control via group permissions
- CSRF protection on all POST routes
- Input validation on client and server side

View file

@ -0,0 +1,415 @@
# Seguridad y Control de Acceso - Website Sale Aplicoop
## Descripción General
El addon implementa un sistema completo de control de acceso basado en:
1. **ACL (Access Control List)** - Permisos de lectura, escritura, creación y eliminación
2. **Record Rules** - Filtros automáticos de registros por compañía
3. **Grupos de Usuarios** - Roles con permisos específicos
## Arquitectura de Seguridad
```
Usuario
Grupo (group_group_order_user / group_group_order_manager)
ACL (ir.model.access.csv) → Permisos globales (CRUD)
Record Rules (record_rules.csv) → Filtros por compañía
Datos Accesibles
```
## 1. ACL (Access Control List)
Ubicación: [security/ir.model.access.csv](../security/ir.model.access.csv)
### Estructura
```csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_group_order_user,group.order user,model_group_order,website_sale_aplicoop.group_group_order_user,1,0,0,0
access_group_order_manager,group.order manager,model_group_order,website_sale_aplicoop.group_group_order_manager,1,1,1,1
```
### Campos
| Campo | Descripción |
|-------|-------------|
| `id` | Identificador único interno |
| `name` | Descripción legible |
| `model_id:id` | Referencia al modelo (model_group_order) |
| `group_id:id` | Grupo de usuarios que recibe permisos |
| `perm_read` | 1 = Puede leer, 0 = No puede leer |
| `perm_write` | 1 = Puede editar, 0 = No puede editar |
| `perm_create` | 1 = Puede crear, 0 = No puede crear |
| `perm_unlink` | 1 = Puede eliminar, 0 = No puede eliminar |
### Roles y Permisos
#### Grupo: `group_group_order_user` (Usuarios Finales)
```csv
access_group_order_user,group.order user,model_group_order,
website_sale_aplicoop.group_group_order_user,1,0,0,0
```
**Permisos:**
- ✅ Leer órdenes (`perm_read=1`)
- ❌ Editar órdenes (`perm_write=0`)
- ❌ Crear órdenes (`perm_create=0`)
- ❌ Eliminar órdenes (`perm_unlink=0`)
**Casos de uso:**
- Navegar órdenes disponibles
- Ver detalles de pedido
- Agregar productos al carrito
#### Grupo: `group_group_order_manager` (Administradores)
```csv
access_group_order_manager,group.order manager,model_group_order,
website_sale_aplicoop.group_group_order_manager,1,1,1,1
```
**Permisos:**
- ✅ Leer órdenes (`perm_read=1`)
- ✅ Editar órdenes (`perm_write=1`)
- ✅ Crear órdenes (`perm_create=1`)
- ✅ Eliminar órdenes (`perm_unlink=1`)
**Casos de uso:**
- Crear y gestionar órdenes
- Modificar configuración de órdenes
- Cerrar o cancelar órdenes
- Eliminar órdenes (si es necesario)
## 2. Record Rules (Reglas de Registro)
Ubicación: [security/record_rules.csv](../security/record_rules.csv)
### Propósito
Las record rules filtran automáticamente qué registros puede ver/editar un usuario según el valor del campo `company_id`.
```
Usuario de Company A
Record Rule: domain = [('company_id', 'in', company_ids)]
Company_ids (del usuario) = [1] (Company A)
Solo puede acceder a registros donde company_id = 1
```
### Estructura
```csv
id,name,model_id:id,groups:eval,domain_force,perm_read,perm_write,perm_create,perm_unlink
rule_group_order_company_read,group.order: company access read,model_group_order,
website_sale_aplicoop.group_group_order_user,"[('company_id', 'in', company_ids)]",1,0,0,0
rule_group_order_company_write,group.order: company access write,model_group_order,
website_sale_aplicoop.group_group_order_manager,"[('company_id', 'in', company_ids)]",1,1,1,1
rule_group_order_manager_global,group.order: manager global access,model_group_order,
"['admin']","[]",1,1,1,1
```
### Campos
| Campo | Descripción |
|-------|-------------|
| `id` | Identificador único |
| `name` | Descripción |
| `model_id:id` | Modelo que aplica la regla |
| `groups:eval` | Grupo de usuarios (evaluado como Python) |
| `domain_force` | Filtro dominio (sintaxis de búsqueda Odoo) |
| `perm_read/write/create/unlink` | Permisos bajo esta regla |
### Reglas Implementadas
#### Rule 1: Usuarios Finales - Lectura por Compañía
```csv
rule_group_order_company_read,group.order: company access read,model_group_order,
website_sale_aplicoop.group_group_order_user,"[('company_id', 'in', company_ids)]",1,0,0,0
```
**Dominio:** `[('company_id', 'in', company_ids)]`
- `company_ids` = lista de compañías del usuario
- Filtra automáticamente por compañía
**Ejemplo:**
```
Usuario "Juan" pertenece a [Company A]
Intenta ver órdenes
Dominio aplicado: company_id IN (1)
Resultado: Solo órdenes de Company A
```
#### Rule 2: Administradores - Lectura/Escritura por Compañía
```csv
rule_group_order_company_write,group.order: company access write,model_group_order,
website_sale_aplicoop.group_group_order_manager,"[('company_id', 'in', company_ids)]",1,1,1,1
```
**Dominio:** `[('company_id', 'in', company_ids)]`
- Igual que usuarios finales
- Pero con permisos de escritura/creación
**Ejemplo:**
```
Admin "Pedro" pertenece a [Company A, Company B]
Crea nueva orden
Dominio aplicado: company_id IN (1, 2)
- Si crea en Company A: ✅ Permitido
- Si crea en Company B: ✅ Permitido
- Si intenta acceder a Company C: ❌ Denegado
```
#### Rule 3: Superusuarios - Acceso Global
```csv
rule_group_order_manager_global,group.order: manager global access,model_group_order,
"['admin']","[]",1,1,1,1
```
**Grupo:** `['admin']` (Superusuario de Odoo)
**Dominio:** `[]` (vacío = sin restricción)
**Comportamiento:**
- Acceso completo a todos los registros
- Puede ver/editar órdenes de cualquier compañía
- Sin filtrado por company_id
## Flujo de Control de Acceso
### Escenario 1: Usuario Final Lee Órdenes
```
1. Usuario "Maria" (group_group_order_user, Company A)
2. Abre menú "Órdenes de Grupo"
3. Odoo verifica:
a) ACL: ¿Tiene perm_read=1? → Sí (grupo_group_order_user)
b) Record Rule: ¿Cumple domain [('company_id', 'in', [1])]?
- Solo órdenes donde company_id = 1
4. Resultado: Maria ve solo sus órdenes de Company A
```
### Escenario 2: Usuario Intenta Editar Orden
```
1. Usuario "Carlos" (group_group_order_user, Company A)
2. Intenta editar orden de Company A
3. Odoo verifica:
a) ACL: ¿Tiene perm_write=1? → No (grupo_group_order_user tiene 0)
b) Resultado: ❌ Acceso denegado - no puede editar
```
### Escenario 3: Admin Edita Orden de Otra Compañía
```
1. Admin "Rosa" (group_group_order_manager, Company A, B)
2. Intenta editar orden de Company B
3. Odoo verifica:
a) ACL: ¿Tiene perm_write=1? → Sí (grupo_group_order_manager)
b) Record Rule: ¿Cumple domain [('company_id', 'in', [1, 2])]?
- company_id de orden = 2
- 2 IN (1, 2) = Sí
c) Resultado: ✅ Rosa puede editar la orden
```
### Escenario 4: Superuser Accede a Todo
```
1. Admin "System" (superuser)
2. Intenta editar cualquier orden de cualquier compañía
3. Odoo verifica:
a) Es admin? → Sí
b) Rule: rule_group_order_manager_global aplica (domain = [])
c) Resultado: ✅ Acceso completo, sin restricciones
```
## Tests
Archivo: [tests/test_record_rules.py](../tests/test_record_rules.py)
### Casos de Prueba
1. **test_user_company1_can_read_own_orders**
- Verifica que usuario de Company A ve sus órdenes
2. **test_user_company1_cannot_read_company2_orders**
- Verifica que usuario NO ve órdenes de Company B
3. **test_admin_can_read_all_orders**
- Verifica que admin con acceso a ambas compañías ve todo
4. **test_user_cannot_write_other_company_order**
- Verifica que usuario no puede editar órdenes de otra compañía (AccessError)
5. **test_record_rule_filters_search**
- Verifica que búsqueda automáticamente filtra por compañía
6. **test_cross_company_access_denied**
- Verifica que acceso entre compañías es denegado
7. **test_admin_can_bypass_company_restriction**
- Verifica que admin puede acceder a cualquier compañía
### Ejecución
```bash
# Ejecutar solo tests de record rules
odoo -d odoo -i website_sale_aplicoop -t website_sale_aplicoop.tests.test_record_rules --test-enable --stop-after-init
# Con pytest
pytest tests/test_record_rules.py -v
```
## Mejores Prácticas
### ✅ Hacer
1. **Confiar en ACL y Record Rules**
```python
# Odoo filtra automáticamente
orders = env['group.order'].search([])
# Solo devuelve órdenes de compañía del usuario
```
2. **Usar grupos de seguridad correctamente**
```xml
<!-- En vista XML -->
<group string="Administración" groups="website_sale_aplicoop.group_group_order_manager">
<button name="action_open" type="object" string="Open Order"/>
</group>
```
3. **Asignar compañías a usuarios**
```python
user.write({
'company_id': company_a.id,
'company_ids': [(6, 0, [company_a.id, company_b.id])]
})
# El usuario ahora ve órdenes de A y B
```
### ❌ No Hacer
1. **No usar sudo() sin razón válida**
```python
# ❌ Malo - bypasea todas las restricciones
order = env['group.order'].sudo().search([])
# ✅ Bueno - respeta reglas
order = env['group.order'].search([])
```
2. **No modificar ACL directamente en SQL**
- Siempre use el CSV de datos
3. **No olvidar agregar usuarios a grupos**
- Los usuarios deben estar en `group_group_order_user` o `group_group_order_manager`
4. **No asumir permisos sin verificar**
- Siempre test con usuarios reales
## Diagnóstico de Problemas
### Problema: Usuario no ve ninguna orden
**Causas posibles:**
1. No está en grupo `group_group_order_user`
2. No está asignado a la compañía correcta
3. No existen órdenes en su compañía
**Solución:**
```python
# Verificar grupo
user.groups_id # Debe incluir group_group_order_user
# Verificar compañía
user.company_ids # Debe incluir la compañía del usuario
# Verificar órdenes existentes
env['group.order'].search([('company_id', '=', company_id)])
```
### Problema: Usuario no puede crear órdenes
**Causas posibles:**
1. Está en `group_group_order_user` (lectura solo)
2. No tiene permiso `perm_create`
**Solución:**
```python
# Mover a grupo de manager
user.groups_id = [(3, group_order_user.id), (4, group_order_manager.id)]
```
### Problema: Error AccessError al leer orden
**Causa probable:**
- La orden está en una compañía diferente
- Record rule está denegando acceso
**Solución:**
```python
# Verificar compañía de orden
order.company_id # Comparar con user.company_ids
```
## Historial de Cambios
### v18.0.1.0.2
- ✨ Record rules agregadas para multicompañía
- 🔒 ACL actualizado con documentación
- 🧪 7 test cases para control de acceso
- 📚 Documentación completa de seguridad
## Referencias
- [Documentación Odoo - ACL](https://www.odoo.com/documentation/18.0/application/general/settings/users_and_companies/access_rights.html)
- [Documentación Odoo - Record Rules](https://www.odoo.com/documentation/18.0/application/general/settings/users_and_companies/record_rules.html)
- [OWASP - Access Control](https://owasp.org/www-community/attacks/Role-Based_Access_Control)
## Cambios recientes y acciones realizadas (19-12-2025)
Se documentan aquí las modificaciones y acciones realizadas durante la sesión de depuración y ejecución de tests:
- **Regla interna `rule_group_order_user_company_read_internal`**: se actualizó el dominio de
`('company_id', '=', user.company_id.id)` a `('company_id', 'in', user.company_ids.ids)` para
soportar usuarios multi-compañía (por ejemplo, administradores creados en tests con
`company_ids` que contienen varias compañías). Esto permite que usuarios con varias
compañías vean las `group.order` pertenecientes a cualquiera de sus `company_ids`.
- **Escape de entidades XML**: se corrigieron errores de parseo XML (p. ej. `xmlParseEntityRef: no name`)
reemplazando `&` por `&amp;` en los dominios de las reglas cuando era necesario.
- **ACL temporal para triage de tests**: durante la depuración se añadió/ajustó una entrada mínima
en `security/ir.model.access.csv` (`access_group_order_base`) para permitir operaciones de prueba
(lectura/creación/edición según necesitaba el entorno de tests). Esta entrada se introdujo solo
para facilitar la ejecución de tests y validaciones locales; considerar revisarla antes de
publicar si se requiere endurecer los permisos.
- **Ejecuciones de tests**:
- Módulo `website_sale_aplicoop`: ejecución local completada — `63 tests`, **0 fallos** para este módulo.
- Ejecución completa del conjunto de tests de Odoo: `3583 tests` ejecutados en total;
**34 fallos** y **65 errores** (log completo disponible en `/tmp/test_output_full_run.log`).
- **Recomendaciones**:
- Si se desea completar la corrección de la suite completa, empezar triando las primeras
fallas del log (`grep -n "FAILED\|Traceback" /tmp/test_output_full_run.log | head -n 50`).
- Revisar la permanencia de `access_group_order_base` en `ir.model.access.csv` y ajustarla
para que los tests no hayan forzado permisos en producción.
- Mantener la regla que limita el acceso del portal a `product.supplierinfo` para no exponer
`res.partner` al portal; cualquier información adicional del proveedor debe inyectarse
desde los controladores de manera explícita y mínima.
Esta sección se añadió para dejar constancia de los cambios que afectan a la política de acceso
y a la ejecución de tests; actualizarla cuando se hagan revert/ajustes adicionales.

View file

@ -0,0 +1,51 @@
## Creating a Group Order
1. Go to **Website Sale > Group Orders > Create**
2. Fill in the order details:
- **Order Name**: Descriptive name (e.g., "Weekly Vegetable Order")
- **Start Date**: When the order opens for shopping (leave empty for orders that are always open)
- **End Date**: When the order closes (optional; leave empty for permanent orders)
- **Cutoff Day**: Day of week when purchases stop (0=Monday, 6=Sunday) - mandatory
- **Recurrence Period**: How often the order repeats (once, weekly, biweekly, monthly)
- **Suppliers**: Link to product suppliers (informational)
- **Products**: Products available in this order (REQUIRED - add via the "Products" tab)
- **Categories**: Product categories available in this order (informational)
- **Groups**: Which user groups can participate (REQUIRED - users from these groups can shop)
3. **Assign Products**: Click on the "Products" tab and add all products that should be available for this order
4. Click **Save** and transition the order to **Open** state to allow shopping
### Note on Start Date
- If **Start Date is empty**, the order is considered always open (ignoring date range checks)
- If **Start Date is set**, the order is only active starting from that date
- Use empty Start Date for orders that should always be available during their time window
## Shopping for a Group Order
1. Navigate to the website storefront at `/eskaera` (group orders page)
2. View active group orders for your participating groups
3. Select an order to view available products
4. Add products to your cart (separate cart per order)
5. At checkout, confirm your order to convert items to a sales order draft
6. Proceed through standard Odoo checkout workflow
## Configuration
### Managing Groups
1. Go to **Contacts > Groups** (res.partner with is_group=True)
2. Create groups for user communities
3. Add partners/users to groups via the **Members** tab
### Managing Products
1. Products are linked to group orders via the **Group Orders** field in product settings
2. Set pricing and availability per group order
3. Assign products to categories used in group orders
### Date & Time Validation
- `start_date` must be ≤ `end_date` (when both filled)
- Empty end_date = permanent order