Skip to content

Operator Backend Decision Gap Mining

Scope: source-mining continuation for admin/operator features where source PHP, view, or JavaScript decides money, ranking, filters, status, counters, or template eligibility. Source code is evidence only.

flowchart LR
  AdminUI[Admin or customer UI]
  SourceController[Source controller or JavaScript]
  BackendCommand[Modern backend command]
  ReadModel[Backend-owned read model]
  Policy[Versioned domain policy]
  Audit[Audit and snapshot]
  Portal[Modern portal]

  AdminUI -. legacy mutable fields .-> SourceController
  SourceController -. evidence only .-> BackendCommand
  BackendCommand --> Policy
  BackendCommand --> Audit
  Policy --> ReadModel
  ReadModel --> Portal
  Portal -->|explicit command with expected revision| BackendCommand

Design rule: frontend can render backend facts and submit commands. It must not recalculate payment conversion totals, settlement completion, matching rank, score caps, history filters, dashboard counters, or template eligibility.

Source evidenceModern bounded context / featureStatusWhy backend must own it
source-refs/sanmopia_web/application/controllers/api/Payment.php:65-219, source-refs/sanmopia_web/application/controllers/api/Payment.php:325-382, source-refs/sanmopia_web/application/controllers/api/Payment.php:508-583, source-refs/sanmopia_web/application/controllers/api/Payment.php:657-735, source-refs/sanmopia-admin/application/models/Payment_model.php:3923-4042pricing_settlement / PaymentProductPlan, PaymentObligation, PaymentProductCommunicationRoute, PaymentProviderRouteFeeSnapshot, PaymentMethodFeePolicy, PaymentVirtualAccountInstructionPayloadPartialSource routes deposit, balance, pre-reservation, coupon, and change-payment through separate controller branches and later reuses payment type ids/method strings for fees, receipts, settlement, notification, and operator states. Backend now owns an authenticated payment product-plan catalog and exposes it through GET /payment-product-plans and GET /payment-product-plans/{planCode}. The same Supabase-backed catalog validates Kill Bill pre-registration by stable plan code and returns product family, ledger obligation, allowed payment methods, provider route, refund/conversion policy, virtual-account expiry, settlement participation, realization timing, fee basis, settlement line templates, communication routes, effective dates, and frozen customer-visible amount behavior. sanmopia_payment_product_plan_catalog_items stores versioned active/effective catalog revisions with explicit grants and RLS; v2 migrated gift/voucher fee-band settlement lines, v3 migrated notification route policy into JSONB catalog columns, and v4 adds delegated payer payment-complete routes for customer-share deposit/balance, pre-reservation deposit, and payment-change completion without mutating old catalog rows. The communication route keys now match backend-owned Novu/Strapi workflow catalog rows and Supabase trigger/audience constraints for mother, payer, coupon receiver, branch operator, and branch secondary-contact audiences. PlanPaymentCommunicationDispatchHandler now converts payment catalog routes plus recipient facts into durable dispatch intents, and SupabaseCommunicationDispatchStore persists dispatch requests by catalog rule lookup plus provider dispatch event audit rows; ReservationPaymentWorkflow now accepts a typed payment communication dispatch command through Restate payload/input replay and invokes the planner after successful payment closeout while recording dispatch-planning failures as retryable workflow failures. SupabasePaymentCommunicationDispatchCommandResolver now derives paid-payment dispatch commands from sanmopia_payment_records, active payment-product plan routes, reservation mother facts, deterministic branch-operator membership facts, and active family payment-delegation facts before the Kill Bill webhook starts the workflow. Delegated payer resolution honors mother member profile, reservation booking id, product-plan payment scope, grant window, revocation, and booking-specific precedence, and refuses ambiguous multiple-payer authority instead of choosing a random family member. Gift-coupon receiver resolution now reads user-backed sanmopia_payment_coupon_receiver_snapshots and can produce a separate coupon_fulfilled dispatch command instead of relying on controller payload strings; phone-only gift receiver contacts now route through sanmopia_payment_direct_recipient_contact_snapshots and contact-only sanmopia_direct_contact_communication_dispatch_requests so provider payloads no longer require fake user ids. Primary branch payment notices now prefer versioned sanmopia_branch_payment_notice_contacts policy rows with recipient_kind='branch_office' and default condition primary_branch_payment_notice_contact, falling back to branch-operator membership only when no policy row exists. Branch secondary payment notices now resolve from the same versioned table instead of hardcoded branch-id extras. PaymentProviderRouteFeeSnapshot binds provider route plus method-fee quote during Kill Bill pre-registration, returns it through the contract, and persists nullable snapshot columns on sanmopia_payment_records for new rows. PaymentProviderTemplatePayload now renders a policy-versioned providerTemplatePayload for each dispatch request with frozen workflow, recipient, amount, payment, and subject variables before Novu handoff, so templates no longer need PHP/UI string assembly or amount formatting. PaymentVirtualAccountInstructionPayload now renders Kill Bill REST virtual-account facts from payment_provider_context (method.virtualAccount.accountNumber, bank, remitteeName, expiredAt) with legacy KCP key fallback and always uses frozen backend amount_krw, removing source hardcoded pre-reservation 100000 and UI-side account/deadline formatting. Remaining backend work: virtual-account-issued dispatch path, queue worker execution, provider result reconciliation, Kill Bill admin repair commands, Kill Bill REST adapter hardening, and hidden source payment-type adapter edges.
source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:68-75, source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:142-162, source-refs/sanmopia-admin/static/js/reservation_status/payment_change.js:93-123, source-refs/sanmopia-admin/static/js/reservation_status/payment_change.js:144-180, source-refs/sanmopia_web/static/js/mypage/reservation_changePayment.js:43-75, source-refs/sanmopia_web/application/controllers/api/Payment.php:658-757, source-refs/sanmopia_web/application/models/Payment_model.php:856-900, source-refs/sanmopia-admin/application/models/Payment_model.php:4422-4521pricing_settlement / ReservationPaymentConversionProjection, PaymentConversionCommand, PaymentConversionOperatorDecisionPartialAdmin list computes customer-share amount from mutable reservation price/coupon data, exposes editable conversion price and status, and maps cancelled reservation plus waiting status to a synthetic UI status. Customer web initializes a conversion amount, deletes un-deposited conversion rows, runs gateway payment, stores gateway context, and triggers notifications. Backend now has pricing_settlement/features/payment_conversion domain/application foundation. Remaining migration: Supabase persistence, API/HTTP route binding, operator authorization, frontend replacement, and provider result reconciliation.
source-refs/sanmopia-admin/static/js/calculate_detail.js:81-164, source-refs/sanmopia-admin/static/js/calculate_detail.js:169-226, source-refs/sanmopia-admin/static/js/calculate_detail.js:240-274, source-refs/sanmopia-admin/application/controllers/Calculate.php:150-191, source-refs/sanmopia-admin/application/controllers/Calculate.php:219-245, source-refs/sanmopia-admin/application/controllers/Calculate.php:279-309pricing_settlement / SettlementStatementCommandContext, SettlementLineAcknowledgement, SettlementTransitionAuditPartialOne browser FormData payload mixes selected normal rows, voucher rows, reservation ids, payment type ids, used coupons, product coupons, cancelled product coupons, manual row names/prices/counts, delivery fee, month due, message, memo, actor type, board id, and current settlement month. Backend must split this into typed commands with idempotency keys, expected board/line revisions, actor authorization, row-level audit, private memo visibility, and replay/repair behavior.
source-refs/sanmopia_web/static/js/step3.js:198-474, source-refs/sanmopia_web/static/js/step3.js:766-831, source-refs/sanmopia_web/static/js/step3.js:1148-1179, source-refs/sanmopia_web/application/views/reservation/retc.php:45-200, source-refs/sanmopia_web/application/models/Reservation_model.php:1115-1373, source-refs/sanmopia_web/application/controllers/api/Reservation.php:86-239pricing_settlement / ChargeAdjustmentPolicy, PaymentAdjustmentCatalog, CustomerVisibleChargePaymentSummaryPartialBooking and detail pages recompute payable subtotals from DOM inputList data, PRICE_TABLE, and mutable coupon rows; source also branches UI totals around cancellation/refund state. Backend now exposes GET /reservation-financial-lifecycles/{reservationBookingId}/customer-visible-charge-payment-summary, composing finalized charge snapshot rows and Supabase payment records into backend-owned display lines, totals, payment state, receipt refs, and caller-total mismatch diagnostics. Remaining work: persist snapshots from every creation/change path and reject UI-level total rewrites after closeout.
source-refs/sanmopia-admin/application/controllers/Reservation.php:415-431reservation_operations / OperatorFinancialMutationLockPolicy, ReservationFinancialFieldLock, ActorStageCapabilityProjectionPartialSource computes a page-level cantSave flag from actor type, service end, waiting-deposit exception, completed/cancelled states, and dated operator exceptions. Backend now resolves field-level lock previews with stable reason codes and derives actorKind from authenticated branch/HQ actor context through POST /operator-financial-mutation-lock-decisions/{reservationId}. Remaining work: every mutation command must re-check the same policy from a Supabase current-state snapshot and expected revision, then persist lock override audit facts.
source-refs/sanmopia-admin/static/js/calculate_detail.js:297-337, source-refs/sanmopia-admin/application/controllers/Calculate.php:568-578, source-refs/sanmopia-admin/application/controllers/DataRoom.php:30-94, source-refs/sanmopia-admin/application/controllers/DataRoom.php:218-250, source-refs/sanmopia-admin/application/controllers/DataRoom.php:311-386, source-refs/sanmopia-admin/application/models/Payment_model.php:4382-4400pricing_settlement / SettlementDataRoomRequest, SettlementArtifactGrant, SettlementCompletionProjection, OperatorReturnStateTokenPartialSource uses direct GET download by excelType and boardId, branch data-room pages read current board rows, generic data-room download posts selected reservation ids, and settlement completion is derived from raw flag/count subqueries. Backend must own artifact request state, signed grant creation, retry/repair status, board/source projection revision, completion counters, branch/HQ scope, and return-state tokens.
source-refs/sanmopia-admin/application/models/Manager_evaluation_model.php:612-728, source-refs/sanmopia-admin/application/controllers/ajax/ManagerEvaluation.php:146-228, source-refs/sanmopia-admin/application/models/Manager_model.php:115-152, source-refs/sanmopia-admin/application/models/Manager_model.php:677-725caregiver_performance_recognition / CaregiverPerformanceScorecard, CaregiverGradeSnapshot, CaregiverGradeRecalculationHistoryPartialSource caps each score type, then tries to cap combined monthly and other-work history, but the final adjustment is made on a loop-local row. Ajax score history independently recalculates capped rows for display. Backend must compute original score, capped score, combined cap, before/after totals, old/new caregiver rule, grade snapshot link, and policy version once, then expose immutable scorecard entries.
source-refs/sanmopia-admin/application/models/Matching_model.php:44-124, source-refs/sanmopia-admin/application/models/Matching_model.php:144-176, source-refs/sanmopia-admin/application/models/Matching_model.php:724-862, source-refs/sanmopia_web/application/models/Manager_matching_model.php:9-65, source-refs/sanmopia_web/application/models/Manager_matching_model.php:67-164, source-refs/sanmopia_web/application/models/Manager_matching_model.php:284-325, source-refs/sanmopia_web/application/controllers/Service.php:604-622, source-refs/sanmopia_web/application/controllers/api/Service.php:840-846caregiver_assignment / CaregiverCandidateSnapshot, CaregiverMatchingPolicy, CaregiverTransportEvidencePartialSource combines preference/personality score, active preference filters, schedule function, pet-allergy exclusion, branch and grade filters, search text, random tie seed, distance sort, cached transport time, Haversine distance display, ODSAY travel-time lookup, and premium/special class mapping split across web and admin code. Backend must persist candidate evidence, ranking policy version, deterministic tie-breaker, distance and transit provider result with TTL/source, pet exclusion, class eligibility, schedule proof, branch scope, score-vs-travel priority, and grade/class snapshot before assignment.
source-refs/sanmopia-admin/application/views/office_management/office_detail.php:198-217, source-refs/sanmopia-admin/application/views/office_management/office_detail.php:222-430, source-refs/sanmopia-admin/static/js/office_management/office_detail.js:167-246, source-refs/sanmopia-admin/static/js/office_management/office_detail.js:253-276, source-refs/sanmopia-admin/application/models/Branch_model.php:345-425branch_operations / BranchOfficeProfileCommand, BranchServiceAreaPolicy, ServiceCoverageDecision, BranchOfficeProfileRevisionPartialSource UI chooses membership/lifecycle inputs, service-area address depth, and address normalization, then saves without an expected revision and navigates by reload/history state. Backend has branch office profile and service-area policy foundation across domain/application/adapters/interfaces. Remaining migration: split profile commands from coverage policy commands, enforce expected revision on every operator save, expose conflict diffs, and replace history-state reload UX.
source-refs/sanmopia-admin/application/controllers/Reservation.php:208-316, source-refs/sanmopia-admin/application/views/reservation_status/reservation_history_new.php:66-128, source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:31-64, source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:66-143, source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:225-260, source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:708-724, source-refs/sanmopia-admin/application/controllers/Reservation.php:1728-1736, source-refs/sanmopia-admin/application/models/Reservation_model.php:3864-4006reservation_operations / ReservationHistoryReadModel, ReservationHistoryFilterState, ReservationHistoryExportRequestPartialSource reconstructs history state from DataTables query params, hash fragments, session search, serialized cookies, date controls, dashboard filter flags, and selected checkboxes for export. Backend must own a typed filter contract, dashboard-origin filter token, row projection, export request, authorization scope, and stable search/sort semantics so browser history or cookie state cannot change business meaning.
source-refs/sanmopia-admin/application/controllers/Administrator.php:57-103, source-refs/sanmopia-admin/application/views/home/dashboard.php:36-83, source-refs/sanmopia-admin/application/views/home/dashboard.php:88-150, source-refs/sanmopia-admin/application/models/Reservation_model.php:983-1045business_reporting / OperatorDashboardSnapshot, ReservationOperationsCounter, CaregiverOperationsCounterPendingSource caches dashboard arrays for 600 seconds and the view renders mixed count(...) values plus model-returned counts for users, reservations, active caregivers, unpaid salary info, unassigned reservations, consulting, change requests, and balance-missing reservations. Backend must own snapshot time, branch scope, counter definitions, stale age, drill-down filter tokens, and cache invalidation so dashboard numbers and detail lists share one read-model revision.
source-refs/sanmopia-admin/application/models/Spreadsheet_model.php:15-58, source-refs/sanmopia-admin/application/controllers/DataRoom.php:311-386, source-refs/sanmopia-admin/application/models/Spreadsheet_model.php:3914-4198, source-refs/sanmopia-admin/application/models/Spreadsheet_model.php:4284-4598, source-refs/sanmopia-admin/static/dist/excel/type10_3_old_20240125.xlsx, source-refs/sanmopia-admin/static/dist/excel/type9_240717.pdfdocument_reporting / PrintableTemplateRegistry, BulkExportTemplatePolicy, DocumentRenderArtifact, TemplateVersionManifestPartialSource keeps positional document ids, static XLSX/PDF filenames, old template variants, service-type bundle arrays, temp-file zip generation, static PDF copying, and direct external conversion service details in one spreadsheet model. Backend must own template kind, version, revision, service eligibility, renderer, storage object reference, render artifact hash, bundle membership, and failed conversion retry/audit without exposing static file paths or source numeric ids.
  • Existing checklist rows already cover broad settlement data-room, caregiver-matching, document-reporting, analytics, and payment-conversion tracks. This page adds sharper evidence for fields and UI/controller decisions that should become backend commands or read models.
  • Do not port source names such as raw status ids, excelType, boardId, spreadsheet type numbers, or DataTables column indexes into new contracts. Keep them only in migration manifests and source evidence docs.
  • Restate fits payment conversion, settlement artifact build/repair, and document render flows where retries, external providers, or stateful handoff are part of the business outcome.
  • Traffic-spike survival is tracked in Traffic Spike Backpressure Gap Mining. Backend commands must accept quickly, persist intent, and shed/queue work before Uvicorn, Supabase Postgres, renderer workers, or external providers are exhausted.
  • Runtime telemetry is tracked in Observability and Backpressure. OpenTelemetry traces and metrics support operations, analytics, and incident analysis, but operator-facing history still comes from backend read models and domain journals.

Compose already uses Traefik labels, container expose, env-driven image/port settings, self-hosted Supabase, Restate, SpiceDB, and Novu. The remaining operator-backend gap is not another proprietary queue; it is a documented traffic policy:

  • Traefik owns edge load balancing, HTTP rate limits, in-flight request caps, read-route retry, and circuit breaker middleware only where the current OSS image supports it.
  • Supabase Queues / pgmq is the default durable queue because Supabase self-hosting is already the platform source of truth.
  • Restate owns durable workflows where retry/replay crosses provider calls, multiple domain commands, or long-running artifact builds.
  • Valkey/Redis is justified only for hot TTL counters or short locks after Supabase queue metrics prove Postgres is too hot. Existing Novu Redis is notification-internal, not general Sanmopia domain state.
  • NATS/RabbitMQ stays deferred until there is measured fan-out or throughput evidence that pgmq plus Restate cannot satisfy.
  • OpenTelemetry Collector is the neutral observability gateway. Backend emits OTLP on the internal network; Prometheus/Grafana/Tempo/Loki are exporter additions, not feature-slice dependencies.

Backpressure must cover payment pre-registration, document generation, settlement exports, notification sends, matching batch runs, and webhook processing. Each flow needs explicit worker concurrency, idempotency key, priority, retry/dead-letter policy, replay command, and queue observability before production traffic-spike testing.