85 lines
3.7 KiB
Python
85 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
from uuid import uuid4
|
|
|
|
from app.core.exceptions import DomainError
|
|
from app.modules.sync.engine import SyncEngine
|
|
from app.modules.sync.schemas import ChangeEventIn
|
|
from app.modules.zootech.catalog_apply import load_catalog_row
|
|
from app.modules.zootech.sync_content_hash import compute_content_hash
|
|
|
|
|
|
class ZootechServiceError(DomainError):
|
|
pass
|
|
|
|
|
|
def _emit_change(enterprise_id: str, table: str, record_id: str, payload: dict, version: int, content_hash: str) -> None:
|
|
event = ChangeEventIn(
|
|
event_id=str(uuid4()),
|
|
seq=None,
|
|
domain="global",
|
|
table=table,
|
|
record_id=record_id,
|
|
action="upsert",
|
|
version=version,
|
|
content_hash=content_hash,
|
|
payload=payload,
|
|
emitted_at=datetime.now(UTC),
|
|
origin_site_id=SyncEngine.ORCHESTRATOR_SITE_ID,
|
|
)
|
|
SyncEngine(enterprise_id, SyncEngine.ORCHESTRATOR_SITE_ID).process_push([event], origin="orchestrator")
|
|
|
|
|
|
def upsert_component(enterprise_id: str, record_id: str | None, body: dict) -> dict:
|
|
rid = record_id or str(uuid4())
|
|
existing = load_catalog_row(enterprise_id, "component", rid)
|
|
version = int(body.get("version") or (existing or {}).get("version") or 0) + 1 if existing else 1
|
|
payload = {
|
|
"id": rid,
|
|
"name": body.get("name", (existing or {}).get("name", "")),
|
|
"type": body.get("type", (existing or {}).get("type", "")),
|
|
"is_active": body.get("is_active", (existing or {}).get("is_active", True)),
|
|
"dry_matter": float(body.get("dry_matter", (existing or {}).get("dry_matter", 0.0))),
|
|
"protein": float(body.get("protein", (existing or {}).get("protein", 0.0))),
|
|
"energy": float(body.get("energy", (existing or {}).get("energy", 0.0))),
|
|
"price": float(body.get("price", (existing or {}).get("price", 0.0))),
|
|
"external_no": body.get("external_no", (existing or {}).get("external_no")),
|
|
"version": version,
|
|
}
|
|
content_hash = compute_content_hash(payload)
|
|
payload["content_hash"] = content_hash
|
|
_emit_change(enterprise_id, "component", rid, payload, version, content_hash)
|
|
return load_catalog_row(enterprise_id, "component", rid) or payload
|
|
|
|
|
|
def patch_component(enterprise_id: str, record_id: str, body: dict) -> dict:
|
|
existing = load_catalog_row(enterprise_id, "component", record_id)
|
|
if not existing:
|
|
raise ZootechServiceError("NOT_FOUND")
|
|
merged = {**existing, **body}
|
|
return upsert_component(enterprise_id, record_id, merged)
|
|
|
|
|
|
def upsert_recipe(enterprise_id: str, record_id: str | None, body: dict) -> dict:
|
|
rid = record_id or str(uuid4())
|
|
existing = load_catalog_row(enterprise_id, "recipe", rid)
|
|
version = int(body.get("version") or (existing or {}).get("version") or 0) + 1 if existing else 1
|
|
payload = {
|
|
"id": rid,
|
|
"name": body.get("name", (existing or {}).get("name", "")),
|
|
"heads_per_trip": int(body.get("heads_per_trip", (existing or {}).get("heads_per_trip", 1))),
|
|
"mixing_time": int(body.get("mixing_time", (existing or {}).get("mixing_time", 0))),
|
|
"trip_percent": float(body.get("trip_percent", (existing or {}).get("trip_percent", 100.0))),
|
|
"dry_matter_locked": body.get("dry_matter_locked", (existing or {}).get("dry_matter_locked", False)),
|
|
"version": version,
|
|
}
|
|
if "ingredients" in body:
|
|
payload["ingredients"] = body["ingredients"]
|
|
elif existing and "ingredients" in existing:
|
|
payload["ingredients"] = existing["ingredients"]
|
|
content_hash = compute_content_hash(payload)
|
|
payload["content_hash"] = content_hash
|
|
_emit_change(enterprise_id, "recipe", rid, payload, version, content_hash)
|
|
return load_catalog_row(enterprise_id, "recipe", rid) or payload
|