#!/usr/bin/env python3 import os import time import sys import xmlrpc.client from pathlib import Path from datetime import datetime MACHINE_MAP = { "CORT-00": "Cortadora 0", "CORT-01": "Cortadora 01", "CORT-02": "Cortadora 02", } WATCH_STATES = ["waiting", "ready", "progress"] def read_env(path=".env"): env = {} p = Path(path) for raw in p.read_text().splitlines(): line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) env[k.strip()] = v.strip().strip('"').strip("'") return env def existing_fields(models, db, uid, password, model, wanted): fields = models.execute_kw( db, uid, password, model, "fields_get", [], {"attributes": ["string", "type"]}, ) return [f for f in wanted if f in fields] def search_read(models, db, uid, password, model, domain, fields, limit=50, order="id desc"): valid_fields = existing_fields(models, db, uid, password, model, fields) return models.execute_kw( db, uid, password, model, "search_read", [domain], { "fields": valid_fields, "limit": limit, "order": order, }, ) def m2o_name(value): if isinstance(value, list) and len(value) >= 2: return value[1] return None def connect(): env = read_env() url = env["ODOO_URL"].rstrip("/") db = os.getenv("ODOO_DB_OVERRIDE") or env["ODOO_DB"] user = env["ODOO_USER"] password = env["ODOO_PASSWORD"] common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common") uid = common.authenticate(db, user, password, {}) if not uid: raise RuntimeError("Autenticación Odoo fallida") models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object") return url, db, user, password, uid, models def print_state(models, db, uid, password): print() print("=" * 100) print(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) print("=" * 100) for machine_id, wc_name in MACHINE_MAP.items(): workcenters = search_read( models, db, uid, password, "mrp.workcenter", [("name", "=", wc_name)], ["id", "name", "active", "display_name"], limit=5, order="id asc", ) if not workcenters: print(f"{machine_id} | {wc_name} | ERROR: no existe centro de trabajo") continue wc = workcenters[0] wc_id = wc["id"] workorders = search_read( models, db, uid, password, "mrp.workorder", [ ("workcenter_id", "=", wc_id), ("state", "in", WATCH_STATES), ], [ "id", "name", "state", "workcenter_id", "production_id", "product_id", "date_start", "date_finished", "qty_production", ], limit=100, order="id desc", ) by_state = {s: [] for s in WATCH_STATES} for wo in workorders: by_state.setdefault(wo.get("state"), []).append(wo) progress = by_state.get("progress", []) ready = by_state.get("ready", []) waiting = by_state.get("waiting", []) if len(progress) == 1: beacon_state = "AZUL_FIJO_CANDIDATO" elif len(progress) > 1: beacon_state = "INCOHERENTE_MULTIPLES_PROGRESS" else: beacon_state = "SIN_ORDEN_CARGADA" print( f"{machine_id} | {wc_name} | " f"waiting={len(waiting)} ready={len(ready)} progress={len(progress)} | " f"{beacon_state}" ) for wo in progress: print( f" ACTIVE progress: " f"wo_id={wo.get('id')} " f"mo={m2o_name(wo.get('production_id'))} " f"product={m2o_name(wo.get('product_id'))} " f"date_start={wo.get('date_start')}" ) if not progress and ready: first = ready[0] print( f" primera ready: " f"wo_id={first.get('id')} " f"mo={m2o_name(first.get('production_id'))} " f"product={m2o_name(first.get('product_id'))}" ) def main(): watch = False interval = 2.0 if "--watch" in sys.argv: watch = True idx = sys.argv.index("--watch") if len(sys.argv) > idx + 1: interval = float(sys.argv[idx + 1]) url, db, user, password, uid, models = connect() print(f"ODOO_URL={url}") print(f"ODOO_DB={db}") print(f"ODOO_USER={user}") print(f"uid={uid}") if watch: while True: print_state(models, db, uid, password) time.sleep(interval) else: print_state(models, db, uid, password) if __name__ == "__main__": main()