Skip to content

UI prototype → gap list against current state and alignment plan ​

Reference baseline: docs/design/mockups (tokens.css is the single style source for all five clients: the default "graphite × antique gold" theme is taken from the logo #666666/#C7A674, with three switchable primary colors via data-theme="pine|cinnabar|indigo"; rice-paper background --paper, gold-amber accent --amber, serif headings --serif).

Agreed conventions:

  • P0 = high-frequency core-path screens (patient records, treatment entry, global shell theme), aligned pixel-perfect first; P1 = remaining implemented screens aligned one by one; prototype only = planned features, no implementation scheduled.
  • Gaps are grouped into four categories: layout / color scheme / components / copy.
  • The data support column marks whether each information block in the prototype has a real interface behind it: ✅ directly usable; ⚠️ requires frontend aggregation; ❌ no interface. Blocks marked ❌ are rendered as a "planned feature" placeholder or not rendered at all during implementation — no fake data (same warning convention as app.md).
  • Each screen is committed independently once done (titles in the style feat(web): patient record aligned to prototype), with local dev server screenshots compared against the prototype images in docs/public/screenshots/ before committing.

tokens single-source plan ​

ClientCurrent stateApproach
web admin consoleantd default blue #1890ff (config/defaultSettings.ts:12), system sans-serifCopy tokens.css → web/src/tokens.css and import it in global.less; align defaultSettings colorPrimary with the ProLayout token to --brand; switch headings/numbers to --serif
desktop shellThe bootstrap page desktop/ui/index.html carries its own stylesSync the tokens variables and serif headings
app mini programSingle-page index.css with scattered variables like --brand-color #c7a674Import tokens.css, delete the scattered variables and reference the new ones
KioskDark green #2f6d4f / #f6f5f1 backgroundThe values happen to equal the pine theme → apply data-theme="pine" on the whole page, import tokens.css, replace the scattered hex values
tablet display screenDark #101613 + #ffce54Import tokens.css, keep the graphite base with --amber accents, replace scattered hex values

1. Admin console web (screen by screen) ​

#Screen (prototype)Current stateMain gapsData supportLevel
1Global shell + theme (per the appearance prototype)ProLayout mix layout, antd blue, white backgroundColor: primary/selected/links all switch to the --brand family; Layout: mix → pure sidebar (layout: 'side'), white sidebar with rounded selected block; Components: logo slot replaced by a 养-character square badge + serif clinic name; Copy: Youngs.fun → clinic name subtitle✅P0
2Patient record (admin-customer)Customer/Update bare ProForm with 7 fields stacked verticallyLayout: full-page rework = header card (avatar/name/level chip/meta info + actions) + 4 stat cards + left "treatment records" timeline + right "held cards" rail; Components: serif name/numbers, gold-amber level chip, timeline date blocks, remaining-visit progress bar, follow-up reminder note block; Copy: 「年纪」→「年龄」 etc.Name/gender/age/phone/level/birthday ✅; treatment records ✅ queryTreatByPage({customerId}); cumulative spend ✅ GET /api/v1/order/summary (fetchOrderSummary, sum of completed order prices); held cards/remaining visits ✅ already implemented (customer_cards + /api/v1/card, the patient page renders the remaining-visit progress bar); next follow-up ❌ no per-patient query → "planned" placeholderP0
3Treatment entry = desktop workstation (desktop-workstation)Treat/Create single-column long ProForm (four examinations full-width textarea, five-phase nested ProCards)Layout: patient header card + four examinations 2×2 + pulse grid for left/right hand (cun/guan/chi) + point selection left/right + diagnosis (pattern + course) + five-phase diagram + treatment plan entries + right rail (history lookup / print prescription note entry); Color/Components: card-based panels, label chips, serif section headings; Copy: labels aligned to prototype (chief complaint / inquiry / inspection / palpation / pulse / point selection / diagnosis / treatment plan)All Treat fields ✅; patient header card ✅ fetchCustomerById; history lookup ✅ queryTreatByPage; print prescription note ⚠️ the print template pipeline (D7) can be attached; order entry/card write-off ❌ no order creation interface in the treatment flow → "planned" placeholder; scan-to-enter ✅ existing ScannerInput/scan flowP0
4Daily report (admin-dash-day)Dashboard/Day three ProCard tablesLayout: 4 stat cards on top + left staff summary table + right last-7-days bar chart + today's follow-ups/today's summary; Color/Components: status pill (submitted/not filled), reminder buttons, serif numbers; Copy: title carries the date + ‹previous day› navigationStaff summary/today's follow-ups/today's summary ✅ three interfaces already in use; on-duty/treatment/new-patient counts ❌ no aggregation interface → deferred or "planned"; last 7 days ⚠️ aggregate by per-day treat queries (interface params support startTime/endTime)P1
5Service card management (admin-items)Item/Query ProTable (name/price/description/time)Layout: tip banner + cover column + publish toggle column + sort column + pagination; Components: first-character cover chip; Copy: tip matches the prototypeName/price/description ✅; cover/publish/sort ✅ the entity already has them (ItemEntity cover/enabled/sort, ItemView persists enabled=1/sort=0 by default; only the web typings.d.ts is out of sync) → P1 can genuinely implement toggle/sort/cover without placeholdersP1
6Order management (admin-orders)Order/Query ProTable with status Tag in blue/orange/green/grayLayout: tab status filter (existing valueType select → change to tabs or keep); Color: Tags switch to theme-colored pills; Copy: 「已下单」→「待接待」 to match the prototype and the Kiosk agreed policyOrder number/patient/item/amount/status/accept-complete-cancel ✅ all realP1
7Advertising screen (admin-ads)`Ads/MaterialsSchedulesScreensCalls` four ProTable pages
8Retrospective / follow-up / staff / loginNo dedicated prototypeOnly passively aligned with the global shell theme (#1); no dedicated screen work✅P1 (passive)
9Appearance settings (admin-appearance)No such pageCreate a "system/appearance settings" page: 4 theme cards to choose from → write to localStorage → runtime switch of data-theme + antd tokens + live preview areaPurely frontend ✅P1
10Appointment scheduling / Chinese medicine prescriptions / billing and settlementNone → appointments/settlements domains already builtAppointment scheduling already implemented (5910aee/f04cf62: appointments table + /api/v1/appointment + /appointment/* list workbench + /appointment/schedule physician×slot scheduling grid); billing and settlement already implemented (26d6419/9d1e018: settlements table + stored-value ledger deductions + /api/v1/settlement, /api/v1/recharge + /billing/settle settlement counter; visit card deduction 81aaf5e, receipt printing f57f10d already implemented, prescription herb lines still design-only); Chinese medicine prescriptions already implemented (f91add1/0ee1d1d: prescriptions/prescription_items tables + /api/v1/prescription + /prescription/* for writing and querying prescriptions, herbs as free text; compatibility review 7100355, pricing 60e818c, decoction-service pickup 141f295 already implemented)Appointments ✅ / prescriptions ✅ / billing ✅All three are implemented; prescription inventory (herb in/out) remains planned

2. Desktop workstation (desktop shell) ​

#ScreenCurrent stateMain gapsLevel
1Treatment entry main screenLoads the web /treat/createSame as web screen 3; lands with P0; the ScannerInput scan-to-locate bar aligns with the pageP0 (same as web)
2Shell bootstrap page ui/index.htmlOwn light styles, peripheral self-check buttonsAlign colors/serif headings with tokens; unify the display style of peripheral status (receipt printer/scanner)P1

3. Mini program app ​

Four-tab structure (Home / Appointments / Cards / Mine); page registration and tabBar in app/src/app.config.ts; the white-label self-check has been moved from Home to the bottom of the "Mine" page. The table below records "current state" per the current implementation; unimplemented parts still list gaps as before, with no fake data.

#ScreenCurrent stateMain gapsLevel
1Home (mobile-home)Implemented per the prototype: brand header / appointment banner (entry → pages/booking) / "today's wellness" grid (actually fetches GET /api/v1/kiosk/items) / "my cards" (entry linked to pages/card; devices that have looked up cards echo name + card count + lookup date locally for 7 days) / wellness tips; colors consolidated into tokens (src/tokens.css, same source as mockups)No layout/color gaps; card purchase and remaining-visit details remain planned — remaining visits are only shown on the card page after verification, and the plaintext is never stored on the deviceP1 (aligned)
2Self-service booking (mobile-booking)Implemented: date strip (7 days from today) → time-slot grid (9:00–17:00 on the hour, past slots today disabled) → item selection (actually fetches published items, "decide at store" allowed) → phone/name submission → receipt shows appointment number and slot; goes through the unauthenticated write entry POST /api/v1/kiosk/appointments; policy in app-booking designNo layout gaps; slot occupancy lookup is not implemented — the server does not query occupancy, conflicts are reconciled by staff in person (noted on the same page)P1 (implemented)
3Card lookup by phone (mobile-card)Implemented: phone number + SMS verification code two-factor (POST /api/v1/kiosk/sms-code → card-lookup); weapp additionally wires wx.login → openid-session to exchange for an openid and takes the openid_only path (openid kept in memory only, silent fallback to the SMS path if the exchange fails, H5 uses SMS only); driven by the three-state switch sinomed.phone-lookup.mode, default off (disabled); every attempt including rejections lands in the card_lookup_logs auditopenid self-binding already implemented (POST /api/v1/kiosk/openid-bind; under openid_only the user proves their phone with a code then binds to the patient record, idempotent on the same record, rejected if already bound to another record; activation still lacks WeChat credentials, see the blocker section in the repo-root todo.md); no login sessionP1 (implemented)
4Mine (mobile-me)Implemented: local appointment summary + cross-device appointment lookup (phone + verification code as a one-time two-factor via POST /api/v1/kiosk/my-appointments, returns unexpired not-yet-arrived appointments, honestly returns empty when there are none) + self-service cancellation of not-yet-arrived appointments (POST /api/v1/kiosk/appointment-cancel, re-issuing a code then cancelling; decided on the user's behalf 2026-10-16) + theme switching (rendered on H5 only) + white-label self-check at the bottom of the pageLogin sessions (session persistence) are not implemented — unauthenticated phone number lookup would cause horizontal data exposure, beyond the demo MVP scopeP1 (implemented)

4. Kiosk ​

#ScreenCurrent stateMain gapsLevel
1Service selection (kiosk-menu)The menu page already has a service list + shopping basket structureColors are already dark green (=pine) → switch to tokens variables to consolidate scattered hex; serif headings, rice-paper background, gold-amber price tags/accents; basket/checkout button details aligned to the prototypeP1
2Order receipt (order)ExistsAligned globally per aboveP1

5. Display screen tablet ​

#ScreenCurrent stateMain gapsLevel
1Carousel + queue calling (tablet-screen)The player carousel + call queue-calling bar are implementedDark background switched to the tokens graphite base, h1 em in gold-amber, serif headings, 王* masking on the queue-calling bar (implemented, to be verified); page number/progress detailsP1

Execution order ​

  1. P0 web global shell theme (tokens.css landed in web + colorPrimary/ProLayout aligned) → 2. P0 patient records → 3. P0 treatment entry (including the desktop workstation main screen) → 4. P1 orders → 5. P1 service cards → 6. P1 daily report → 7. P1 advertising screen → 8. P1 appearance settings (theme switching implemented) → 9. P1 Kiosk → 10. P1 tablet → 11. P1 app → 12. P1 desktop bootstrap page.

Per screen: change the implementation → pnpm build/lint passes → local screenshots compared against prototype PNGs → independent commit (title in the style feat(web): patient record aligned to prototype, body listing layout/color/components/copy changes with evidence) → fetch+rebase → push.

Progress ​

  • [x] P0-1 web global shell theme alignment (bd72bae)
  • [x] P0-2 patient record alignment (fc84cfa)
  • [x] P0-3 treatment entry (desktop workstation main screen) alignment (42e567b; the desktop shell shares the page)
  • [x] P1-4 order management alignment (f42e0ee)
  • [x] P1-5 service card management alignment (8d4b77f)
  • [x] P1-6 daily report alignment (1c72d5b, same batch as fix(server): paginated date filtering switched to Date comparison b8dc2a8)
  • [x] P1-7 advertising screen four pages aligned (bcb6b1c; four pages merged into one page with 4 tabs + right rail, old routes redirected)
  • [x] P1-8 appearance settings (8591447; runtime theme switching: data-theme CSS variables + useAntdConfigSetter injecting a plugin into ConfigProvider + settings.colorPrimary/sidebar tokens, delivered in three layers)
  • [x] P1-9 Kiosk alignment (eccf091; tokens.css copied from the same source + index.html mounting data-theme="pine"; hero/three-step capsules/gradient initial-character cards/two-row basket aligned to the kiosk-menu prototype, all scattered hex values consolidated into variables)
  • [x] P1-10 tablet display screen alignment (2caa81f; tokens.css copied from the same source; graphite base --ink, paper-colored rounded slide cards with real materials embedded, pager dots showing real progress, queue calling changed from a full-screen overlay to the prototype's bottom bar = gold-amber label + masked number + serif room name, auto-recesses after 15s)
  • [x] P1-11 app mini program Home alignment (9566fdb; mobile-home layout: brand header/appointment banner/today's wellness/my cards/wellness tips/white-label self-check at the page bottom; the grid actually fetches published items, appointment and card remaining visits honestly shown as "planned feature" pills+note; tokens.css imported from the same source, --brand-color white-label injection retained)
  • [x] P1-12 desktop shell bootstrap page alignment (5e656b7; grep re-check confirms zero residual scattered colors across all five clients, --brand-color white-label injection mechanism retained)
  • [x] P1-13 prescription compatibility review + herb dictionary/pricing (8e3402f/0144ba0; eighteen incompatibilities/nineteen antagonisms static rule hints without blocking; herbs dictionary + real-time prescription trial calculation, unlisted herb names are not billed)
  • [x] P1-14 card entity (visit card) and settlement deduction (81aaf5e; customer_cards + /api/v1/card for issuing/deactivating cards, payType 5 deducts 1 visit with 0 actual income; the patient page's card rail and the settlement counter's visit card option implemented)
  • [x] P1-15 settlement receipt printing (f57f10d/1b33703; GET /api/v1/print/receipt/{settlementId} server-side templated 80mm HTML: order number S+settlement id, the visit card deduction amount column shows 「次卡抵扣」, names HTML-escaped; the settlement counter offers a print entry after successful payment and recent settlements can be reprinted; templates can still be overridden via data/printtemplates)
  • [x] P1-16 prescription decoction-service pickup (141f295/47e4828; prescriptions +decoction_status/decoction_bags, prescriptions with decoction=true set bag count = number of doses and land in 「待煎」; PUT /prescription/{id}/decoction?status= moves one-way from 待煎→可取→已取, regressions/skips/no decoction selected → 400; service fee ¥3/bag computed in real time and shown as a hint only; the prescription page has a checkbox, the query page a status column + drawer flow buttons; rush and pickup window not implemented — demo scope)
  • [x] P1-17 prescription herb lines entering settlement — ruling: not implemented (based on the existing in-repo ruling "billing is based on service card order settlement": prescription pricing is a trial-calculation/hint policy, not a billing source — see the settlement section in api.md, the note on the prescription page, and the PrescriptionController javadoc, since 60e818c and reiterated by 141f295). The prototype billing's herb line (「8 味 × 7 剂 ¥280」 next to tuina) is illustrative, not an interface contract; when it conflicts with a ruling, the ruling prevails. Technical evidence: orders.price is integer yuan while prescriptions are priced per unit, so conversion to yuan necessarily rounds, violating "money matters must be exact"; multi-line settlement (settlement_items) is a domain-level change. The decoction fee ¥3/bag follows the same policy (hint only).
  • [x] P1-18 staff leave completed (c33f1cf/7e8a7a0; web.md leftover "/staff/leave submission calls the staff creation interface, feature incomplete": the original server POST /staff/leave was a placeholder returning an empty object and the web page's misuse of createStaff would turn a leave submission into staff creation — rebuilt: staff_leaves table + /api/v1/staff/leave/{,page,id} for submission/pagination/deletion (type 0 sick leave 1 personal leave, reason, start/end time validation, joins in staff names), web registration form + recent leave list; policy: record-only, does not change login status, no approval flow (demo scope))
  • [x] P1-19 condition prescription templates (6b5d992/a6b0ab7; aligning with chinese_medicine_store_cos's condition prescription library approach: prescription_templates + items tables, /api/v1/prescription/template/{,page,enabled,id} CRUD + publish filtering (deactivated ones don't appear on the prescription page), condition names unique 1–20 characters, templates store only suggested values (doses/decoction/usage/notes), updates replace all herb lines in one pass (sort=line order); web /prescription/templates management page (inline add/remove of herb lines, publish Switch, delete Popconfirm) + the prescription page's "apply template" dropdown brings everything in one click, freely editable after and never written back to the template; seeds 风寒感冒/脾胃虚弱 as two examples, HTTP tests covering CRUD/duplicate names/validation/unauthenticated 401)
  • [x] P1-20 tuina/moxibustion treatment-course card write-off ledger (9e5a379/2205c6c; reusing the P1-14 visit card chain policy — tuina/moxibustion treatment-course cards are service cards counted by course + customer_cards, this change fills the missing write-off dimension in course management: card_usages append-only ledger, settlement payType=5 deducts a visit and records "which visit/serving employee (from the order's staffId)/order number" in the same transaction, rolls back with the settlement, no manual deduction; GET /api/v1/card/{id}/usages returns newest first with staff names joined; web patient record card rail shows "used X/N times" + a write-off record popup; seed demo cards get 3 historical write-offs (backfilled into the timeline, old cards backfilled without order numbers per policy); HTTP tests covering issue card→create order→visit card settlement→query ledger plus no-card 400/unknown card holding 400/unauthenticated 401)
  • [x] P1-21 paste prescriptions and pickup flow (2134975/280c435; prescriptions gains prescription_type (0 decoction default/1 paste), paste_status (paste prescriptions land in 1 待制作), craft (finishing method, paste only), column definitions with default 0 so legacy insert points stay unchanged; POST /prescription/ writes paste prescriptions priced per ingredient without bag counts (mutually exclusive with decoction service, each with its own pickup chain); PUT /api/v1/prescription/{id}/paste?status= moves one-way from 待制作→可取→已取, non-paste → 「不是膏方」400, regression/skip → 「流转无效」400, same shape as the decoction flow but not mixed with it; web prescription page type Radio (decoction/paste, switching to paste clears decoction, switching back clears the finishing method) + finishing method Select (炼蜜/清膏/糖膏/阿胶收膏), query page list type/paste status columns + detail drawer with a "制成 · 转可取 / 顾客已领取" button; seeds add a paste example (nine herbs, 30 doses priced per ingredient, 炼蜜 finishing, lands in 「可取」 leaving 「已取」 for the demo); 5 unit tests + HTTP tests covering write paste→flow→decoction 400/skip 400/unauthenticated 401)
  • [x] P1-22 patient medical history (allergy/past history) (7bb2679/bfcf3e8; the features.md electronic medical record row's gap "no independent history structure": customer_histories append-only table (type 0 allergy/1 past, content ≤200 characters), GET/POST /api/v1/customer/{id}/history + DELETE /api/v1/customer/history/{id}, guards for nonexistent patient/invalid type/blank content/overlength all return 400 (content is trimmed before storage, nothing is persisted when validation fails); no edit or audit trail — demo scope, delete means delete; web patient record history rail (allergy/past Radio.Button + input + records, inline type pill/content/date/delete confirmation); seeds give two demo patients one allergy and one past history each (backfilled into the timeline 28/28/9 days ago); HTTP tests covering record two→newest first→delete one and the four guards/unauthenticated 401; a walkthrough fix on the way: the paste seed missed setting decoction_status (NOT NULL column without default) causing the demo environment's paste seed to fail silently)
  • [x] P1-23 prescription allergy review (6c354cc/7237b2f; the allergy dimension of the features.md compatibility-review row: POST /api/v1/prescription/allergy-check compares against customer_histories type=0 allergy history, a hit occurs when the record's raw text contains the herb name — only terms ≥2 characters participate, single characters like 「参」 avoid false positives on long texts; one herb hitting multiple histories reports one each, one history hitting multiple herbs reports one each; past history does not participate; unknown patient/empty list → 400; hints do not block, same policy as the compatibility review; the web prescription page debounces 500ms after locating the patient and checks as you type, the hit block lists herb + history raw text, no placeholder when there's no hit; seed 王慕清「阿胶、蜂蜜过敏」× paste containing 阿胶 hits naturally in the demo; 4 unit tests + HTTP tests covering create record→add history→hit and guards/unauthenticated 401)
  • [x] P1-24 monthly billing report (a2f695a/e5e4eea; the features.md report row's gap "no business/billing reports": GET /api/v1/settlement/report/month?month=yyyy-MM fetches the month's settlements by settlement time and aggregates in Java (the SQLite dialect does no date grouping) — days[] lists per-day order counts/actual income only for days with settlements, payTypes[] payment method composition in ascending payType with labels, visit card write-offs (payType 5, actual income 0) in a separate cardCount, stored-value top-ups not counted as revenue; invalid month format/nonexistent month → 400, unauthenticated → 401; web /dash/month (month picker + three cards for monthly actual income/settlement count/visit card write-offs + payment method composition + per-day details + policy note); seeds add 6 settlements this month via 微信/支付宝/现金/储值 (backfilled into the timeline by day, stored-value following the same policy as negative ledger entries) + 1 visit card write-off (the 4th visit with an order number, deducting a visit, consistent with the P1-20 write-off ledger policy); 2 unit tests + 2 HTTP tests; a real-device walkthrough for 2026-10 reported 7 orders ¥3516, 1 visit card write-off, tuina card remaining 6/10 matching 4 write-off records, and a same-restart re-verification found no duplicate seeds)
  • [x] P1-25 formula library (07832e5/cfb32fa; the features.md herb/formula row's gap "formula library and pinyin search not done", aligning with his_mvp's formula library + pinyin input: formulas + formula_items tables (unique formula names 1–20 characters, pinyin full-spelling lowercase lookup code, source/indications; herbs same as prescriptions herb/weight/special/sort), /api/v1/formula/{,page,id} CRUD + keyword fuzzy match on formula name or pinyin code (like concat('%',:kw,'%'), an empty keyword from the service matches all); guards for empty name/over 20 characters/duplicate name (update exempts itself)/empty pinyin/not lowercase letters (entry auto-lowercases)/at least 1 herb/empty herb name/dose>0/nonexistent formula all return 400; updates replace all herb lines in one pass; division of labor with condition templates — formulas store only literature reference values without doses/decoction suggestions; web /prescription/formulas management page (name+pinyin two-line rows, herb chips, name/pinyin search) + the prescription page's "formula library" dropdown search brings out the whole formula in one click (herbs carried over, dose count unchanged, freely editable after and never written back); seeds with 9 classic formulas (四君子汤/四物汤/逍遥散/补中益气汤/玉屏风散/六味地黄丸/平胃散/二陈汤/八珍汤, with sources and effects) + 10 herbs added to the dictionary (early-return guard removed, existing databases backfilled incrementally by existsByName); 5 unit tests + HTTP tests covering create→search→edit→delete and guards/unauthenticated 401)
  • [x] P1-26 acupoint dictionary (9ecc30d/b8247ca; the features.md acupuncture/point-selection row's gap "no acupoint dictionary": acupoints single table (unique acupoint names 1–10 characters, pinyin full-spelling lowercase lookup code, meridian required, location/indications optional), /api/v1/acupoint/{,page,id} CRUD + keyword fuzzy match on acupoint name or pinyin code; guards for empty name/over 10 characters/duplicate name (update exempts itself)/empty pinyin/not lowercase letters/empty meridian/over 20 characters/nonexistent acupoint all return 400; the treatment record's point selection field stays free text — the dictionary only assists point selection; web /treat/acupoints management page (name+pinyin two-line rows, meridian pills, name/pinyin search) + the treatment page's left/right point selection rows each gain a "dictionary point selection" dropdown (search by name/pinyin, selections appended with 「、」into the point selection text, still freely writable); seeds with 27 common acupoints (足三里/三阴交/合谷/太冲/内关/外关/曲池/阳陵泉/委中/百会/风池/大椎/关元/气海/中脘/神阙/涌泉/太溪/血海/阴陵泉/肩井/命门/肾俞/脾俞/胃俞/印堂/太阳, with meridian/location/indications per point); 5 unit tests + HTTP tests covering add→search→edit→delete and guards/unauthenticated 401)
  • [x] P1-27 recurring staff shift table (b684f78/0be5ae0; the features.md scheduling row's gap "recurring staff shift table not done": staff_shifts single table (staff_id + weekday 1 Monday…7 Sunday + start/end HH:mm, unique per staff per weekday, cycling weekly), /api/v1/shift/{,page,staff/{staffId},id} CRUD + pagination with optional staffId/weekday filters (the service dispatches derived queries by null value to avoid the SQLite JPQL null-parameter pit, ordered by staff+weekday+time); guards for empty staff/nonexistent staff/out-of-range weekday/time not HH:mm (hour≤23 minute≤59)/start ≥ end/duplicate staff+weekday (update exempts itself)/nonexistent shift all return 400, responses join in staff names and weekday labels; shift table policy: scheduling reference only, attendance check-ins still follow signs, no write-back to check-in records, no linkage with leave/appointments for now; web /staff/shifts weekly matrix page (rows=staff, columns=Mon~Sun, slot times clickable to adjust, ✕ Popconfirm delete, "add shift" popup with staff/weekday/TimePicker 30-minute steps + off-work after on-work prevalidation, canAdmin only); seeds with 沈知远 Mon–Fri 09:00-18:00, 苏文若 Mon–Sat 09:30-18:30, 顾景明 Wed–Sun 10:00-19:00 for 16 entries total; 5 unit tests + HTTP tests covering add shift→pagination filter→weekly list→edit→delete and guards/unauthenticated 401)
  • [x] P1-28 acupuncture prescription records (2099b39/9d282af; the features.md acupuncture/point-selection row's gap "acupuncture prescription (needle method/retention/manipulation records) not done": treatment treats gains 4 columns (acu_method needle method ≤30 characters, retention_minutes retention minutes 1-240 optional, manipulation manipulation ≤30 characters, acu_course course/frequency ≤100 characters, blank text treated as empty), saved in full with treatment creation/updates (TreatView bidirectional mapping, unified validation in service save/update, out-of-range 400); web treatment page "acupuncture prescription" section (needle method/retention InputNumber 1-240/manipulation/course TextArea, refilled on revisit); seeds: the insomnia record gets 毫针+耳穴压豆/25 minutes/平补平泻/2×/week for 2 weeks, the back pain record gets 温针/20 minutes/补法/twice a week×3 (written only when the needle method is empty, idempotent); TreatUpdateTest extended with 2 cases (field round-trip/guards) + new TreatAcuHttpTest with 2 cases (create→read back→update→clear, guards, unauthenticated 401))
  • [x] P1-29 weekly shift vs. attendance comparison (e4af672/afd2a7d; the features.md scheduling row's gap "shift table and attendance linkage not done": GET /api/v1/staff/timesheet/week?weekDate= anchors the input to its week's Monday (rolling back (dow-2+7)%7 days), pulls the week's signs using the same TimesheetUtil policy as monthly attendance (earliest on-work/latest off-work of the day, rounded to whole hours, historical days missing an off-work punch count 0), staff fully as rows by ascending id × 7 days, shift plans compared by weekday (blank when no shift/no punch); empty weekDate takes the current week, invalid format reports 400; along the way DateUtil.parseParam is switched to strict mode (out-of-range dates like 2026-13-40 no longer roll over into valid dates); web /staff/timesheet/week staff × 7-day comparison board (expected/actual two rows, pick a date to see its week + back to current week, canAdmin only); StaffWeekTimesheetTest 3 cases + StaffWeekTimesheetHttpTest 2 cases (structure reading/400/401))
  • [x] P1-30 herb in/out stock and expiry alerts (the features.md herb inventory row's gap "inventory not done": herb_stock_logs single-table ledger (herb_id + type 1 in/0 out + quantity in grams + batch expiry + supplier ≤50 characters + note ≤100 characters), no balance column — stock is recomputed from the ledger = total in − total out, outbound consumes batches first-expired-first-out (FEFO), GET /api/v1/herb-stock/balance?expiryWithinDays= gives each herb's current grams + earliest unconsumed batch expiry + days to expiry + warnExpiry (default 30 days, cap 365, expired also alerts); /api/v1/herb-stock/{,/page,/balance,id} registration/ledger pagination (optional herbId/type filters, id descending, herb names joined)/balance/delete (demo scope, no audit trail); guards for missing herb/nonexistent herb/invalid type/non-positive quantity/expiry not yyyy-MM-dd or earlier than today/overlong supplier and note/outbound exceeding current stock all return 400; web /prescription/herb-stock balance dashboard (threshold 30/60/90 switching) + ledger registration popup (outbound without expiry) + ledger pagination delete; seeds: 5 common herbs inbound (黄芪 5000/当归 3000/茯苓 4000/白术 2500/甘草 6000 grams, all with batch expiry and supplier) + 1 outbound of 甘草 800 grams (clinic requisition), 白术 expiry 25 days serves as the 30-day threshold alert example; HerbStockTest 3 cases + HerbStockHttpTest 2 cases. Ledger scope — no purchase documents, stocktaking, or settlement involved, and prescriptions still don't deduct stock)
  • [x] P1-31 external therapy prescription templates (the features.md acupuncture/point-selection row's gap "ear point/plaster and similar external therapy templates not done": therapy_templates single table (unique template names 1–20 characters, method external therapy method required ≤10 characters, target suggested points/body area + course suggested course/frequency + note precautions each ≤100 characters optional, enabled publish flag), /api/v1/therapy-template/{,enabled,page,id} CRUD + pagination with optional name fuzzy / method exact filters (Specification conjunction, avoiding the SQLite derived-query null-parameter pit); guards for empty name/over 20 characters/duplicate name (update exempts itself)/empty method/over 10 characters/overlong optional sections all return 400; same policy as condition templates — suggested values only, after applying on the treatment page they can be freely edited with no write-back to the template, and deactivated ones don't appear on the treatment page; web /prescription/therapy-templates management page (method chips, publish toggle) + the treatment page's acupuncture prescription section gains an "apply external therapy template" dropdown (method/course refilled, point suggestions appended with 「、」into point selection-left); seeds with 4 common external therapy combinations (失眠·耳穴压豆/咳喘·穴位贴敷/肾阳虚·温灸关元/腰痛·留罐); TherapyTemplateTest 3 cases + TherapyTemplateHttpTest 2 cases (create→list→pagination filter→edit→delete and guards/unauthenticated 401))
  • [x] P1-32 pharmacy and dispensing flow (the features.md pharmacy/dispensing row's gap "not implemented", per the finalized policy in dispensing.md: prescriptions gains dispense_status (0 待发药 default/1 已发药 one-way; NOT NULL column, both the service save and the seed creation points explicitly set 0)/dispense_time/dispense_by columns + dispense_logs one-way ledger (prescription_id indexed, action=1, staff_id, note ≤100 characters); POST /api/v1/prescription/{id}/dispense moves 待发药→已发药 (duplicate → 400「处方已发药」, nonexistent → 400, operator taken from the current logged-in account, note ≤100 characters); GET /api/v1/prescription/dispense/page workbench pagination (optional dispenseStatus/customerKeyword filters, id descending, joins in patient name/phone plus type/dose count/herb count/decoction-paste labels); decoction service/paste advancing to 「已取」 automatically sets 已发药 and writes a ledger entry (operator left blank, note records the source — the deviation from the finalized "take the operating user" is recorded in the doc); web /prescription/dispensary pharmacy workbench (three-state switching with counts, patient search, dispensing popup with optional note and one-way confirmation, dispensed rows read-only) + the prescription query drawer's dispensing status and quick confirmation; seed dispensing demo (first prescription set to dispensed + ledger backfilled into the timeline); DispenseTest 5 cases + DispenseHttpTest 1 case (dispense→pagination filter→duplicate 400→unauthenticated 401))
  • [x] P1-33 refunds and daily reconciliation (per the finalized policy in reconciliation.md: refund_logs single table (settlement_id unique index — one refund per order, order_id indexed, money=original settlement actual income (visit card 0), pay_type denormalized, staff_id optional, reason ≤100 characters required); POST /api/v1/settlement/{id}/refund refunds the whole order back to its original channel — stored value restores a positive ledger entry (recharges +note column writes 「退单 #结算id」), the visit card voids that order's card_usages and restores the visit count, 微信/支付宝/现金 only write a refund record; the order 2→1 can be re-collected; GET /reconciliation?date= aggregates three segments by natural day (payments/top-ups/refunds + netMoney, cashNetMoney), the top-up segment only counts real top-ups (positive, non-refund notes — stored-value payment deductions count on the payment side, refund restorations on the refund side); the settlements.order_id unique index is removed with the "re-collect after refund" policy (SettlementIndexMigration drops the legacy index from old databases on startup, and settle's duplicate-prevention is changed to "most recent unrefunded"); web /billing/reconciliation daily reconciliation page (stat cards + payment composition + top-up/refund detail tables) + the settlement counter's row-level "refund" on the most recent settlement (reason popup with one-way confirmation, refunded tag); seeds refund one historical cash settlement for the demo (order returns to the queue and can be re-collected); RefundReconciliationTest 6 cases + RefundHttpTest 2 cases (refund→reconciliation→duplicate 400→missing/invalid date 400→unauthenticated 401), real-device verification of stored-value refund restoring the balance, re-collection landing a new settlement, and the three-collection block)
  • [x] P1-34 automatic queue calling on arrival (the "automatic queue calling on arrival number" gap in docs/tablet.md, approved 2026-10-12: appointment check-in 0→1 makes the server automatically write queue_calls and the front desk's manual queue calling run in parallel — number = that day's check-in sequence zero-padded to two digits (natural day of check-in time, sequenced by id), room = treating physician's name + 「诊室」 / 「待分配诊室」 when unassigned, name masked (张*), broadcast to all screens; skipped/cancelled numbers are not called; walk-ins without appointments still go through manual queue calling. Landed in AppointmentServiceImpl.fireQueueCall + QueueCallEntity comment refresh, with the regression AppointmentSlotTest.receiveFiresQueueCallAndNoShowDoesNot (skipped numbers not called/one call per check-in/two-digit number/room from physician name/name masked/full-screen broadcast/unassigned room))
  • [x] P1-35 mini program four-tab status addendum (the §3 table rewritten per current implementation: Home's "my cards" changed from a "planned" placeholder to a real entry + local summary, and the appointment/cards/mine screens added to the current-state table from the prototype list; remaining planned items honestly left blank = card purchase and remaining-visit details, patient openid binding registration, login session. Corresponding implementation 9cfaa92/be5da27/af0af7f)

Last updated: