360 lines
18 KiB
Markdown
360 lines
18 KiB
Markdown
# Reglas del Proyecto — Hazard (Odoo 16)
|
||
|
||
## Información General
|
||
- **Empresa:** Hazard (marca de ropa urbana / streetwear)
|
||
- **Plataforma ERP:** Odoo 16 Community con módulos Enterprise
|
||
- **Ecommerce:** Shopify (sincronizado con Odoo vía módulo custom `hazard_shopify`)
|
||
- **Servidor:** VPS en Plesk bajo el dominio `tesscorp.com.mx`
|
||
- **Idioma de comunicación:** Siempre responder en español
|
||
|
||
## Arquitectura Docker
|
||
|
||
### Contenedores
|
||
| Contenedor | Imagen | Puerto Externo | Puerto Interno |
|
||
|---|---|---|---|
|
||
| `odoo-16-hazard-new` | `hazard-odoo-odoo` (custom, basada en `odoo:16`) | `16001` | `8069` |
|
||
| `odoo-16-db-hazard-new` | `postgres:17` | No expuesto | `5432` |
|
||
|
||
### Volúmenes y Rutas
|
||
- **Módulos Enterprise:** `./Addons/enterprise` → `/mnt/enterprise` (dentro del contenedor)
|
||
- **Módulos Custom:** `./Addons/custom` → `/mnt/extra-addons` (dentro del contenedor)
|
||
- **Configuración Odoo:** `./Conf/odoo.conf` → `/etc/odoo/odoo.conf`
|
||
- **Docker Compose:** `/var/www/vhosts/tesscorp.com.mx/hazard.tesscorp.com.mx/docker-compose.yml`
|
||
- **IP interna del contenedor DB:** `172.18.0.2` (usada por MCP Server PostgreSQL)
|
||
|
||
### Base de Datos
|
||
- **Base de datos principal (producción):** `hazard_new`
|
||
- **Usuario DB:** `odoo`
|
||
- **Contraseña DB:** `odoo`
|
||
|
||
### Credenciales Odoo (XML-RPC)
|
||
- **URL:** `http://localhost:16001`
|
||
- **Base de datos:** `hazard_new`
|
||
- **Usuario admin:** `admin`
|
||
- **Contraseña admin:** `admin`
|
||
|
||
## Módulos Custom Instalados
|
||
| Módulo | Estado | Ubicación |
|
||
|---|---|---|
|
||
| `hazard_shopify` | ✅ Instalado | `Addons/custom/hazard_shopify` |
|
||
| `hazard_inventory_report` | ✅ Instalado | `Addons/custom/hazard_inventory_report` |
|
||
| `hazard_website` | ❌ No instalado | `Addons/custom/hazard_website` |
|
||
|
||
### Otros Módulos de Terceros
|
||
- `bi_sql_editor` — Editor SQL en interfaz Odoo
|
||
- `query_deluxe` — Consultas avanzadas desde Odoo
|
||
- `sql_request_abstract` — Abstracción para consultas SQL
|
||
- `l10n_mx_sat_sync_itadmin_ee` — Sincronización SAT México
|
||
|
||
## Reglas de Desarrollo Obligatorias
|
||
|
||
### Python
|
||
- **NUNCA** usar `print()`. Siempre usar `logging` con `_logger = logging.getLogger(__name__)`.
|
||
- Usar prefijos descriptivos en logs: `_logger.info("[Módulo] Mensaje")`.
|
||
- Siempre agregar docstrings a funciones y clases.
|
||
|
||
|
||
### Despliegue de Cambios
|
||
1. **Cambios en archivos `.py` (modelos/lógica):** Requiere restart del contenedor.
|
||
```bash
|
||
docker restart odoo-16-hazard-new
|
||
```
|
||
2. **Cambios en estructura de tablas (fields nuevos):** Requiere restart + actualización del módulo.
|
||
```bash
|
||
docker restart odoo-16-hazard-new
|
||
# Luego ejecutar el script de actualización:
|
||
python3 update_module.py
|
||
```
|
||
3. **Cambios solo en XML/vistas/reportes:** Solo requiere actualización del módulo (sin restart).
|
||
|
||
### Seguridad y Respaldos
|
||
- **NUNCA** ejecutar `UPDATE` o `DELETE` masivos sin respaldo previo.
|
||
- Script de respaldo: `backup_hazard.sh` (respalda DB + código, retención 15 días).
|
||
- Los backups se suben a Google Drive automáticamente vía Plesk.
|
||
|
||
### Código de Barras
|
||
- Formato EAN13 estandarizado con estructura `20000...` (ej. `2000010012749`).
|
||
- NUNCA crear scripts que alteren la secuencia de códigos de barras.
|
||
|
||
### Archivos que NO se deben modificar
|
||
- Módulos en `/mnt/enterprise` (módulos oficiales de Odoo Enterprise).
|
||
- Solo modificar código en `/mnt/extra-addons` (módulos custom).
|
||
|
||
## Scripts Útiles del Proyecto
|
||
| Script | Propósito | Comando |
|
||
|---|---|---|
|
||
| `backup_hazard.sh` | Respaldo completo (DB + código) | `bash backup_hazard.sh` |
|
||
| `update_module.py` | Actualizar módulo hazard_shopify via XML-RPC | `python3 update_module.py` |
|
||
|
||
## Conexión MCP Server PostgreSQL
|
||
El agente tiene acceso directo de solo lectura a la base de datos `hazard_new` a través del MCP Server configurado en `mcp_config.json`. Usar la herramienta `query` del servidor `odoo-postgres` para consultas SQL cuando sea necesario inspeccionar datos.
|
||
|
||
## Flujo Custom y Maquila (B2B)
|
||
- **Regla Crítica:** Los productos de las categorías `Insumos / Materia Prima` y `Producto Final Custom` pertenecen al flujo B2B/Maquila.
|
||
- Estos productos **NUNCA** deben enviarse ni sincronizarse con Shopify. Están tecnológicamente aislados de la plataforma de Retail.
|
||
- Cualquier script, sincronización o lógica de Odoo que interactúe con Shopify debe excluir explícitamente estas categorías.
|
||
|
||
---
|
||
|
||
## Arquitectura del Módulo `hazard_shopify`
|
||
|
||
### Modelos Custom (13 modelos)
|
||
|
||
| Modelo Técnico | Archivo | Descripción |
|
||
|---|---|---|
|
||
| `shopify.config` | `models/shopify_config.py` | Configuración central de la tienda Shopify. Contiene TODA la lógica de sincronización (~4500 líneas). |
|
||
| `shopify.sku.simulation.line` | `models/sku_simulation_line.py` | Líneas de simulación de SKU para mapeo Shopify ↔ Odoo. |
|
||
| `shopify.stock.queue` | `models/shopify_stock_queue.py` | Cola asíncrona para sincronización reactiva de inventario. |
|
||
| `shopify.cost.line` | `models/shopify_cost_line.py` | Líneas de sincronización de costos desde Shopify. |
|
||
| `shopify.publication` | `models/shopify_publication.py` | Canales de venta de Shopify (Online Store, POS, etc.). |
|
||
| `shopify.product.publication` | `models/shopify_product_publication.py` | Tabla pivote: estado de publicación de un producto por canal. |
|
||
| `shopify.location.mapping` | `models/shopify_config.py` (final) | Mapeo Sucursal Shopify ↔ Almacén/Ubicación Odoo. |
|
||
| `shopify.location.wizard` | `models/shopify_location_wizard.py` | Wizard para seleccionar sucursal de Shopify. |
|
||
| `shopify.refund.wizard` | `wizard/shopify_refund_wizard.py` | Asistente para reembolsos manuales hacia Shopify. |
|
||
|
||
### Modelos Heredados (overrides)
|
||
|
||
| Modelo | Archivo | Qué agrega |
|
||
|---|---|---|
|
||
| `product.template` | `models/product_template.py` | Campos `shopify_id`, `shopify_published`, `shopify_sync_status`, lógica de push a Shopify, canales de venta GraphQL, generación de barcodes. |
|
||
| `product.product` | `models/product_template.py` (L960+) | Campos **CRÍTICOS**: `shopify_variant_id` (llave de variante), `shopify_inventory_item_id` (llave de inventario). Auto-generación de EAN-13 al crear producto. |
|
||
| `sale.order` | `models/sale_order.py` | Campos `shopify_order_id`, `shopify_fulfillment_status`, `shopify_refund_status`, `shopify_shipping_title`. Lógica BOPIS, fulfillment, cancelación de pickings. |
|
||
| `stock.quant` | `models/stock_quant.py` | Override de `_update_available_quantity()` para encolar sincronización reactiva al cambiar stock. |
|
||
| `res.users` | `models/res_users.py` | Override de `_search()` para ocultar usuarios de soporte (`admin`, `@tesscorp.com.mx`). |
|
||
|
||
### Campos Shopify Críticos (NUNCA eliminar)
|
||
|
||
**En `product.product`:**
|
||
- `shopify_variant_id` — ID numérico de la variante en Shopify. Es la llave para push de SKU, barcodes y fulfillment.
|
||
- `shopify_inventory_item_id` — ID del ítem de inventario en Shopify. Es la llave para TODA la sincronización de stock.
|
||
|
||
**En `product.template`:**
|
||
- `shopify_id` — ID del producto (template) en Shopify.
|
||
- `shopify_published` — Flag booleano que indica si el producto existe en Shopify.
|
||
|
||
**En `sale.order`:**
|
||
- `shopify_order_id` — ID numérico del pedido en Shopify. Llave para duplicados y fulfillment.
|
||
- `shopify_shipping_title` — Título del método de envío. Se usa para detectar pedidos BOPIS (retiro en tienda).
|
||
|
||
---
|
||
|
||
## Flujos de Negocio del Conector
|
||
|
||
### Flujo 1: Importación de Pedidos (Shopify → Odoo)
|
||
```
|
||
Cron cada N min → cron_import_orders()
|
||
→ API REST: GET /orders.json?status=any&updated_at_min=...
|
||
→ Por cada pedido: _import_single_shopify_order(s_order)
|
||
→ Si ya existe: actualiza estado financiero, fulfillment, refunds, detecta ediciones
|
||
→ Si es nuevo: crea res.partner → crea sale.order → auto-confirma
|
||
→ Si POS o Fulfilled: auto-valida albarán de salida
|
||
→ Si tiene envío: agrega línea de producto ENVIO
|
||
→ Si pagado y auto_workflow_payment: publica factura + registra pago
|
||
→ Si fecha < cutoff: cancela albaranes (protección histórica)
|
||
→ Ejecuta _process_shopify_refunds_automation_logic()
|
||
```
|
||
|
||
**Reglas importantes:**
|
||
- El producto se busca por: 1° SKU (`default_code`), 2° `shopify_variant_id`, 3° nombre (`ilike`), 4° genérico `SHOPIFY-GENERIC`.
|
||
- Los impuestos se omiten (`tax_id: []`) porque Shopify ya los incluye en el precio.
|
||
- Los descuentos se calculan como porcentaje desde `discount_allocations`.
|
||
|
||
### Flujo 2: Sincronización de Inventario (Odoo → Shopify)
|
||
```
|
||
Odoo es la FUENTE DE VERDAD del stock.
|
||
|
||
Modo Reactivo (por evento):
|
||
stock.quant._update_available_quantity() → detecta cambio
|
||
→ Si producto tiene shopify_inventory_item_id → encola en shopify.stock.queue
|
||
→ Cron cada 5 min: process_queue_batch() → procesa 15 productos por lote
|
||
→ Llama a _sync_variants_stock_to_shopify()
|
||
|
||
Modo Cron (masivo periódico):
|
||
cron_sync_inventory_to_shopify() → todas las variantes con shopify_inventory_item_id
|
||
→ _sync_variants_stock_to_shopify(variants)
|
||
→ API REST: POST /inventory_levels/set.json por cada variante × ubicación
|
||
```
|
||
|
||
**Regla CRÍTICA:** Jamás invertir la dirección. El cron `cron_sync_inventory_from_shopify` está redirigido a `cron_sync_inventory_to_shopify` por seguridad operativa.
|
||
|
||
### Flujo 3: Fulfillment (Odoo → Shopify)
|
||
```
|
||
Desde el pedido en Odoo → botón "Enviar a Shopify"
|
||
→ action_notify_shopify_fulfillment()
|
||
→ Doble fallback:
|
||
1° Intento: Endpoint legacy POST /orders/{id}/fulfillments.json
|
||
2° Intento: Nuevo endpoint POST /fulfillments.json via fulfillment_orders
|
||
→ Escribe shopify_fulfillment_id y shopify_fulfillment_status en sale.order
|
||
```
|
||
|
||
### Flujo 4: BOPIS (Buy Online, Pickup In Store)
|
||
```
|
||
Detección automática por shopify_shipping_title:
|
||
Si contiene: OFICINA, RETIRO, PICKUP, SUCURSAL, TIENDA
|
||
→ Al confirmar pedido: _create_pickup_internal_transfer()
|
||
→ Crea traslado interno: Bodega ALMA → Oficina HRSGOLF
|
||
→ Reserva stock y alerta si hay faltantes
|
||
→ Al editar pedido en Shopify: _update_pickup_internal_transfer_qty()
|
||
→ Ajusta cantidades en traslados existentes o crea complementarios
|
||
```
|
||
|
||
### Flujo 5: Reembolsos y Devoluciones Automáticas
|
||
```
|
||
_process_shopify_refunds_automation_logic(sale_order, s_order)
|
||
→ Candado Sandbox: Si shopify_sandbox_mode=True, solo procesa SKUs con "PRUEBA"
|
||
→ Por cada refund no procesado:
|
||
A. Devolución de Stock: Crea stock.picking de entrada + auto-valida
|
||
B. Nota de Crédito: Crea account.move tipo out_refund en borrador
|
||
→ Registra refund_id en shopify_refund_ids_processed para idempotencia
|
||
```
|
||
|
||
**ADVERTENCIA:** Este flujo ya opera en producción. Cambios aquí impactan contabilidad real y stock físico.
|
||
|
||
### Flujo 6: Publicación de Productos (Odoo → Shopify)
|
||
```
|
||
Desde ficha de producto → botón "Publicar en Shopify"
|
||
→ action_push_to_shopify()
|
||
→ Si shopify_id existe: PUT /products/{id}.json (actualización)
|
||
→ Si no existe: POST /products.json (creación)
|
||
→ Sincroniza canales de venta vía API GraphQL (publishablePublish mutation)
|
||
→ Sube imagen si tiene (POST /products/{id}/images.json)
|
||
```
|
||
|
||
---
|
||
|
||
## Sistema de Crons (7 Acciones Planificadas)
|
||
|
||
| # | XML ID | Propósito | Frecuencia Default | Activo | Método |
|
||
|---|---|---|---|---|---|
|
||
| 1 | `ir_cron_shopify_import_orders` | Importar pedidos Shopify → Odoo | 5 min | ✅ | `cron_import_orders()` |
|
||
| 2 | `ir_cron_shopify_sync_inventory` | Stock Odoo → Shopify (masivo) | 12 horas | ❌ | `cron_sync_inventory_to_shopify()` |
|
||
| 3 | `ir_cron_shopify_refresh_tokens` | Renovar tokens OAuth (24h Shopify) | 20 horas | ✅ | `cron_refresh_tokens()` |
|
||
| 4 | `ir_cron_shopify_download_images` | Descargar imágenes (manual/batch) | 1 min | ❌ | `_cron_download_images_batch()` |
|
||
| 5 | `ir_cron_shopify_auto_image_sync` | Auto-descargar imágenes nuevas | 2 horas | ✅ | `cron_auto_download_images()` |
|
||
| 6 | `ir_cron_shopify_process_stock_queue` | Procesar cola de stock reactiva | 5 min | ✅ | `process_queue_batch()` |
|
||
| 7 | `ir_cron_shopify_clean_stock_queue` | Limpiar cola completada (+7 días) | 1 día | ✅ | `clean_done_queue()` |
|
||
|
||
**Reglas sobre Crons:**
|
||
- Los crons 1, 2 y 5 se actualizan dinámicamente desde el `write()` de `shopify.config` cuando el usuario cambia la frecuencia en la interfaz.
|
||
- **NUNCA** desactivar manualmente los crons 3 (tokens) o 6 (cola de stock) — rompe la operación.
|
||
- El cron 4 se activa/desactiva automáticamente (botón → se activa, termina → se desactiva).
|
||
|
||
---
|
||
|
||
## API de Shopify
|
||
|
||
### Configuración Técnica
|
||
- **Versión API REST:** `2024-01`
|
||
- **Versión API GraphQL:** `2024-01`
|
||
- **Autenticación:** Token de acceso (`shpat_...`) en header `X-Shopify-Access-Token`
|
||
- **Renovación de Token:** OAuth con `client_id` y `client_secret`, renovación automática cada 20h vía cron.
|
||
- **Rate Limiting:** `time.sleep(0.5)` entre requests (2 req/s). Reintentos automáticos en HTTP 429 (espera 5s, 3 intentos).
|
||
|
||
### Endpoints Usados
|
||
| Método | Endpoint | Propósito |
|
||
|---|---|---|
|
||
| GET | `/orders.json` | Importar pedidos |
|
||
| GET | `/products.json` | Importar catálogo |
|
||
| POST/PUT | `/products/{id}.json` | Crear/actualizar productos |
|
||
| POST | `/products/{id}/images.json` | Subir imágenes |
|
||
| POST | `/inventory_levels/set.json` | Sincronizar stock |
|
||
| GET | `/locations.json` | Obtener sucursales |
|
||
| POST | `/orders/{id}/fulfillments.json` | Fulfillment legacy |
|
||
| GET | `/orders/{id}/fulfillment_orders.json` | Fulfillment orders (nuevo) |
|
||
| POST | `/fulfillments.json` | Fulfillment nuevo API |
|
||
| POST | `/orders/{id}/refunds/calculate.json` | Calcular reembolso |
|
||
| POST | `/orders/{id}/refunds.json` | Ejecutar reembolso |
|
||
| PUT | `/variants/{id}.json` | Actualizar variante (SKU, barcode) |
|
||
| GET | `/inventory_levels.json` | Consultar niveles de stock |
|
||
| POST (GraphQL) | `/graphql.json` | Costos, canales de venta, publicaciones |
|
||
|
||
---
|
||
|
||
## Grupos de Seguridad del Módulo
|
||
|
||
Definidos en `security/hazard_security.xml`. Categoría: `Hazard / HRSGOLF`.
|
||
|
||
| XML ID | Nombre | Permisos | Hereda de |
|
||
|---|---|---|---|
|
||
| `group_hazard_designer` | Diseño | Crea productos, sube fotos. Sin precios ni Shopify. | `base.group_user` |
|
||
| `group_hazard_warehouse` | Bodega | Stock y tracking. Sin precios. | `stock.group_stock_user` |
|
||
| `group_hazard_sales` | Ventas B2B | Cotizaciones/pedidos. Precios solo lectura. | `sales_team.group_sale_salesman` |
|
||
| `group_hazard_finance` | Admin/Finanzas | Control total: precios, catálogo, Shopify. | `sales_team.group_sale_salesman_all_leads` |
|
||
|
||
**Regla:** Solo `group_hazard_finance` tiene acceso de escritura a `shopify.config`. Los demás tienen solo lectura o sin acceso.
|
||
|
||
---
|
||
|
||
## Cola Asíncrona de Stock (`shopify.stock.queue`)
|
||
|
||
Mecanismo reactivo para sincronizar inventario en tiempo semi-real:
|
||
|
||
1. **Trigger:** `stock.quant._update_available_quantity()` detecta cualquier cambio de stock en Odoo.
|
||
2. **Filtro:** Solo encola productos con `shopify_inventory_item_id` (vinculados a Shopify).
|
||
3. **Encolado:** Crea registro en `shopify.stock.queue` con estado `pending` (evita duplicados).
|
||
4. **Procesamiento:** Cron cada 5 min → `process_queue_batch()` → procesa 15 productos por lote.
|
||
5. **Limpieza:** Cron diario → `clean_done_queue()` → elimina registros `done` con más de 7 días.
|
||
|
||
**Condición:** Solo procesa si `auto_sync_products = True` en la configuración de Shopify.
|
||
|
||
---
|
||
|
||
## Lógica de SKU (`sku_logic.py`)
|
||
|
||
Formato estándar: `HRS-{TIPO}-{MODELO}-{GENERO}-{COLOR}-{TALLA}`
|
||
|
||
Ejemplo: `HRS-POL-CLUB-M-BLK-L` = Polo Club, Hombre, Negro, Talla L
|
||
|
||
### Diccionarios de Mapeo (hardcoded)
|
||
- **CATEGORIES:** `hat→HAT`, `polo→POL`, `hoodie→HOD`, `bag→BAG`, `glove→GLV`, `towel→TWL`, etc.
|
||
- **GENDERS:** `men→M`, `women→W`, `kids→K`. Default `U` (unisex) para accesorios.
|
||
- **COLORS:** `black→BLK`, `white→WHT`, `green→GRN`, `blue→BLU`, `red→RED`, etc.
|
||
|
||
**Regla:** NUNCA modificar estos diccionarios sin aprobación explícita del cliente. Un cambio rompe la consistencia de los SKUs existentes.
|
||
|
||
---
|
||
|
||
## Reglas de Seguridad Adicionales
|
||
|
||
### Override de Usuarios (`res_users.py`)
|
||
- El modelo `res.users` oculta los usuarios de soporte (`admin` y `@tesscorp.com.mx`) de los usuarios del cliente.
|
||
- **NUNCA eliminar este override.** Los usuarios del cliente no deben ver las cuentas de desarrollo.
|
||
|
||
### Sandbox de Reembolsos
|
||
- El campo `shopify_sandbox_mode` en `shopify.config` protege la base de datos de producción.
|
||
- Cuando está activo (por defecto), la automatización de reembolsos solo procesa pedidos con SKUs que contienen la palabra `PRUEBA`.
|
||
- **NUNCA desactivar este modo sin autorización explícita y pruebas exhaustivas.**
|
||
|
||
### Producto Genérico de Envío
|
||
- El producto con SKU `ENVIO` (tipo servicio) se usa para las líneas de costo de envío importadas de Shopify.
|
||
- Se crea automáticamente si no existe. **No eliminar.**
|
||
|
||
### Cliente Genérico POS
|
||
- El contacto `Cliente POS Shopify` se reutiliza para ventas POS sin datos de cliente.
|
||
- **No eliminar ni renombrar.** Evita crear contactos basura por cada venta POS.
|
||
|
||
### Log Persistente de Crons
|
||
- Ruta dentro del contenedor: `/var/lib/odoo/shopify_cron.log`
|
||
- Volumen Docker: `odoo-web-data-hazard-new`
|
||
- Máximo 5 MB con rotación automática.
|
||
- Para consultar: `docker exec odoo-16-hazard-new cat /var/lib/odoo/shopify_cron.log`
|
||
|
||
### Fecha de Corte de Importación
|
||
- El campo `shopify_import_cutoff_date` en `shopify.config` protege el inventario de pedidos históricos.
|
||
- Pedidos creados ANTES de esta fecha se importan pero sus albaranes de salida se cancelan automáticamente.
|
||
- **No modificar esta fecha sin entender el impacto en el stock.**
|
||
|
||
---
|
||
|
||
## Autorización de Borrado
|
||
- NUNCA elimines archivos, directorios o recursos proactivamente sin la autorización explícita y directa del usuario.
|
||
- Si el usuario pregunta "¿Puedo borrar esto?", "¿Es seguro borrar esto?" o similar, limítate EXCLUSIVAMENTE a responder a su pregunta proporcionando la información.
|
||
- NO asumas que el usuario quiere que tú ejecutes el comando de borrado. Espera a que el usuario diga explícitamente "Bórralo", "Por favor elimínalo tú", o que ejecute la acción él mismo.
|
||
|
||
## Limpieza y Scripts Temporales
|
||
- NUNCA crees scripts temporales de Python (o bash) directamente en la carpeta raíz del proyecto.
|
||
- Si necesitas crear un script (`.py`, `.sh`, etc.) para una tarea, colócalo SIEMPRE dentro del directorio `scripts/`. Crea este directorio si no existe.
|
||
|
||
## Ejecución y Cambios de Código
|
||
- NUNCA apliques cambios en el código ni ejecutes comandos que modifiquen el entorno sin antes consultar y recibir la autorización explícita ("Adelante", "Ok", "Dale", etc.) del usuario. Debes plantear la solución primero y esperar aprobación.
|