Files

360 lines
18 KiB
Markdown
Raw Permalink 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.
# 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.