🎉 Initial commit - Hazard Odoo 16 project

- Custom modules: hazard_shopify, hazard_inventory_report, hazard_website
- Third-party modules: bi_sql_editor, query_deluxe, sql_request_abstract, l10n_mx_sat_sync_itadmin_ee
- Docker setup: Odoo 16 + PostgreSQL 17
- Utility scripts: backup_hazard.sh, update_module.py
- Agent configuration: .agents/AGENTS.md
- Security: Enterprise modules, credentials, backups and production data excluded via .gitignore
This commit is contained in:
2026-06-30 06:12:35 -06:00
commit 28d9e56f47
350 changed files with 99832 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""
Script de corrección puntual para el pedido S00651.
====================================================
PROBLEMA: El pedido S00651 fue editado en Shopify (GRIS TALAVERA FMG HAT: 5 → 6).
La orden de venta y la orden de entrega se actualizaron, pero el traslado interno
ALMA/INT/00083 ya estaba validado con 5 unidades.
SOLUCIÓN: Crear un traslado interno complementario de +1 unidad del ALMACÉN → OFICINA.
USO:
python3 fix_transfer_s00651.py --dry-run # Solo muestra lo que haría
python3 fix_transfer_s00651.py --apply # Crea el traslado complementario
"""
import xmlrpc.client
import sys
import logging
from datetime import datetime
# ─── Configuración ────────────────────────────────────────────────────────
ODOO_URL = 'http://localhost:16001'
ODOO_DB = 'hazard_new'
ODOO_USER = 'admin'
ODOO_PASS = 'admin'
# ─── Datos del pedido afectado ────────────────────────────────────────────
SALE_ORDER_NAME = 'S00651'
PRODUCT_SKU = 'HRS-HAT-FMG-U-GRY-OS' # GRIS TALAVERA FMG HAT
QTY_FALTANTE = 1 # Diferencia: 6 - 5 = 1
# ─── Logging ──────────────────────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger(__name__)
def connect():
common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
uid = common.authenticate(ODOO_DB, ODOO_USER, ODOO_PASS, {})
if not uid:
raise ConnectionError("No se pudo autenticar con Odoo")
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')
logger.info("Conectado a Odoo (uid=%s)", uid)
return uid, models
def main():
if len(sys.argv) < 2 or sys.argv[1] not in ('--dry-run', '--apply'):
print(__doc__)
sys.exit(1)
mode = sys.argv[1]
uid, models = connect()
# 1. Buscar el pedido S00651
sale_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'sale.order', 'search', [[('name', '=', SALE_ORDER_NAME)]])
if not sale_ids:
logger.error("No se encontró el pedido %s", SALE_ORDER_NAME)
sys.exit(1)
sale_id = sale_ids[0]
sale_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'sale.order', 'read', [sale_id],
{'fields': ['name', 'state', 'warehouse_id']})
sale = sale_records[0]
logger.info("Pedido encontrado: %s (ID=%s, Estado=%s)", sale['name'], sale_id, sale['state'])
# 2. Buscar el producto GRIS TALAVERA
product_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'product.product', 'search', [[('default_code', '=', PRODUCT_SKU)]])
if not product_ids:
logger.error("No se encontró el producto con SKU %s", PRODUCT_SKU)
sys.exit(1)
product_id = product_ids[0]
product_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'product.product', 'read', [product_id],
{'fields': ['name', 'display_name', 'default_code']})
product = product_records[0]
logger.info("Producto encontrado: %s (ID=%s)", product['display_name'], product_id)
# 3. Buscar el traslado interno original (ALMA/INT/00083)
internal_move_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.move', 'search', [[
'|', ('picking_id.sale_id', '=', sale_id), ('picking_id.origin', '=', SALE_ORDER_NAME),
('picking_id.picking_type_id.code', '=', 'internal'),
('product_id', '=', product_id),
('state', '=', 'done'),
]])
if not internal_move_ids:
logger.error("No se encontró movimiento interno completado para este producto en este pedido")
sys.exit(1)
move_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.move', 'read', [internal_move_ids[0]],
{'fields': ['picking_id', 'product_uom_qty', 'product_uom', 'location_id', 'location_dest_id', 'warehouse_id']})
move_data = move_records[0]
picking_id = move_data['picking_id'][0]
picking_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'read', [picking_id],
{'fields': ['name', 'state', 'picking_type_id', 'location_id', 'location_dest_id']})
picking = picking_records[0]
logger.info("Traslado original: %s (Estado=%s, Cantidad=%s)", picking['name'], picking['state'], move_data['product_uom_qty'])
# 4. Verificar la orden de entrega
delivery_move_ids = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.move', 'search', [[
'|', ('picking_id.sale_id', '=', sale_id), ('picking_id.origin', '=', SALE_ORDER_NAME),
('picking_id.picking_type_id.code', '=', 'outgoing'),
('product_id', '=', product_id),
('state', '!=', 'cancel'),
]])
if delivery_move_ids:
del_move_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.move', 'read', [delivery_move_ids[0]],
{'fields': ['product_uom_qty', 'picking_id']})
del_move = del_move_records[0]
del_picking_records = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'read', [del_move['picking_id'][0]],
{'fields': ['name', 'state']})
del_picking = del_picking_records[0]
logger.info("Orden de entrega: %s (Estado=%s, Demanda=%s)", del_picking['name'], del_picking['state'], del_move['product_uom_qty'])
logger.info("")
logger.info("═══════════════════════════════════════════════════")
logger.info(" RESUMEN DE CORRECCIÓN")
logger.info("═══════════════════════════════════════════════════")
logger.info(" Pedido: %s", SALE_ORDER_NAME)
logger.info(" Producto: %s (%s)", product['display_name'], PRODUCT_SKU)
logger.info(" Traslado: %s (ya completado con %s unidades)", picking['name'], int(move_data['product_uom_qty']))
logger.info(" Faltante: +%s unidad(es)", QTY_FALTANTE)
logger.info(" Acción: Crear traslado complementario ALMA → OFICINA")
logger.info("═══════════════════════════════════════════════════")
if mode == '--dry-run':
logger.info("")
logger.info(" 🔍 DRY RUN - No se modificó nada")
logger.info(" Para aplicar ejecutar: python3 fix_transfer_s00651.py --apply")
return
# 5. Crear traslado complementario
logger.info("")
logger.info("Creando traslado complementario...")
new_picking_id = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'create', [{
'picking_type_id': picking['picking_type_id'][0],
'location_id': picking['location_id'][0],
'location_dest_id': picking['location_dest_id'][0],
'origin': SALE_ORDER_NAME,
'sale_id': sale_id,
'move_type': 'direct',
}])
new_move_id = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.move', 'create', [{
'name': f"[Complemento S00651] {product['display_name']}",
'product_id': product_id,
'product_uom_qty': QTY_FALTANTE,
'product_uom': move_data['product_uom'][0],
'picking_id': new_picking_id,
'location_id': picking['location_id'][0],
'location_dest_id': picking['location_dest_id'][0],
'warehouse_id': move_data['warehouse_id'][0] if move_data['warehouse_id'] else False,
}])
# Confirmar y reservar
models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'action_confirm', [[new_picking_id]])
models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'action_assign', [[new_picking_id]])
# Leer el nombre del nuevo traslado
new_picking = models.execute_kw(ODOO_DB, uid, ODOO_PASS,
'stock.picking', 'read', [new_picking_id],
{'fields': ['name', 'state']})
logger.info("")
logger.info("✅ Traslado complementario creado: %s (Estado: %s)", new_picking['name'], new_picking['state'])
logger.info(" Producto: %s", product['display_name'])
logger.info(" Cantidad: +%s unidad(es)", QTY_FALTANTE)
logger.info(" Ruta: ALMA/Stock → OFFIC/Existencias")
logger.info("")
logger.info("⚡ SIGUIENTE PASO: Notifica a almacén para que valide el traslado %s", new_picking['name'])
if __name__ == '__main__':
main()