40-clients/ucepsa/edge-oee-demo/tools/watch_odoo_active_workorders.py
2026-06-25 18:51:33 +02:00

189 lines
4.9 KiB
Python
Executable File

#!/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()