194 lines
5.7 KiB
Python
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
|