Branch Office Operations Gap Mining
Branch Office Operations Gap Mining
Section titled “Branch Office Operations Gap Mining”Scope: branch/headquarters office operations from the legacy admin source. Source controllers mix branch profile, service-area policy, membership fee, bank accounts, image uploads, login audit, and caregiver-facing content. Modern migration should split those into DDD feature slices and use Supabase Auth, Storage, RLS, SpiceDB/OPA authorization, and Strapi where content authoring is the real product.
Ubiquitous Names
Section titled “Ubiquitous Names”BranchOfficeProfile: branch legal/operational profile, contact info, business number, representative names, address, and active/deleted state.BranchOperator: 현대화의 지점 운영자 actor. 레거시branch_manager는 경계 alias일 뿐 계약·권한·테스트의 canonical role은branch_operator다.BranchOperatorCredential: office login identifier, password rotation, token/session state, and duplicate-login guard.BranchSettlementAccount: purpose-specific settlement bank-account snapshot for statement remittance, branch deposit, membership fee, or refund, with owner, masked number, verification state, valid period, supersession, and audit.BranchSettlementAccountSnapshot: immutable account facts frozen for one settlement statement so later branch-account edits cannot change completed or in-progress settlement output.BranchMembershipFeePolicy: monthly branch membership fee snapshot, valid period, and revision history.BranchServiceAreaPolicy: available, consultation-only, and unavailable area rules by address depth and effective period.BranchBrandAsset: branch profile image, stamp image, storage object, version, and deletion/supersession audit.BranchLoginAuditEvent: branch operator login IP, device/user-agent class, auth result, and session id.OfficeContentPublication: branch/HQ-authored manager notices, education schedules, event notices, central notices, and caregiver news.
Component Flow
Section titled “Component Flow”flowchart LR HQ[Headquarters administrator] BranchOperator[Branch operator] Caregiver[Caregiver portal] Profile[BranchOfficeProfile] Credential[BranchOperatorCredential] SettlementAccount[BranchSettlementAccount] AccountSnapshot[BranchSettlementAccountSnapshot] Membership[BranchMembershipFeePolicy] ServiceArea[BranchServiceAreaPolicy] BrandAsset[BranchBrandAsset] LoginAudit[BranchLoginAuditEvent] Content[OfficeContentPublication] Storage[(Supabase Storage)] CMS[Strapi CMS] HQ --> Profile HQ --> Credential BranchOperator --> Profile Profile --> SettlementAccount SettlementAccount --> AccountSnapshot Profile --> Membership Profile --> ServiceArea Profile --> BrandAsset Credential --> LoginAudit BrandAsset --> Storage HQ --> Content BranchManager --> Content CMS --> Content Content --> Caregiver
Source Evidence And Migration Gaps
Section titled “Source Evidence And Migration Gaps”Branch Profile And Operator Credential
Section titled “Branch Profile And Operator Credential”Source behavior:
- Office detail GET chooses branch id from the URL for headquarters, but only
this read path replaces it with the signed-in branch id for branch users.
Evidence:
source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:374-381. - Office submit accepts the caller-supplied URL
$indexwithout applying the GET path’s session-branch substitution, then forwards it as the update key. Evidence:source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:399-448. - The first JavaScript guard only marks branch name, membership fee, login id,
and password inputs readonly. It does not bind the submitted URL index to the
authenticated branch. Evidence:
source-refs/sanmopia-admin/static/js/office_management/office_detail.js:1-10. - Office submit accepts branch name, representative names, business number,
login id, membership pay, description, email, address, phone numbers, primary
and secondary bank accounts, office status, and optional password. Evidence:
source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:399-452. - Duplicate login id is checked before saving. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:186-200. - Saving branch profile inserts or updates branch rows; new branch creation also
creates an auth token. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:202-259. - The browser submit flow reloads the current page and then calls browser
history navigation after an update. Evidence:
source-refs/sanmopia-admin/static/js/office_management/office_detail.js:253-276.
Modern gap:
BranchOfficeProfileandBranchOperatorCredentialmust be separate. Credential changes need password rotation, session invalidation, audit, and auth provider mapping. Profile changes need revisioned facts.- Active/deleted office state should become branch lifecycle state, not raw delete flag copied into domain.
- Duplicate login checks should be backed by Supabase Auth/unique constraints and explicit conflict responses.
- Profile edits need
expected_revision, typed conflict diffs, and a stable command result route. The browser must not decide success navigation by mixing reload and history state. - The own-profile GET and PATCH must take no authoritative branch or actor id.
The server resolves exactly one active
branch_operatormembership from the Supabase session, verifies the branch permission in SpiceDB, and fails closed for zero/multiple membership or pending relationship delivery. - The existing generic
POST /branch-office-profile-updatesis not P02 authority while it acceptsbranchOfficeProfileIdfrom the caller. It must be hardened behind the same server-owned resolver or closed before P02 can pass; a client-supplied id plusmanagecheck does not prove invitation activation or own-branch scope. PATCH /branch/profileowns only display/contact/structured-address facts. Branch/legal identity, lifecycle, credential, membership fee, raw settlement account, and service-area policy remain separate HQ or feature commands. Exact replay returns the prior full projection; stale revision returns typed409with latest projection and field diff; invalid or forbidden fields return typed422without partial mutation.
Settlement Accounts And Membership Fees
Section titled “Settlement Accounts And Membership Fees”Source behavior:
- Branch profile stores two bank account sets directly on the branch row.
Evidence:
source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:420-427andsource-refs/sanmopia-admin/application/models/Branch_model.php:223-231. - Saving profile writes a monthly membership fee row keyed by branch and
current year/month string. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:261-289.
Modern gap:
BranchSettlementAccountshould store statement-remittance, branch-deposit, membership-fee, and refund purposes, effective period, verification evidence, masked number, and supersession history. Settlement statements should snapshot account values when a statement is requested.BranchMembershipFeePolicyshould be a versioned policy with valid period and branch scope. The current month side effect on profile save is not enough.
Service Area Policy
Section titled “Service Area Policy”Source behavior:
- Source reads service-available, consultation-only, and unavailable areas as
separate accept-type lists. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:291-343. - Inserting area rows maps address depth by
sido,sigungu, andbname. Consultation/unavailable areas are rejected unless they belong to an existing available area. Evidence:source-refs/sanmopia-admin/application/models/Branch_model.php:345-433. - The office detail page exposes separate buttons for level 1, level 2, and
level 3 address scope, and JavaScript normalizes Daum address fields before
posting the rule. Evidence:
source-refs/sanmopia-admin/application/views/office_management/office_detail.php:222-430andsource-refs/sanmopia-admin/static/js/office_management/office_detail.js:167-246. - Area deletion directly deletes by branch and service-area id. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:597-608.
Modern gap:
BranchServiceAreaPolicyshould store rules by address depth, policy kind, effective period, actor, and revision. Deletes should create superseded facts so historical reservation assignment and pricing eligibility remain readable.- Address normalization belongs in a shared location policy helper, not in branch UI/controller code.
ServiceCoveragePolicyshould evaluate the priority between available, consultation-only, and unavailable rules for a normalized address. Matching, branch routing, and pricing should consume the same decision projection.- 2026-07-04 backend/contract update: branch service-area policy publication is
now exposed as
POST /branch-service-area-policy-revisionsand policy lookup is exposed asPOST /branch-service-area-policy-decisions. Both derive actor identity from Supabase Auth and enforce branchmanage/viewthrough SpiceDB. The decision response carries semantic policy kind, matched rule id, rule revision, address depth, and Daum/Kakao/operator evidence so UI code no longer owns coverage precedence or address-depth branching. The decision path now also recordsBranchServiceAreaDecisionSnapshotevidence with booking id, decision code, matched rule reference, coverage profile key, and service-calendar profile key. Remaining binding work is reservation workflow invocation,reservation_service_address_idprecision, and consumer reads for matching, pricing, branch transfer, and conflict audit.
2026-07-05 persistent-worker tightening:
| Missing backend policy | Source evidence | Source behavior | Modern target |
|---|---|---|---|
| Effective-dated service-area publication | source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:172-207, source-refs/sanmopia-admin/application/models/Branch_model.php:337-426, source-refs/sanmopia-admin/static/js/office_management/office_detail.js:194-245, source-refs/sanmopia-admin/application/views/office_management/office_detail.php:226-430 | UI posts possible / counsel / impossible plus address depth strings from Daum address data; model inserts current rows with no effective date, expected revision, actor reason, import source, or policy snapshot. | BranchServiceAreaPolicyVersion must publish effective-dated rules with actor, expected revision, address-provider evidence, rule fingerprint, policy kind, coverage profile key, service-calendar policy profile key, and impact plan. |
| Address-match decision snapshot | source-refs/sanmopia_web/application/models/Branch_model.php:38-144, source-refs/sanmopia_web/application/controllers/api/Service.php:47-83, source-refs/sanmopia_web/application/models/Reservation_model.php:2239-2301 | Booking finds a possible branch by address-level fallback, special split-address handling, and unavailable override; consultation can also come from consultation area, second address, birth/service timing, multi-baby, or picked caregiver. | ReservationServiceAreaDecision and ReservationServiceAddressCoverageSnapshot must freeze normalized address, matched possible/consultation/unavailable rule, override reason, consultation reason codes, branch owner, service-address row id, and policy revision before reservation workflow continues. |
| Service-area withdrawal and branch exception audit | source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:326-344, source-refs/sanmopia-admin/application/models/Branch_model.php:591-607, source-refs/sanmopia-admin/static/js/office_management/office_detail.js:288-317, source-refs/sanmopia-admin/application/views/office_management/office_detail.php:295-430 | Deletion hard-removes rows by branch/service-area id; branch-user mutation guards are commented in controller/view for consultation and unavailable lists, while possible-area buttons are admin-gated in view only. | BranchServiceAreaWithdrawalCommand must supersede rows instead of deleting, reject stale revisions, enforce branch/HQ capability by command policy, record before/after diffs, and emit invalidation for active quotes, matching, settlement, and conflict audit. |
| Coverage profile consumer binding | source-refs/sanmopia_web/application/controllers/api/Service.php:47-103, source-refs/sanmopia_web/application/models/Reservation_model.php:2239-2301 | Coverage result immediately selects branch id and consultation flag, then later readers can reread current mutable branch rows. | BranchCoverageProfileDecision must be read by booking, second service address, extension, continuation, branch transfer, pricing, caregiver matching, refund, and settlement consumers from frozen evidence rather than current BRANCH_SERVICE_AREA_TB. |
Brand Assets And Storage
Section titled “Brand Assets And Storage”Source behavior:
- Profile image and stamp image upload replace the branch row path and delete
the previous file. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:435-594. - Office content image upload copies files across legacy upload folders and
returns CKEditor-style payloads. Evidence:
source-refs/sanmopia-admin/application/models/Office_management_model.php:12-151.
Modern gap:
BranchBrandAssetshould be Storage-backed with object key, checksum, content type, owner branch, supersession, delete marker, and audit.- Printable documents should reference a versioned stamp/profile asset captured at render time, not mutable branch row paths.
- Office content media should use Strapi/Supabase Storage asset references, not copied upload directories.
Login Audit
Section titled “Login Audit”Source behavior:
- Branch login audit records branch id, IP address, and coarse user-agent
browser/robot/mobile classification. Evidence:
source-refs/sanmopia-admin/application/models/Branch_model.php:610-645.
Modern gap:
BranchLoginAuditEventshould record auth provider subject, session id, result, IP, parsed device, user-agent, actor relation, and risk metadata.- Login audit is security evidence. It should not be optional side-effect code hidden in the branch model.
Office Content For Caregivers And Branches
Section titled “Office Content For Caregivers And Branches”Source behavior:
- Office management owns image upload, manager notices, education schedules,
manager request list/removal, central notices, branch-facing notices, event
notices, Sanmopia news, and FAQ. Evidence:
source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:454-1181andsource-refs/sanmopia-admin/application/models/Office_management_model.php:153-577. - Manager notice list includes branch-specific rows plus global rows, sorted by
fixed flag and created date. Evidence:
source-refs/sanmopia-admin/application/models/Office_management_model.php:153-175.
Modern gap:
OfficeContentPublicationshould be CMS-authored where possible. Branch/HQ target scope, fixed/pinned order, publish window, attachments, read tracking, and media references should be modeled explicitly.CaregiverOfficeRequestThreadshould own manager request list/view/remove behavior, separate from content publications.- FAQ/customer support content should live in the customer engagement content context, not branch profile operations.
Feature-Level Queue
Section titled “Feature-Level Queue”- Backend/contract:
BranchOfficeProfilecommand/read model with revision, duplicate-login conflict, branch lifecycle state, and audit. - Backend/contract:
BranchSettlementAccountwith masked account snapshot, verification state, andBranchSettlementAccountSnapshothandoff for settlement statements. - Backend/Supabase:
SupabaseBranchOfficeProfileRepositorypersists branch profile current state, revisions, purpose-scoped settlement accounts, membership-fee policies, and operator credential rotations in service-role tables. - Backend/contract:
BranchMembershipFeePolicywith branch scope and valid period; settlement calculation reads frozen fee policy. - Backend/contract:
BranchServiceAreaPolicywith address-depth rules, effective period, supersession, and lookup API. - Backend/contract:
BranchBrandAssetStorage lifecycle and render-time asset capture for documents. - Backend/security:
BranchLoginAuditEventfrom auth/session middleware. - CMS/backend:
OfficeContentPublicationvia Strapi with branch/HQ target scope and caregiver portal inbox projection. - Backend:
CaregiverOfficeRequestThreadseparate from office content CRUD.
2026-07-03 Checklist Expansion
Section titled “2026-07-03 Checklist Expansion”-
Branch profile command split by fact group. Status:
Partial. Evidence:source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:399-452,source-refs/sanmopia-admin/application/models/Branch_model.php:160-166, andsource-refs/sanmopia-admin/application/models/Branch_model.php:202-259. Source behavior: one office submit saves branch profile, login id/password, membership pay, email/address, phones, two bank account sets, and office status. Backend foundation now exists inbackend-repo/src/sanmopia_modernization/domain/branch_operations/features/office_profile/andbackend-repo/src/sanmopia_modernization/application/branch_operations/features/office_profile/. It splits profile, lifecycle, settlement-account, membership-fee, and credential-rotation changes into strict commands with caller-supplied ids, expected revision, actor, reason, and effective timestamps. 2026-07-04 update: settlement account purpose names now use the same contract vocabulary as branch operations (statement_remittance,branch_deposit,membership_fee, andrefund), and backend/application contracts can freeze a verified account revision intoBranchSettlementAccountSnapshotReadModelfor one settlement statement. Snapshot requests reject stale account revisions and unverified accounts, so statement generation does not read mutable branch account rows after the fact. 2026-07-04 runtime update:SupabaseBranchOfficeProfileRepositorynow persists the split read model throughsanmopia_branch_office_profiles,sanmopia_branch_office_profile_revisions,sanmopia_branch_settlement_accounts,sanmopia_branch_membership_fee_policies, andsanmopia_branch_operator_credential_rotations; stage migration, lint, and generated Supabase types were refreshed from the self-hosted stage database. The existingsanmopia_settlement_remittance_account_snapshotstable also gained nullable profile/account lineage fields for idempotent frozen statement-account evidence. Follow-up backend update addsBranchSettlementAccountSnapshotService,SupabaseBranchSettlementAccountSnapshotRepository, andPOST /branch-settlement-account-statement-snapshots; the API derivesactor_idfrom Supabase Auth, checks branchsettlepermission through SpiceDB, and persists replay-safe snapshots before settlement documents read account facts. Contract follow-up now adds the public OpenAPI path, JSON schemas, and TS helpersbranchSettlementAccountStatementSnapshotsPath()plusbranchSettlementAccountStatementSnapshotRequestHeaders(). Profile-edit follow-up addedPOST /branch-office-profile-updates; it derives the actor from Supabase Auth and checks caller-selected branchmanagethrough SpiceDB, but still accepts authoritativebranchOfficeProfileIdfrom the request. It is therefore a generic foundation, not the P02 self-scoped authority, and must be hardened or closed before actor acceptance. Contract follow-up adds OpenAPI, JSON schemas, and TS helpersbranchOfficeProfileUpdatesPath()plusbranchOfficeProfileUpdateRequestHeaders(). Lifecycle follow-up now addsPOST /branch-office-lifecycle-changes; it uses Supabase Auth actor derivation, branchmanage, expected revision, and the ubiquitous statesonboarding,active,suspended, andclosedinstead of raw source delete flags. Contract follow-up adds OpenAPI, JSON schema, and TS helpersbranchOfficeLifecycleChangesPath()plusbranchOfficeLifecycleChangeRequestHeaders(). Settlement-account replacement follow-up now addsPOST /branch-settlement-account-replacements; it accepts purpose-scoped, masked-only account facts, derives the actor from Supabase Auth, checks branchmanage, and commits asettlement_accountrevision with expected-revision conflict handling. Contract follow-up adds OpenAPI, JSON schema, and TS helpersbranchSettlementAccountReplacementsPath()plusbranchSettlementAccountReplacementRequestHeaders(). Remaining gaps: broader self-scoped invitation-activated actor resolution, atomic CAS/idempotency, field-level conflict diffs, retained-audit privacy/reaper policy, UI binding, and service-coverage runtime binding are not accepted yet. -
Service-area rule supersession. Status:
Partial. Evidence:source-refs/sanmopia-admin/application/models/Branch_model.php:291-343,source-refs/sanmopia-admin/application/models/Branch_model.php:345-433,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, andsource-refs/sanmopia-admin/application/models/Branch_model.php:597-608. Source behavior:AREA_ACCEPT_TYPE_ID_PKvalues1,2, and3represent possible, consultation-only, and impossible service areas, and consultation/impossible rows must fit under available-area hierarchy. The browser chooses address depth and prepares address-level fields before posting. Backend/contract update:BranchServiceAreaPolicynow has publish and decision HTTP APIs backed by the existing Supabase repository, semantic policy kinds, effective periods, revision metadata, address-provider evidence, and matched-rule decision output. Remaining gap: bind the decision to booking/extension snapshots, branch matching, pricing, branch-transfer overrides, and conflict audit so later branch rule edits do not rewrite reservation history. -
Branch profile stale-save and navigation safety. Status:
Partial. Evidence:source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:399-452,source-refs/sanmopia-admin/static/js/office_management/office_detail.js:253-276, andsource-refs/sanmopia-admin/application/views/office_management/office_detail.php:198-217. Source behavior: one form submit can overwrite branch profile, lifecycle state, membership fee, account fields, and credential fields without a revision predicate, then mixes page reload and browser history navigation. Backend foundation now rejects staleexpected_revisionvalues and returns a public branch-office read model frombackend-repo/src/sanmopia_modernization/application/branch_operations/features/office_profile/. Remaining gaps: durable conflict records, field-level diff response, persisted audit store, API error mapping, route/read-model response contract, and UI stale-page recovery. -
Branch asset render snapshot. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/models/Branch_model.php:430-594andsource-refs/sanmopia-admin/application/models/Office_management_model.php:12-151. Source behavior: profile image, stamp image, and content uploads replace mutable file paths and delete previous files. Modern gap:BranchBrandAssetandOfficeContentMediaAssetshould preserve previous Storage object, checksum, uploader, supersession reason, and render-time asset snapshot for documents that include branch profile/stamp images. -
Login audit report alignment. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/models/Branch_model.php:610-645andsource-refs/sanmopia-admin/application/models/Statistics_model.php:1971-2137. Source behavior: branch login log is both security evidence and report source for branch connect analytics. Modern gap:BranchLoginAuditEventshould feedAdminAnalyticsCatalogthrough a read-model projection so security audit fields and aggregated report columns stay consistent. -
Office-authored content and request routing. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/controllers/OfficeManagement.php:454-1181,source-refs/sanmopia-admin/application/models/Office_management_model.php:153-577,source-refs/sanmopia-manager/application/controllers/User.php:398-439, andsource-refs/sanmopia-manager/application/models/User_model.php:308-321. Source behavior: manager notices, education schedules, HQ notices, branch notices, event notices, Sanmopia news, FAQ, manager requests, and manager-to-HQ request alerting sit behind office management. Modern gap:OfficeContentPublicationneeds target audience, branch scope, fixed/pinned order, publish window, media ids, and read projection;CaregiverOfficeRequestneeds request state, reply, notification delivery, and support audit.