Files

18 KiB
Raw Permalink Blame History

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.
    docker restart odoo-16-hazard-new
    
  2. Cambios en estructura de tablas (fields nuevos): Requiere restart + actualización del módulo.
    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.