From 7e521a377e8c129c17ce2e1a22be6dea114070f7 Mon Sep 17 00:00:00 2001 From: plx Date: Tue, 18 Aug 2026 14:47:13 +0000 Subject: [PATCH] fix(opportunity-flow): wait after sent quote until payment evidence --- app/domain/opportunity_flow/rules.py | 28 ++++++++++++---- ..._v4928_1_5_60_company_workflow_profiles.py | 33 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/app/domain/opportunity_flow/rules.py b/app/domain/opportunity_flow/rules.py index 9d3aec8..e11151d 100644 --- a/app/domain/opportunity_flow/rules.py +++ b/app/domain/opportunity_flow/rules.py @@ -333,18 +333,34 @@ def decide_blif_next_action(e: OpportunityEvidence, profile: CompanyWorkflowProf ) # Default/BLIF normal sequence: budget document, payment, invoice, then preparation/shipping. - if e.payment_terms in {PAYMENT_BEFORE_SHIPPING, "", "undefined", "agreement"} and e.has_quote and not e.payment_confirmed: + if ( + e.payment_terms in {PAYMENT_BEFORE_SHIPPING, "", "undefined", "agreement"} + and e.has_quote + and e.quote_sent + and not e.has_invoice + and not e.payment_confirmed + ): next_action = _action( profile, - ACTION_CONFIRM_PAYMENT, - f"Orçamento {e.quote_number or ''} associado. Confirmar pagamento antes de emitir fatura.", - priority="alta", + ACTION_NO_ACTION, + f"Orçamento {e.quote_number or ''} enviado. Aguardar decisão do cliente ou evidência de pagamento.", + force_label="Aguardar cliente / pagamento", target_url=f"/opportunities/{e.opportunity_id}#operacao" if e.opportunity_id else None, document_id=e.quote_id, document_number=e.quote_number, ) - available_actions.append(next_action) - return OpportunityDecision(next_action, "Fluxo normal BLIF exige pagamento confirmado depois do orçamento e antes da fatura.", available_actions=available_actions, warnings=warnings, commercial_stage=COMMERCIAL_STAGE_WAITING_PAYMENT, financial_state=_financial_state(e), physical_state=_physical_state(e), profile_name=profile.name, decision_version=profile.version) + return OpportunityDecision( + next_action, + "O orçamento foi enviado e ainda não existe evidência de pagamento que exija validação. " + "Aguardar o cliente; o follow-up comercial assume quando ficar devido.", + warnings=warnings, + commercial_stage=COMMERCIAL_STAGE_WAITING_PAYMENT, + financial_state=_financial_state(e), + physical_state=_physical_state(e), + profile_name=profile.name, + decision_version=profile.version, + ) + if e.payment_confirmed and not e.has_invoice and ( not e.has_fiscal_customer or not e.fiscal_data_complete diff --git a/tests/domain/opportunity_flow/test_v4928_1_5_60_company_workflow_profiles.py b/tests/domain/opportunity_flow/test_v4928_1_5_60_company_workflow_profiles.py index 52797ff..39e9f6d 100644 --- a/tests/domain/opportunity_flow/test_v4928_1_5_60_company_workflow_profiles.py +++ b/tests/domain/opportunity_flow/test_v4928_1_5_60_company_workflow_profiles.py @@ -17,18 +17,39 @@ def test_blif_profile_loads_defaults_and_labels(): assert profile.documents["invoice"]["label"] == "Fatura" -def test_quote_before_shipping_requires_confirm_payment_before_invoice(): +def test_sent_quote_waits_for_customer_or_payment_evidence(): evidence = build_opportunity_evidence( - {"id": "opp-1", "stage": "QUOTE_SENT", "metadata": {"payment_terms": "before_shipping"}}, - linked_customer={"id": "c1", "tax_id": "123", "billing_email": "a@b.pt", "address": "Rua", "postal_code": "1000", "city": "Lisboa"}, - linked_documents=[{"id": "q1", "document_kind": "quotation", "document_number": "ORC.ORC2026.177", "total_amount": 202.95}], + { + "id": "opp-1", + "stage": "QUOTE_SENT", + "metadata": {"payment_terms": "before_shipping"}, + }, + linked_customer={ + "id": "c1", + "tax_id": "123", + "billing_email": "a@b.pt", + "address": "Rua", + "postal_code": "1000", + "city": "Lisboa", + }, + linked_documents=[ + { + "id": "q1", + "document_kind": "quotation", + "document_number": "ORC.ORC2026.177", + "total_amount": 202.95, + } + ], operation_snapshot={"links": [], "cards": []}, fiscal_data_complete=True, ) + decision = decide_opportunity_next_action(evidence, _profile()) - assert decision.next_action.code == "CONFIRM_PAYMENT" + + assert decision.next_action.code == "NO_ACTION" + assert decision.commercial_stage == "WAITING_PAYMENT" assert decision.next_action.document_number == "ORC.ORC2026.177" - assert "antes da fatura" in decision.next_action.description or "antes de emitir fatura" in decision.next_action.description + assert "Aguardar" in decision.next_action.description def test_payment_confirmed_without_invoice_sends_invoice():