chore: clean up historical scripts, add MCP configurations, update agent rules
This commit is contained in:
+268
-1
@@ -52,7 +52,7 @@
|
||||
- **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.
|
||||
- Preferir código legible sobre código compacto.
|
||||
|
||||
|
||||
### Despliegue de Cambios
|
||||
1. **Cambios en archivos `.py` (modelos/lógica):** Requiere restart del contenedor.
|
||||
@@ -88,3 +88,270 @@
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user