fix(opportunity-flow): separate quote creation from quote sending
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from app.domain.opportunity_flow import build_opportunity_evidence, decide_opportunity_next_action, load_company_profile
|
||||
from app.domain.opportunity_flow.audit import audit_decisions
|
||||
from app.domain.opportunity_flow.evidence import OpportunityEvidence
|
||||
|
||||
|
||||
def _profile():
|
||||
@@ -106,7 +107,7 @@ def test_fiscal_conflict_blocks_financial_actions():
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
assert decision.next_action.code == "REVIEW"
|
||||
blocked = {a.code for a in decision.blocked_actions}
|
||||
assert {"CONFIRM_PAYMENT", "SEND_INVOICE", "CREATE_JASMIN_QUOTE"} <= blocked
|
||||
assert {"CONFIRM_PAYMENT", "SEND_INVOICE", "CREATE_QUOTE"} <= blocked
|
||||
|
||||
|
||||
def test_after_delivery_invoice_without_payment_allows_prepare_odoo_before_payment():
|
||||
@@ -123,3 +124,165 @@ def test_after_delivery_invoice_without_payment_allows_prepare_odoo_before_payme
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
assert decision.next_action.code == "PREPARE_ORDER"
|
||||
assert "sem pagamento prévio" in decision.reason or "sem exigir pagamento" in decision.next_action.description
|
||||
|
||||
|
||||
def test_new_lead_without_fiscal_customer_does_not_make_fiscal_validation_primary():
|
||||
evidence = build_opportunity_evidence(
|
||||
{
|
||||
"id": "opp-new-no-fiscal",
|
||||
"stage": "NEW_LEAD",
|
||||
"metadata": {"payment_terms": "before_shipping"},
|
||||
},
|
||||
linked_customer=None,
|
||||
linked_documents=[],
|
||||
operation_snapshot={"links": [], "cards": []},
|
||||
fiscal_data_complete=False,
|
||||
)
|
||||
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
|
||||
assert decision.next_action.code != "VALIDATE_FISCAL_CUSTOMER"
|
||||
assert decision.financial_state == "no_document"
|
||||
|
||||
|
||||
def test_payment_confirmed_without_fiscal_customer_requires_fiscal_validation():
|
||||
evidence = build_opportunity_evidence(
|
||||
{
|
||||
"id": "opp-paid-no-fiscal",
|
||||
"stage": "PAYMENT_CONFIRMED",
|
||||
"metadata": {"payment_terms": "before_shipping"},
|
||||
},
|
||||
linked_customer=None,
|
||||
linked_documents=[
|
||||
{
|
||||
"id": "q-paid",
|
||||
"document_kind": "quotation",
|
||||
"document_number": "ORC.TEST.1",
|
||||
}
|
||||
],
|
||||
operation_snapshot={
|
||||
"links": [
|
||||
{
|
||||
"system": "clientflow",
|
||||
"external_type": "payment",
|
||||
"status": "confirmed",
|
||||
}
|
||||
],
|
||||
"cards": [],
|
||||
},
|
||||
fiscal_data_complete=False,
|
||||
)
|
||||
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
|
||||
assert decision.next_action.code == "VALIDATE_FISCAL_CUSTOMER"
|
||||
assert decision.financial_state == "payment_confirmed"
|
||||
|
||||
|
||||
def _base_no_document_evidence(stage: str):
|
||||
return build_opportunity_evidence(
|
||||
{
|
||||
"id": f"opp-{stage.lower()}",
|
||||
"stage": stage,
|
||||
"metadata": {"payment_terms": "before_shipping"},
|
||||
},
|
||||
linked_customer=None,
|
||||
linked_documents=[],
|
||||
operation_snapshot={"links": [], "cards": []},
|
||||
fiscal_data_complete=False,
|
||||
)
|
||||
|
||||
|
||||
def test_new_lead_without_document_is_not_promoted_to_quote_or_fiscal():
|
||||
decision = decide_opportunity_next_action(
|
||||
_base_no_document_evidence("NEW_LEAD"),
|
||||
_profile(),
|
||||
)
|
||||
|
||||
assert decision.next_action.code == "NO_ACTION"
|
||||
assert decision.commercial_stage == "NEW_LEAD"
|
||||
|
||||
|
||||
def test_info_sent_without_document_is_not_promoted_to_quote():
|
||||
decision = decide_opportunity_next_action(
|
||||
_base_no_document_evidence("INFO_SENT"),
|
||||
_profile(),
|
||||
)
|
||||
|
||||
assert decision.next_action.code == "NO_ACTION"
|
||||
assert decision.commercial_stage == "INFO_SENT"
|
||||
|
||||
|
||||
def test_quote_requested_without_document_creates_quote():
|
||||
decision = decide_opportunity_next_action(
|
||||
_base_no_document_evidence("QUOTE_REQUESTED"),
|
||||
_profile(),
|
||||
)
|
||||
|
||||
assert decision.next_action.code == "CREATE_QUOTE"
|
||||
|
||||
|
||||
def test_quote_sent_without_linked_document_and_with_send_evidence_reconciles():
|
||||
evidence = build_opportunity_evidence(
|
||||
{
|
||||
"id": "opp-quote-sent",
|
||||
"stage": "QUOTE_SENT",
|
||||
"metadata": {"payment_terms": "before_shipping"},
|
||||
},
|
||||
linked_customer=None,
|
||||
linked_documents=[],
|
||||
tasks=[
|
||||
{
|
||||
"id": "task-send-quote",
|
||||
"action_code": "SEND_QUOTE",
|
||||
"status": "completed",
|
||||
}
|
||||
],
|
||||
operation_snapshot={
|
||||
"links": [],
|
||||
"cards": [],
|
||||
},
|
||||
fiscal_data_complete=False,
|
||||
)
|
||||
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
|
||||
assert decision.next_action.code == "RECONCILE_DOCUMENTS"
|
||||
|
||||
|
||||
def test_created_quote_must_be_sent_before_payment_confirmation():
|
||||
evidence = OpportunityEvidence(
|
||||
opportunity_id="opp-created-quote",
|
||||
stage="QUOTE_REQUESTED",
|
||||
has_fiscal_customer=True,
|
||||
fiscal_identity_validated=True,
|
||||
fiscal_data_complete=True,
|
||||
has_quote=True,
|
||||
quote_sent=False,
|
||||
payment_confirmed=False,
|
||||
quote_id="quote-1",
|
||||
quote_number="ORC.TEST.1",
|
||||
)
|
||||
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
|
||||
assert decision.next_action.code == "SEND_QUOTE"
|
||||
|
||||
|
||||
def test_sent_quote_can_advance_beyond_send_quote():
|
||||
evidence = OpportunityEvidence(
|
||||
opportunity_id="opp-sent-quote",
|
||||
stage="QUOTE_SENT",
|
||||
has_fiscal_customer=True,
|
||||
fiscal_identity_validated=True,
|
||||
fiscal_data_complete=True,
|
||||
has_quote=True,
|
||||
quote_sent=True,
|
||||
payment_confirmed=False,
|
||||
quote_id="quote-2",
|
||||
quote_number="ORC.TEST.2",
|
||||
)
|
||||
|
||||
decision = decide_opportunity_next_action(evidence, _profile())
|
||||
|
||||
assert decision.next_action.code != "SEND_QUOTE"
|
||||
|
||||
Reference in New Issue
Block a user