Files
clientflow_backend/app/work_center_action_policy.py

194 lines
5.7 KiB
Python

"""Canonical work-center action precedence and reconstructed review state.
The operator must see one first safe action across the work center and the
opportunity board. This module intentionally contains no database access so
it can be reused by UI/view-model code and tested in isolation.
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Mapping
ASSOCIATION_ACTION_CODES = {
"ASSOCIATE_OPPORTUNITY",
"REVIEW_ASSOCIATION",
"LINK_DOCUMENT",
}
RECONSTRUCTED_SENSITIVE_ACTIONS = {
"SEND_PROFORMA",
"SEND_INVOICE",
"CONFIRM_PAYMENT",
"CONFIRM_PAYMENT_AND_PREPARE_SHIPMENT",
"PREPARE_ORDER",
"VALIDATE_PHYSICAL_ORDER",
"CREATE_SHIPMENT",
"SHIP_ORDER",
}
RECONSTRUCTED_RECORD_MODES = {
"reconstructed_invoice_review",
"historical_reconstructed",
"legacy_review",
}
ACTION_ALIASES = {"SHIP_ORDER": "CREATE_SHIPMENT"}
def _s(value: Any) -> str:
return str(value or "").strip()
def _lower(value: Any) -> str:
return _s(value).casefold()
def _upper(value: Any) -> str:
return _s(value).upper()
def _as_mapping(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, Mapping) else {}
def _truthy(value: Any) -> bool:
if value is True:
return True
return _lower(value) in {"1", "true", "yes", "sim", "required", "validated", "waived"}
def _explicit_false(value: Any) -> bool:
if value is False:
return True
return _lower(value) in {"0", "false", "no", "não", "nao", "cleared", "validated", "waived"}
def canonical_action_code(value: Any) -> str:
code = _upper(value)
return ACTION_ALIASES.get(code, code)
def is_reconstructed_record(metadata: Any) -> bool:
data = _as_mapping(metadata)
return _lower(data.get("clientflow_record_mode")) in RECONSTRUCTED_RECORD_MODES
def reconstructed_review_status(metadata: Any) -> str:
"""Return required/validated/waived/legacy_unset/not_applicable.
v132 persists ``reconstructed_review_status``. Legacy keys are still read
so upgrades do not re-open a review that was already completed.
"""
data = _as_mapping(metadata)
nested = _as_mapping(data.get("reconstructed_review"))
direct = _lower(nested.get("status") or data.get("reconstructed_review_status"))
if direct in {"required", "validated", "waived"}:
return direct
validated_keys = (
"reconstructed_process_validated",
"historical_evidence_validated",
"reconstructed_review_validated",
)
if any(_truthy(data.get(key)) for key in validated_keys):
return "validated"
required_keys = (
"historical_evidence_review_required",
"reconstructed_process_review_required",
"reconstructed_review_required",
)
if any(_truthy(data.get(key)) for key in required_keys):
return "required"
# Some older repair scripts cleared the boolean instead of writing a
# validated marker. Only interpret that as validated for a known legacy
# reconstructed record.
if is_reconstructed_record(data) and any(
key in data and _explicit_false(data.get(key)) for key in required_keys
):
return "validated"
return "legacy_unset" if is_reconstructed_record(data) else "not_applicable"
def reconstructed_review_required(metadata: Any) -> bool:
return reconstructed_review_status(metadata) == "required"
def reconstructed_review_cleared(metadata: Any) -> bool:
return reconstructed_review_status(metadata) in {"validated", "waived"}
def association_review_required(*, action_code: Any = "", linking_status: Any = "") -> bool:
code = canonical_action_code(action_code)
linking = _lower(linking_status)
return code in ASSOCIATION_ACTION_CODES or linking in {
"ambiguous",
"review_required",
"required",
"conflict",
}
def effective_action_code(
action_code: Any,
*,
metadata: Any = None,
linking_status: Any = "",
) -> str:
"""Return the first explicitly supported operator action.
Titles and free text are deliberately ignored. A blocker must be
persisted as an action/linking status or reconstructed review state.
"""
code = canonical_action_code(action_code)
if association_review_required(action_code=code, linking_status=linking_status):
return "ASSOCIATE_OPPORTUNITY"
if reconstructed_review_required(metadata) and code in {
canonical_action_code(item) for item in RECONSTRUCTED_SENSITIVE_ACTIONS
}:
return "REVIEW_RECONSTRUCTED_PROCESS"
return code
def reconstructed_review_metadata_patch(
status: str,
*,
actor: str,
reason: str = "",
blocked_action_code: str = "",
) -> dict[str, Any]:
normalized = _lower(status)
if normalized not in {"required", "validated", "waived"}:
raise ValueError(f"Unsupported reconstructed review status: {status}")
now = datetime.now(timezone.utc).isoformat()
nested: dict[str, Any] = {
"status": normalized,
"updated_at": now,
"updated_by": actor,
}
if reason:
nested["reason"] = reason
if blocked_action_code:
nested["blocked_action_code"] = canonical_action_code(blocked_action_code)
if normalized == "required":
nested["required_at"] = now
elif normalized == "validated":
nested["validated_at"] = now
nested["validated_by"] = actor
else:
nested["waived_at"] = now
nested["waived_by"] = actor
patch: dict[str, Any] = {
"reconstructed_review_status": normalized,
"reconstructed_review": nested,
"reconstructed_review_required": normalized == "required",
"reconstructed_review_validated": normalized == "validated",
}
return patch