State machines
The platform’s workflows are all enum-backed. This page is the consolidated catalog — the machines that have real transitions as diagrams, and the read-time projections as tables — each cross-linked to the flow page that drives it. Every transition is traced to the exact set-site. Verified against current code.
UnitSaleStatus — the shopper-buy machine
Section titled “UnitSaleStatus — the shopper-buy machine”The local sale workflow (app/Enums/UnitSaleStatus.php, cast on UnitSale.status). Two drivers: the
shopper’s forward progression (each step advances only on a CRM 200), and the CRM webhook UpdateSale,
which can collapse the sale to canceled (or done/draft) from any state.
stateDiagram-v2 [*] --> draft: create draft --> pending_info: reserve, on CRM 200 pending_info --> download_form: update-info download_form --> pending_finish: reservation-form pending_finish --> done: finish-sale draft --> canceled: CRM webhook pending_info --> canceled: CRM webhook download_form --> canceled: CRM webhook pending_finish --> canceled: CRM webhook done --> [*] canceled --> [*]
Set-sites: create CreateSalePortal.php:47 · reserve UnitSaleController@reserveUnitSale:87
(and post-payment PaymentService.php:234-237) · update-info :159 · reservation-form :181 ·
finish-sale :208 · the canceled/done/draft webhook edges UpdateSale (Egypt :87/90/93, Oman
:108/111/114, Montenegro :128/131/134). attach does not transition. The salePayment route
binding only resolves a sale still in draft. Full flow: Shopper buys a unit.
TransactionStatus — the payment ledger
Section titled “TransactionStatus — the payment ledger”The gateway callback machine (app/Enums/TransactionStatus.php, cast on Transaction.status).
stateDiagram-v2 [*] --> pending: initiation via StoreTransaction pending --> success: gateway success callback pending --> failure: gateway fail callback pending --> canceled: gateway cancel callback success --> [*] failure --> [*] canceled --> [*]
pending at initiation (Paymob PaymobService.php:84-90, Thawani :59-66, WSPG :17-25); success
PaymentService.php:46; failure/canceled :83.
OmanBrokerApplicationStatusEnums — Oman pre-registration (12 states)
Section titled “OmanBrokerApplicationStatusEnums — Oman pre-registration (12 states)”The broker onboarding → admin review → CRM registration → renewal machine
(app/Enums/OmanBrokerApplicationStatusEnums.php, cast on OmanPreRegistrationBrokerApplication.status).
The individual (ambassador) path skips the contract; the company path goes through it.
stateDiagram-v2 [*] --> pending_info: start pending_info --> pending_documents: ambassador-info (individual) pending_info --> pending_company_details: company-info (company) pending_company_details --> pending_documents: company-details pending_documents --> pending_review: finalize pending_review --> missing_information: admin missing-info pending_review --> rejected: admin reject pending_review --> active: admin approve (individual) then CRM job pending_review --> contract_sent: admin approve (company) missing_information --> pending_review: broker resubmit contract_sent --> contract_review: submit-contract contract_review --> contract_sent: admin reject contract contract_review --> active: admin approve-contract then CRM job active --> contract_expired: scheduled job contract_expired --> pending_renewal: renewal resubmit contract_expired --> pending_document_review: document resubmit pending_renewal --> active: admin approve renewal pending_renewal --> contract_sent: admin reject renewal pending_document_review --> active: admin approve docs pending_document_review --> missing_information: admin reject docs rejected --> [*]
Set-sites (corrected against source — the ambassador path lands in pending_documents, not
pending_company_details): updateAmbassadorInfo:46 · updateCompanyInfo:77 · updateCompanyDetails:108
· finalize FinalizePreRegistrationApplication.php:44 · admin MarkApplicationAsMissingInfo/Rejected/Approved
· SubmitPreRegistrationSignedContracts.php:42 · ApproveApplicationContract.php:13-19 → the CRM job flips
active only on successful CRM registration (RegisterOmanPreRegistrationBrokerInCrmJob.php:70) ·
contract_expired via NotifyOmanBrokerWithContractExpiredJob.php:64-66. Entry pending_info;
terminal rejected (a rejected application is deleted on a fresh start); the renewal loop is cyclic.
Full flow: Oman broker pre-registration.
EOIPaymentStatusEnums — CRM-owned (mostly inbound)
Section titled “EOIPaymentStatusEnums — CRM-owned (mostly inbound)”The EOI payment status lives in the CRM, not on the local eoi_requests row
(app/Enums/EOIPaymentStatusEnums.php, int-backed). The backend actively sets only PAID; every other
value is delivered by the CRM and reacted to. So this is a table, not a graph.
| Value | State | Who sets / reacts |
|---|---|---|
1 |
PENDING_PAYMENT |
Inbound from CRM; fires the payment-link notification (UpdateEOIPortalAndSendNotifications.php:42) |
2 |
PAID |
Set by the backend on a successful charge → pushed to the CRM (PaymentService.php:283) |
3 |
DRAFT |
Inbound; filtered in the details mapper |
4 |
EXPIRED |
Inbound; fires ExpiredEOI (EOINotification.php:16) |
5 · 6 · 7 |
COLLECTED · REFUNDED · REQUIRES_ACTION |
inbound only Never set in backend/app — enum + label map + Rule::in allowlist only (display/validation) |
Full flow: Submitting an EOI.
Derived: ReservedSaleStatusEnums + the per-country CRM mappers
Section titled “Derived: ReservedSaleStatusEnums + the per-country CRM mappers”The broker “my sales” listing shows a ReservedSaleStatusEnums value
(pending_odh_review · partially_reserved · reserved · closed · lost) that is derived, not stored —
computed on read in UnitSaleMapper.php:40-42 from the sale’s CRM integer contracting_status, dispatched
per country. It’s a projection, so here’s the mapping instead of a graph:
| CRM integer | Egypt | Oman | Montenegro |
|---|---|---|---|
100000003 |
RESERVED |
RESERVED |
— (not a case) |
7 |
— | — | null 🐞 (declared RESERVED=7 but unmapped) |
100000005 |
PARTIALLY_RESERVED |
PARTIALLY_RESERVED |
— |
100000004 |
CLOSED |
CLOSED |
3 → CLOSED |
100000007 |
LOST |
LOST |
4 → LOST |
| lower codes | PENDING_ODH_REVIEW |
PENDING_ODH_REVIEW |
1,2,5,6 → PENDING_ODH_REVIEW |
