Catalogue & Contracts Back-office PRD
日常业务与测试阅读请使用Catalogue 中文业务 PRD。本页保留工程细节、历史变更与取数参考;已知冲突见实现记录。
Canonical product contract · Confirmed through 2026-09-03 · Owner: Operations / Finance / Insurance / Platform
1. Outcome
Catalogue & Contracts is the governed maintenance
centre for commercial master data consumed by Consultation, Billing,
Packages, Benefits and Inventory. It replaces the placeholder
admin-master-data surface while retaining that App ID.
The current iteration changes the frontend structure and may adapt existing APIs, but must not introduce or migrate underlying catalogue pricing tables. Every missing persistence model is recorded in the Backend Adaptation Ledger in section 12 and must not be presented as completed.
2. Confirmed navigation
- Service Items
- Lab & Imaging
- Products
- Consultation Pricing
- Service Packages
- Coupons
- Insurance Cards
- Other Settings
- Change History & Controls
Fees & Offerings is hidden from current-phase
navigation. Its code and data remain available for compatibility and
migration analysis; it is not repurposed as a Discount page and is not
deleted.
There is no Overview or explanatory bridge page.
Insurance & Benefit Programmes opens card management
directly.
3. Scope and ownership
| Area | Current-phase behavior | Delivery state |
|---|---|---|
| Service Items | Domain-specific list/editor, filters, shared List Price/Cost, clinic availability multi-select | Frontend implemented; clinic availability persistence pending |
| Lab & Imaging | One Test Catalogue; click Test Item to inspect Vendor mappings and Cost Schedule | Frontend implemented; mapping/cost persistence pending |
| Products | Domain-specific list/editor, filters, shared List Price/Cost, clinic availability multi-select | Frontend implemented; clinic availability persistence pending |
| Consultation Pricing | One doctor Standard Price, Clinic exceptions and Insurance rates | Frontend implemented; all price persistence/resolution pending |
| Service Packages | Open authoritative Package Setup | Existing workspace; bulk-import requirement to be added there |
| Coupons | Campaign CRUD/lifecycle and Excel import | Foundation implemented; Cashier redemption pending |
| Insurance Cards | Direct card gallery, single-card create/edit, private front/back image upload and source preview; insurance cards reference Policy while enterprise cooperation cards reference eligible Benefit Contracts | Implemented on existing policy_card plus metadata
reference |
| Policies | Manage Policies opens a wide in-page Drawer; simple
create/edit/import |
Implemented on existing policy_contract |
| Complex Benefit Contracts | Administration workspace retained but hidden; existing
corporate_membership / staff_benefit records
are selectable from Insurance Card master |
Advanced administration deferred; source selection included |
| Patient-held insurance | Patient & eMR owns member number, personal policy number, validity, primary flag, evidence and notes | Existing patient-scoped feature; not duplicated here |
4. Shared catalogue rules
4.1 Canonical identity
Service, Lab, Imaging and Product share
clinic_service_items.id. item_kind controls
the domain view:
service— professional service or procedure;lab— laboratory Test Item;imaging— imaging Test Item;product— non-drug physical product.
Drug Master remains separate because dosage, route, interaction, controlled-drug and dispensing rules are materially different.
4.2 Lifecycle and historical integrity
- New records are Draft.
- Publish and deactivate require a reason and separate permission.
- A used record is deactivated, not hard-deleted.
- Signed Consultation, sold Package, Benefit quote, order and Invoice retain the item, applied price and resolver snapshot captured at the time.
- This phase does not require second-person approval.
4.3 List Price and Cost
List Price and Cost are separate concepts and separate columns:
- List Price is the patient-facing reference selling price.
- Cost is the internal cost basis.
For Service and Product, both values are unified across clinics. Clinic selection controls availability only and cannot override either price.
For Lab & Imaging, List Price is unified per Test Item. Cost varies by Vendor, Clinic and Doctor according to section 6; Clinic never changes List Price.
5. Service Items
5.1 List
Columns:
Service Code, Service Name,
Category, List Price, Cost,
Available Clinics, Status,
Actions.
Filters:
- search by Service Code or Service Name;
- Category;
- Available Clinic;
- Status.
5.2 Editor
Fields include Service Code, Service Name, Category, List Price, Cost, Available Clinics, Description and lifecycle metadata.
Available Clinics is a searchable multi-select dropdown with checkbox
rows, All Clinics, select-all and clear behavior. A new
Service defaults to All Clinics. An explicit empty selection means
unavailable everywhere; it must never be silently interpreted as All
Clinics.
6. Lab & Imaging — Test Catalogue
6.1 Page structure
The area is one Test Catalogue page. The previous
four-tab proposal is superseded. Request Form Mapping is
removed from this workspace.
The main table has one row per canonical Test Item:
Unique Code, Test Item Name,
Category, Type, List Price,
Vendors, Cost Coverage, Status,
Actions.
Filters cover search, Category, Lab/Imaging type, Vendor and Status.
Lab & Imaging Test Items are available to all clinics by definition, so this area has no Available Clinics field or filter.
6.2 Test Item detail Drawer
Clicking a row opens a wide right-side Drawer. It is a detail surface, not a second table permanently squeezed beside the catalogue.
The Drawer contains:
- Basic Test Item information and unified List Price.
- Vendor & Code Mapping:
Vendor,Vendor Item Name,Vendor Code,Vendor Default Cost, lifecycle/effective information. - Clinic / Doctor Cost Schedule: Vendor, Clinic, optional Doctor, Cost, effective information and Status.
One Test Item may be offered by multiple Vendors. Vendor Code and Vendor Cost belong to a specific Vendor mapping, not to the canonical Test Item row.
6.3 Diagnostic cost key and resolution
The Cost key is:
Test Item + Vendor + Clinic + Doctor(optional).
Resolution order:
- Doctor + Clinic + Vendor;
- Clinic + Vendor;
- Vendor Default Cost;
- Cost unavailable.
Different Clinics cannot define a different List Price. Different Clinics and Doctors may define a Cost override only.
7. Products
Columns:
SKU / Code, Product Name,
Category, List Price, Cost,
Available Clinics, Inventory Tracking,
Status, Actions.
Filters cover search, Category, Available Clinic, Inventory Tracking and Status.
The editor supports SKU, barcode, unit of measure and Inventory Tracking in addition to the shared fields. Availability uses the same searchable checkbox multi-select as Service Items. Quantity, lots, purchasing and stock movement remain in Inventory.
8. Consultation Pricing
8.1 Main list
The label is Consultation Pricing, not
Doctor Consultation Fees.
Columns:
Doctor, Standard Price,
Clinic Exceptions, Insurance Rates,
Status, Actions.
Every doctor has one Standard Consultation Price. It is the doctor's base consultation price across consultation types and clinics.
8.2 Exceptions
- A Doctor may have a Clinic-specific exception for the same consultation price.
- An Insurance rate is keyed by Doctor + Insurance Card/Policy and applies to all clinics by default.
- A Doctor + Insurance rate may optionally have a Clinic-specific exception.
Resolution order:
- Doctor + Policy + Clinic;
- Doctor + Policy;
- Doctor + Clinic;
- Doctor Standard Price;
- Default consultation price: HKD 1,000.00 (confirmed 2026-09-08).
The current backend has no authoritative model or Billing resolver for this contract. All edit/import actions in this section remain explicitly marked pending until section 12 is implemented.
The executable interim fallback is in ordinary Consultation Billing & Finish: an unbound Visit receives the server-owned HKD 1,000.00 default, while an explicit valid Service Offering remains authoritative, including a zero price. An explicitly bound invalid/expired/cross-clinic/non-HKD Offering still requires correction; other charge lines never inherit this default. The applied default code/version is retained in existing signed snapshots, and signed history is not repriced. This does not implement the pending doctor-standard and Policy/Clinic exception tables or their editing/import UI.
9. Coupons
Coupon Campaign supports code/name, discount type/value, minimum spend, optional maximum discount, dates, redemption limits, target, description and stacking.
Coupon does not stack with a Contract or Benefit discount unless
stackable = true is explicitly configured.
Campaign maintenance and import do not imply Cashier redemption is integrated. Cashier must eventually store the applied campaign/version and reversal history.
10. Insurance Cards and Policies
10.1 Insurance Card master
Card master inherits the existing policy_card
mapping:
- Insurance Company →
provider_name; - Card Name →
name; - Plan Name →
plan_name; - Card Class →
clazz; - Status →
status; - linked Policy →
contract_id; - programme type →
metadata.programme_type(insurance,corporate_membership, orstaff_benefit); - linked enterprise cooperation plan →
metadata.benefit_contract_uuidreferencingbenefit_contracts.contract_uuid; - front/back card face →
image1/image2.
The source contract is mutually exclusive by programme type:
insurancemay link the existingpolicy_contractthroughpolicy_card.contract_id;corporate_membershipmust link a Benefit Contract whosecontract_type = corporate_membership;staff_benefitmust link a Benefit Contract whosecontract_type = staff_benefit.
Corporate membership and Staff benefit are the only enterprise
cooperation types available here. Promotion and Insurance-type Benefit
Contracts are not selectable for this purpose. The card master stores
only the source identity in metadata; contract rules,
plans, eligibility, limits and utilization remain authoritative in
Benefit Contracts and are not copied into policy_card.
Existing cards without metadata.programme_type are
interpreted as insurance for backward compatibility.
The page renders a card-face gallery even when the list is empty, with a real New Card action. It never inserts demo cards.
Front/back JPEG, PNG or WebP files up to 5 MB can be uploaded,
previewed, replaced or removed. Bytes are stored in the existing private
file registry and the resulting private file URL is written to
policy_card.image1/image2.
Insurance Card does not provide bulk Excel import. A usable card requires reviewed card-face files and an intentional Policy link, so Card creation and maintenance remains one record at a time. Policy master data may still be imported in bulk.
10.2 Policy management
Manage Policies opens a wide Drawer on the Insurance
Cards page. The current phase supports simple Policy number/name,
effective/expiry dates, holder type, contract type, insurance type and
Billing visibility on the existing policy_contract
table.
The card detail displays one source preview: linked Policy for an Insurance card, or linked Benefit Contract code/name, sponsor, type, effective period and status for Corporate membership / Staff benefit. Complex benefit rules, caps, beneficiaries, rotating QR, utilization and advanced pricing remain hidden.
11. Bulk Excel import — shared product contract
Every bulk-import-eligible area that creates master data must provide the following contract. Insurance Card is the confirmed exception in this phase because each card requires image and Policy-link review:
The list page exposes one compact secondary Bulk import
launcher only. It is placed in the page-title header at the upper right,
immediately before the page's primary New … action; the
stable action order is Refresh → Bulk import → New …. A
Policy-management Drawer applies the same rule to its Drawer header.
Template download and file selection must not remain as large permanent
page actions, separate body rows or controls inside a search/filter
toolbar. The launcher opens the platform-standard right-side Bulk Import
Drawer containing the complete import flow, so search, filters and
record tables keep visual priority. This is a shared
administrator-maintenance pattern, not a rule imposed on clinical or
task-oriented pages: every approved executable admin import must reuse
the shared Drawer component, while pending persistence models may only
show a clearly non-executable requirement preview in the same pattern.
Insurance Card itself has no launcher; the separate Policy manager may
use the shared Drawer.
Download Excel Templatespecific to that module;- upload
.xlsxor.csv; - full-file validation before write;
- preview with Pass / Warning / Error counts and sample rows;
- explicit duplicate policy (
UPDATEorSKIPwhere permitted); - user confirmation before execution;
- batch execution with partial-success reporting;
- row-level validation messages and downloadable error detail;
- refresh of the list after success;
- import job audit with actor, permission, time, source filename, result counts and status.
Imports create Draft records unless the target model has an explicitly approved alternative. Import does not bypass publish permission or lifecycle rules.
| Module | Template/import state |
|---|---|
| Service Items | Executable for item fields; Available Clinics column pending clinic mapping model |
| Lab & Imaging Test Items | Executable for canonical Test Item fields |
| Diagnostic Vendor Mapping | Required with vendor-mapping model; currently pending |
| Diagnostic Cost Schedule | Required with cost-schedule model; currently pending |
| Products | Executable for item/product fields; Available Clinics column pending clinic mapping model |
| Consultation Pricing | Required with doctor-pricing model; currently pending |
| Service Packages | Required in authoritative Package Setup; current Catalogue link does not duplicate it |
| Coupons | Executable for Campaign draft fields |
| Insurance Cards | No bulk import by confirmed exception; create/edit one card at a time with card-face review |
| Policies | Executable for current-phase policy_contract
fields |
Template download requires read permission. Preview and execute
require the corresponding non-assumed manage permission. A disabled or
missing persistence model must display
Backend adaptation pending; it must not simulate a
successful import.
12. Backend Adaptation Ledger
This ledger is part of the approved product contract.
Current describes verified reuse;
Required / Recommended is future work and is not
authorized for schema implementation in this frontend
iteration.
| Area | Current table / behavior | Required adaptation | Design recommendation / migration note | Current state |
|---|---|---|---|---|
| Canonical Item | clinic_service_items stores Service/Product/Lab/Imaging
identity and several specialized fields |
No immediate split required | Retain one billing identity; later move Product-only attributes to a 1:1 detail table only if schema ownership becomes unclear | Reused |
| Service/Product availability | UI can infer clinic coverage from
clinic_service_offerings, but Offering mixes routing and
price |
Persist explicit many-to-many availability | Add
clinic_item_availability(service_item_id, operating_unit_id, is_available, effective_from/to, status, audit fields)
plus explicit all-clinics semantics; migrate active Offering coverage,
manually review conflicts |
Pending; UI-only |
| Service/Product pricing | clinic_service_items.list_price/default_cost |
Prevent Clinic overrides in new contract | Keep one value per item; remove pricing responsibility from new availability mapping | Reused with rule change |
| Diagnostic Vendor mapping | Legacy clinic_service_offerings partially contains
vendor/code and Clinic-linked price |
Dedicated Vendor Item mapping | Add
diagnostic_vendor_items(service_item_id, vendor_id, vendor_item_name, vendor_code, default_cost, currency, effective dates, status, audit fields);
unique active vendor/code constraint |
Pending |
| Diagnostic Cost Schedule | No authoritative Doctor/Clinic cost model | Effective-dated Cost overrides | Add
diagnostic_cost_schedules(vendor_item_id, operating_unit_id, doctor_user_id nullable, cost, currency, effective dates, status, audit fields);
Doctor row requires Clinic; enforce non-overlapping active periods |
Pending |
| Diagnostic resolver | No confirmed resolver | Implement precedence from section 6.3 and expose trace | Resolver must return winning row, reason and version; orders/invoices snapshot outcome | Pending |
| Request Form Mapping | Previous UI proposal only | Remove from this workspace | If later required for ordering, design it under V-Lab request/form configuration, not Catalogue pricing | Removed / deferred |
| Consultation Standard Price | Current Visit pricing binds an Offering; no doctor standard table | Persist one standard per Doctor | Add
doctor_consultation_prices(doctor_user_id, price, currency, effective dates, status, audit fields) |
Pending |
| Consultation exceptions | No Clinic/Insurance exception model | Persist Clinic, Policy and Policy+Clinic overrides | Add
doctor_consultation_price_exceptions(doctor_user_id, policy_contract_id nullable, operating_unit_id nullable, price, currency, effective dates, status, audit fields)
with valid-scope and non-overlap constraints |
Pending |
| Consultation resolver | No resolver matching section 8.2 | Implement and integrate with Consultation/Billing | Return winning rule and fallback trace; Invoice snapshots doctor/policy/clinic inputs, row/version and final price | Pending |
| Policy | Existing policy_contract supports simple current-phase
fields |
No schema change now | Later add explicit lifecycle status/version/audit instead of relying
on delete_flag; keep Benefit rule structures separate |
Reused; recommendation logged |
| Insurance Card | Existing policy_card supports directory/basic fields
and Policy link; JSONB metadata supports a governed Benefit
Contract reference |
No card schema change or bulk import | Keep private images in file registry; use mutually exclusive Policy or typed Benefit Contract source and require one-card review | Reused / adapted |
| Legacy Fees & Offerings | Existing code/data may still be operationally referenced | Hide UI now; inventory dependencies before deprecation | Make read-only migration report, map routing/availability/vendor fields to their target models, then deprecate only after resolver consumers are cut over | Hidden, retained |
| Excel jobs | Shared import framework provides template/preview/execute/job result | Add task schemas/callbacks as each pending model lands | Same framework, permission wrapper and error workbook; do not create one-off parsers | Partly implemented |
| Package import | Owned by Service Packages app | Add component/template/callback in that app | Import package header + component rows transactionally; validate canonical item codes before write | Pending owner work |
| Billing snapshots | Existing downstream snapshots are incomplete across new resolvers | Persist applied version and trace | No historical repricing after master-data change | Pending integration |
12.1 Required rollout order
- Approve table design and constraints separately.
- Create migrations and import callbacks.
- Backfill legacy Offerings into new availability/vendor/cost models with reconciliation reports.
- Implement resolvers with unit and precedence tests.
- Run dual-read comparison without changing charges.
- Cut over write/read consumers by feature flag.
- Verify Billing snapshots, import audit and rollback path.
- Only then remove
Backend adaptation pendinglabels or deprecate legacy Offering paths.
13. Permissions and audit
| Permission | Purpose |
|---|---|
catalogue.read |
View catalogue and download Service/Product/Test templates |
catalogue.manage |
Create/update drafts and execute Service/Product/Test imports |
catalogue.publish |
Publish/deactivate catalogue items |
coupon.read |
View campaigns and download Coupon template |
coupon.manage |
Create/update Campaign drafts and execute imports |
coupon.publish |
Publish/deactivate Campaigns |
benefits.read |
View Insurance Cards/Policies and download templates |
benefits.manage |
Create/update Cards/Policies, upload images and execute imports |
Assumed/restricted support roles cannot mutate, publish or execute imports. Role, permission, test-account and membership bootstrap data must not be deleted while simplifying this workspace.
14. Acceptance criteria
- The nine confirmed navigation entries and ordering match section 2; Fees & Offerings and complex Benefit Contracts are hidden but retained.
- Service and Product show separate List Price and Cost, confirmed filters and searchable checkbox Clinic multi-select.
- No Clinic can change a Service/Product List Price or Cost.
- Lab & Imaging renders one Test Catalogue and one detail Drawer; Request Form Mapping is absent.
- Test Items have unified List Price and all-clinic availability; Vendor/Clinic/Doctor variation applies only to Cost.
- Consultation Pricing shows every Doctor with Standard Price, Clinic exception and Insurance rate structure, while pending persistence is explicit.
- Insurance Cards opens direct card management, supports private front/back upload, exposes Insurance policy / Corporate membership / Staff benefit types, and shows the matching Policy or typed Benefit Contract source preview.
- Policies are created/edited/imported from an in-page Drawer using
existing
policy_contract; there is no new top-level Policy page. - Service, Test Item, Product, Coupon and Policy each provide one compact Bulk import launcher in the page/Drawer header right, immediately before the primary New action, and open a right-side Drawer with module-specific template download and preview/validate/confirm import. No duplicate launcher remains in the page body or filter toolbar. Insurance Card is the confirmed exception and is maintained one card at a time.
- Pending models show pending import status rather than fake data or fake success.
- Publish/deactivate reason, permissions, import audit and historical snapshots follow this PRD.
- PRD publication, application deployment, end-to-end integration and UAT are reported separately.
- Corporate membership and Staff benefit cards cannot save without a
matching
Benefit Contract; changing type clears the incompatible Policy / Benefit Contract reference instead of keeping two active sources.
15. Delivery status at this revision
- Implemented / adapted without new tables: Service/Product/Test Item lists; confirmed filters; clinic multi-select UI; one Test Catalogue Drawer; hidden Fees & Offerings; Consultation Pricing review UI; direct single-card Insurance Card management with mutually exclusive Policy or typed Benefit Contract source; card image upload; simple Policy Drawer/CRUD; compact Bulk import launchers and the shared right-side template/preview/confirm Drawer for Service, Test Item, Product, Coupon and Policy; permission-protected import routes.
- Existing and reused: canonical catalogue items,
Coupons,
policy_card,policy_contract, Package Setup, Patient Insurance Cards, Drug Catalogue, Users, Units, Templates, Workflow settings and shared Excel engine. - Explicitly pending backend: clinic availability mapping, Diagnostic Vendor mapping, Diagnostic Cost schedules/resolver, Consultation pricing tables/resolver, Package import and downstream Billing integration.
- Retained but hidden: Fees & Offerings UI/code/data and complex Benefit Contracts administration; existing Corporate membership / Staff benefit contracts remain readable as governed card sources.
- Not claimed: database redesign, production deployment, end-to-end pricing cutover or UAT acceptance.
Insurance & Benefit Programmes
名称、卡片详情、双语说明、HTML净化与来源边界统一见 Catalogue 业务 PRD。本文件仅补充底表与迁移设计,不重复维护后续界面变更或发布流水。