Files
clientflow_backend/tests/test_v4928_1_5_132_operational_coherence.py

194 lines
7.9 KiB
Python

from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
import sys
from app.work_center_action_policy import (
effective_action_code,
reconstructed_review_status,
)
def _ensure_real_sqlalchemy():
module = sys.modules.get("sqlalchemy")
if module is not None and not hasattr(module, "__path__"):
for key in list(sys.modules):
if key == "sqlalchemy" or key.startswith("sqlalchemy."):
sys.modules.pop(key, None)
def _normalise(items):
_ensure_real_sqlalchemy()
from app.operations_service import _normalise_work_item_intent
return _normalise_work_item_intent(items)
def _physical_status(sale, pickings, productions):
_ensure_real_sqlalchemy()
from app.odoo_service import _derive_physical_status
return _derive_physical_status(sale, pickings, productions)
ROOT = Path(__file__).resolve().parents[1]
def _load_script():
_ensure_real_sqlalchemy()
path = ROOT / "scripts" / "apply_v132_operational_coherence.py"
spec = spec_from_file_location("apply_v132_operational_coherence", path)
module = module_from_spec(spec)
assert spec and spec.loader
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def test_title_sem_oportunidade_does_not_invent_association_blocker():
item = {
"action_code": "FOLLOW_UP_PAYMENT",
"opportunity_title": "Orçamento Jasmin sem oportunidade · ORC.1",
"opportunity_linking_status": "",
"opportunity_metadata": {},
}
result = _normalise([item])[0]
assert result["action_code"] == "FOLLOW_UP_PAYMENT"
def test_explicit_association_blocker_still_wins():
item = {
"action_code": "FOLLOW_UP_PAYMENT",
"opportunity_linking_status": "ambiguous",
"opportunity_metadata": {},
}
result = _normalise([item])[0]
assert result["action_code"] == "ASSOCIATE_OPPORTUNITY"
def test_explicit_reconstructed_review_blocks_physical_validation():
metadata = {
"clientflow_record_mode": "reconstructed_invoice_review",
"reconstructed_review_status": "required",
}
item = {
"action_code": "VALIDATE_PHYSICAL_ORDER",
"opportunity_metadata": metadata,
}
result = _normalise([item])[0]
assert result["action_code"] == "REVIEW_RECONSTRUCTED_PROCESS"
assert effective_action_code("VALIDATE_PHYSICAL_ORDER", metadata=metadata) == "REVIEW_RECONSTRUCTED_PROCESS"
def test_validated_reconstructed_review_does_not_reopen_from_title():
metadata = {
"clientflow_record_mode": "reconstructed_invoice_review",
"reconstructed_review_status": "validated",
}
item = {
"action_code": "SEND_INVOICE",
"opportunity_title": "Processo reconstruído · Cliente",
"opportunity_metadata": metadata,
}
result = _normalise([item])[0]
assert result["action_code"] == "SEND_INVOICE"
assert reconstructed_review_status(metadata) == "validated"
def test_board_prefers_pending_task_and_explicit_review():
source = (ROOT / "app" / "admin_ui" / "pages" / "opportunities.py").read_text(encoding="utf-8")
function = source[source.index("def _opportunity_card_next_action"):source.index("def _is_noise_opportunity")]
assert function.index("reconstructed_review_required(metadata)") < function.index("central = _central_next_action_for_card")
assert function.index("pending_primary_action_code") < function.index("central = _central_next_action_for_card")
assert 'return "Validar processo reconstruído"' in function
def test_odoo_assigned_is_reserved_not_ready_to_ship():
result = _physical_status(
{"state": "sale"},
[{"name": "WH/OUT/1", "state": "assigned", "picking_type_id": [2, "Delivery Orders"]}],
[],
)
assert result["physical_status"] == "picking_assigned"
assert result["stage"] == "ORDER_PREPARATION"
assert result["ready_to_ship"] is False
assert result["picking_reserved"] is True
def test_migration_plan_rewinds_assigned_and_requires_explicit_review():
module = _load_script()
opportunities = [{
"id": "opp-1",
"title": "Fwd cliente",
"customer_name": "NOLTIA SYSTEM, LDA",
"fiscal_name": "NOLTIA SYSTEM, LDA",
"stage": "SHIPMENT_CREATED",
"last_action_code": "VALIDATE_PHYSICAL_ORDER",
"metadata": {"clientflow_record_mode": "reconstructed_invoice_review"},
}]
links = [{
"opportunity_id": "opp-1", "system": "odoo", "external_type": "physical_status",
"status": "picking_assigned", "payload": {"physical_status": "picking_assigned", "pickings": [{"state": "assigned"}]},
}]
tasks = [{
"id": "task-1", "opportunity_id": "opp-1", "action_code": "VALIDATE_PHYSICAL_ORDER",
"status": "pending", "metadata": {},
}]
plans = module._plan_opportunities(opportunities, links, tasks, [])
assert len(plans) == 1
plan = plans[0]
assert plan.safe is True
assert plan.target_stage == "ORDER_PREPARATION"
assert plan.reconstructed_after == "required"
assert plan.task_operation == "convert_sensitive_to_review"
def test_completed_review_is_persisted_as_validated_not_reopened():
module = _load_script()
opportunities = [{
"id": "opp-2", "title": "Processo reconstruído", "customer_name": "RICARDO",
"fiscal_name": "RICARDO", "stage": "PAYMENT_CONFIRMED", "last_action_code": "SEND_INVOICE",
"metadata": {"clientflow_record_mode": "reconstructed_invoice_review", "reconstructed_review_required": True},
}]
tasks = [
{"id": "review-done", "opportunity_id": "opp-2", "action_code": "REVIEW_RECONSTRUCTED_PROCESS", "status": "done", "metadata": {}},
{"id": "invoice", "opportunity_id": "opp-2", "action_code": "SEND_INVOICE", "status": "pending", "metadata": {}},
]
plans = module._plan_opportunities(opportunities, [], tasks, [])
assert len(plans) == 1
assert plans[0].reconstructed_after == "validated"
assert plans[0].task_operation == "none"
def test_stale_candidate_requires_identity_unless_name_or_nif_matches():
module = _load_script()
items = [
{"id": "i1", "external_id": "318", "document_number": "S00318", "customer_name": "DUNAS INVENCÍVEIS, LDA", "customer_tax_id": "", "payload": {}},
{"id": "i2", "external_id": "323", "document_number": "S00323", "customer_name": "BBKW, LDA", "customer_tax_id": "", "payload": {}},
]
links = [
{"opportunity_id": "o1", "external_id": "318", "external_name": "S00318"},
{"opportunity_id": "o2", "external_id": None, "external_name": "S00323"},
]
opportunities = {
"o1": {"fiscal_name": "DUNAS INVENCÍVEIS, UNIPESSOAL, LDA", "customer_name": "", "title": "", "fiscal_tax_id": ""},
"o2": {"fiscal_name": "", "customer_name": "", "title": "", "fiscal_tax_id": ""},
}
plans = module._plan_candidates(items, links, opportunities, allow_unknown_identity=False)
by_ref = {plan.reference: plan for plan in plans}
assert by_ref["S00318"].safe is True
assert by_ref["S00323"].safe is False
assert by_ref["S00323"].reason == "identity_unknown_requires_review"
def test_reconciliation_no_longer_creates_validation_from_delivery_ready():
source = (ROOT / "app" / "reconciliation_service.py").read_text(encoding="utf-8")
assert 'if fulfilment.get("delivery_done"):' in source
assert 'if fulfilment.get("delivery_done") or fulfilment.get("delivery_ready")' not in source
assert "DELETE FROM operation_links" in source
def test_review_action_is_catalogued_and_completion_persists_validation():
catalog = (ROOT / "app" / "action_catalog.py").read_text(encoding="utf-8")
service = (ROOT / "app" / "opportunity_service.py").read_text(encoding="utf-8")
assert '"REVIEW_RECONSTRUCTED_PROCESS": {' in catalog
assert 'if action_code == "REVIEW_RECONSTRUCTED_PROCESS":' in service
assert 'reconstructed_process_review_validated' in service