Skip to content

Hồ sơ bệnh án — PRD v2.2.0

Phiên bản: 2.0.0 Ngày: 30/04/2026 Tác giả: PO/BA (Sơn Thọ) Loại: Tính năng mới Độ phức tạp: L Module: Phòng khám / CRM / POS / Clinical / Settings


Mục đích: chốt cam kết nghiệp vụ — phạm vi, quyết định, FR/AC, quy tắc, công thức, rủi ro ở mức PO/BA/TL/QA.

Đọc trước: decision-brief.mdTóm tắt điều hànhZ) Nhật ký quyết địnhA4) Yêu cầu chức năngA8) Công thức nghiệp vụ.

Văn phong: theo templates/_LANGUAGE_RULES.md + templates/_STYLE_GUIDE.md. Heading Việt-first; mỗi FR/AC trả lời đủ 6 yếu tố: vai trò + dữ liệu + điều kiện + hành động + kết quả + ngoại lệ.

Lịch sử thay đổi

Phiên bảnNgàyTác giảThay đổi
2.2.030/04/2026PO/BA + Security LeadFR-011 hardening lớn: chuyển toàn bộ chi tiết phân quyền sang permission-spec.md v1.0.0 (canonical owner). FR-011 trong PRD chỉ giữ mô tả nghiệp vụ + AC cấp cao; các chi tiết catalog 22 action / default seed / branch_mode / portal / cache / migration / field masking / emergency governance / Compliance UI / 44 TC-PERM-* chuyển sang permission-spec.md P1-P12. SCR-18 Compliance Audit Viewer thêm vào ui-spec. Thêm 8 AC mới cho FR-011 (AC-011.13..20) trỏ về P12 test matrix.
2.1.030/04/2026PO/BAFR-007 hardening để khớp UI v2.2.0 + dev v2.1.0 + QA v2.1.0: (1) tách AC-007.4 thành AC-007.4.1/4.2/4.3/4.4 cover rule câu xác nhận tự gõ DEC-021 (≥20 ký tự, từ khoá, chặn dán, tốc độ gõ); (2) thêm AC-007.7.1..7.5 cho luồng "Khách từ chối thủ thuật" DEC-024 (huỷ order_item, ghi sổ "Khách từ chối thủ thuật", giữ BA DL, witness y tá, permission clinical_record.refuse_procedure + order.update, idempotency cho record đã in/ký). FR/DEC khác giữ nguyên.
2.0.030/04/2026PO/BARefactor full theo glossary canonical 34 thuật ngữ + template po-ba-workflow mới (G3.7 cổng kiểm tra độ đầy đủ UI). Giữ 100% DEC/FR/AC nội dung từ v1.5.10.
1.5.1029/04/2026PO/BABản cũ — bị thay thế

Tài liệu đầu vào chuẩn

FileVai tròNếu xung đột
SOURCE_OF_TRUTH.mdNguồn sự thật chuẩn + Phương án đã chốtƯu tiên cao nhất
EVIDENCE_PACK.mdBằng chứng code/màn hình/config thậtƯu tiên bằng chứng trước assumption
decision-brief.mdCửa vào packageTóm tắt quyết định; PRD giữ quy ước chi tiết

Quy tắc công thức: A8 (Công thức nghiệp vụ) là nguồn chuẩn; dev-spec C3 chỉ mô tả triển khai.

Hướng dẫn đọc

Đối tượngSection cần đọc
PO/BAdecision-brief.md → A0 → Z → A4 → A8
Sếp / Business Leaddecision-brief.md → TL;DR → A0 → A2
Tech Leaddecision-brief.md → A0 → Z → Dev Spec C1-C5
UI/UX / FEdecision-brief.md → A4 → UI Spec
BE DevA4 / A8 → Dev Spec C1-C12
QAA4 → QA Plan D1-D5

Tóm tắt điều hành (TL;DR)

Phòng khám Diva (Da liễu + Phẫu thuật Thẩm mỹ) đang điền bệnh án bằng giấy tay, gây khó tra cứu khi thanh tra Sở YT, mất đồng bộ tiền sử khách giữa các chi nhánh và không có báo cáo lượt khám / thủ thuật theo ngày. PRD này khóa giải pháp số hóa hồ sơ bệnh án ngoại trú theo TT46/2018, TT51/2017, TT52/2017: tách clinical_profile (Hồ sơ bệnh án chính) và clinical_record (Bệnh án lượt khám), 7 biểu mẫu thực tế (BA DL/TM + 4 shared + phiếu theo dõi điều trị TM), bản giấy ký tay vẫn là gốc pháp lý. Pilot 2 chi nhánh (Cao Lãnh DL, Tân Bình II TM) trước khi mở rộng.

Quyết định còn mở

PDCâu hỏiLựa chọn / khuyến nghịPhụ tráchHạn chótTrạng tháiKết luận
PD-LEGAL-001Retention 10 năm: hot MinIO 1 năm + cold archive 9 năm — Legal/Infra duyệt?A) Theo DEC-026 — khuyến nghị ALegal + InfraTrước pilot T-7OpenTBD
PD-LEGAL-002Sở Y tế chấp nhận luồng giấy ký tay + scan reference?A) Có — go-live; B) Không — defer pilotPO + LegalTrước pilot T-7OpenTBD
PD-LEGAL-003Văn bản xác nhận mẫu giấy + retention + scan + trách nhiệm ký tay cho pilot?A) Có văn bản → go-live; B) Chỉ xác nhận miệng → NO-GOLegal + POTrước pilot T-7OpenTBD
PD-OPS-000D0 support owner, hotline, roster, scanner/printer/account test của CN pilot?A) Đầy đủ → UAT; B) Còn TBD → NO-GOOps + QL CNTrước UATOpenTBD
PD-OPS-001Cutoff/reminder mỗi CN pilot khác mặc định 20:00 không?A) Trạng thái mặc định 20:00 Asia/Ho_Chi_Minh; B) Cấu hình riêngOps + QL CNTrước UATOpenTBD — mặc định A
PD-DATA-001Import ICD-10 mới hơn bản 2015 trước pilot?A) 2015 đủ; B) 2021 mớiMedical Lead + BETrước migration stagingOpenTBD — mặc định A

Backlog giai đoạn 2 (ngoài phạm vi hiện tại)

#Tính năngLý do defer
1Bệnh án điện tử đầy đủ TT46/2018 (chữ ký số, QR, liên thông Sở YT)Scope pháp lý + kỹ thuật vượt Day-1
2Luồng xin quyền + đồng ý xem tầng 3 cross-branch trong app với SLA + consent số hoáCần legal sign-off riêng
3Portal/mobile Phiếu khách tự khai từ xa với OTP + consent versioningTránh phụ thuộc auth/OTP/consent
4Camera mobile + tag vùng cơ thể + so sánh timeline cho ảnh trước-sauPhase 2 yêu cầu mobile work mạnh
5Sales clinical recommendation engineTránh Sale suy diễn y khoa
6Dropdown thuốc + cảnh báo tương tácPháp chế chưa cho kê trên PM
7Report dịch tễ theo ICD-10 chapterPhase 2
8Export bộ hồ sơ BA zip cho khi khách yêu cầu bản saoPhase 2

Z) Nhật ký quyết định

44 DEC giữ 100% nội dung từ v1.5.10, refactor wording theo glossary canonical. Mọi FR phải tham chiếu đúng DEC-xxx.

Z1) Quyết định nghiệp vụ

IDQuyết địnhLý do (≥2 phương án so sánh)NgàyTrạng thái
DEC-001Module Phòng khám bật/tắt theo CN. Trạng thái mặc định TẮT. CN spa thuần không thấy menu/module mớiA) Bật toàn hệ thống → confuse spa thuần; B) Per-tenant config → quá phức tạp; C) Per-branch flag → đã chọn: tránh ảnh hưởng CN không liên quan + triển khai an toàn21/04/2026Locked
DEC-002Tách clinical_profile (Hồ sơ bệnh án chính) và clinical_record (Bệnh án lượt khám). 1 lượt khám có DL+TM → 2 record khác form_type cho cùng appointmentA) 1 bảng gộp → lẫn mã hồ sơ KH với từng lượt; B) 2 bảng tách → đã chọn21/04/2026Locked
DEC-003Walk-in tạo appointment với order_item_id=NULL. Link muộn qua order.reference_appointment_idA) Tạo order trước (giả) → luồng nghiệp vụ sai; B) Walk-in nullable → đã chọn: khớp luồng code hiện tại + appointment giữ semantic ổn định21/04/2026Locked
DEC-004BA Da liễu và Thẩm mỹ tách 2 khuôn biểu mẫu. 4 biểu mẫu shared (cam đoan, cam kết, đơn thuốc, dị ứng) dùng chung khuôn với placeholder {{branch.license_header}}A) 1 form gộp → cấu trúc lẫn lộn; B) Tách 2 khuôn unique + 4 shared → đã chọn: khớp bản giấy hiện tại21/04/2026Locked
DEC-005Phiếu biểu mẫu lưu JSONB (clinical_form_instance.form_data) + form_template định nghĩa schemaA) Cột riêng cho từng field BA → BA TM 60+ field cứng nhắc; B) JSONB + JSON Schema → đã chọn: linh hoạt + GIN index query được21/04/2026Locked
DEC-006Dịch vụ active phải phân loại 3 status: mapped_requires_ba, explicitly_no_ba, unclassified. unclassified chặn phát hànhA) 2 trạng thái có/không → bỏ sót DV mới; B) 3 trạng thái → đã chọn: vận hành SaaS nhiều CN an toàn21/04/2026Locked
DEC-007Bản giấy in từ PM vẫn là gốc pháp lý sau khi BS+KH ký tay. PM lưu scan đối chiếuA) E-signature số → cần legal sign-off + chứng thư số; B) Giấy ký tay + scan → đã chọn: phase 1 không dùng chữ ký số theo TT46/201821/04/2026Locked
DEC-028KH cũ trước bật module: không hồi cứu, chỉ BA mớiA) Hồi cứu toàn bộ → cost làm sạch khổng lồ; B) Không hồi cứu → đã chọn21/04/2026Locked
DEC-030Lifecycle module Phòng khám per CN: off → setup_draft → ready_to_publish → scheduled → live ↔ paused. Wizard hoàn tất chỉ đưa về ready_to_publishA) 1 trạng thái on/off → mất review impact; B) 6 trạng thái có effective_at → đã chọn: tenant đang vận hành cần review + rollback path28/04/2026Locked

Z2) Quyết định UX

IDQuyết địnhLý do (≥2 phương án so sánh)NgàyTrạng thái
DEC-019BA TM 4 phân hệ (Mắt/Mũi/Môi/Khác): auto-hiện section theo dịch vụ POS. BS có thể bật thêm section khácA) Hiện full 4 section luôn → rối; B) Auto-hide theo dịch vụ → đã chọn: giảm click21/04/2026Locked
DEC-020Phiếu biểu mẫu tự lưu nháp 30 giây. BS thấy danh sách "Bản nháp" để quay lạiA) Lưu thủ công → mất data nếu BS quên; B) Autosave 30s → đã chọn21/04/2026Locked
DEC-021Giấy cam đoan: 2 checkbox đồng ý/không + KH tự gõ 1 câu xác nhận. Bản in KH vẫn viết tayA) Chỉ checkbox → tính pháp lý yếu; B) Checkbox + tự gõ → đã chọn21/04/2026Locked
DEC-022Tải bản scan đã ký: cho phép từng tờ riêng hoặc 1 file PDF cả bộ. Trạng thái Đã ký + scan đủ chỉ khi checklist scan bắt buộc đủ theo caseA) 1 file = đủ → hiểu sai; B) Checklist theo case → đã chọn: bản scan đủ bộ mới có giá trị vận hành/pháp lý28/04/2026Locked
DEC-029UI surface Day-1: setup nằm ở Branch Detail tab thứ 4; Hồ sơ bệnh án là first-class tab thứ 17 của Customer Detail. CustomerInfo chỉ giữ summary cardA) Tạo màn cấu hình mới → trùng shell; B) Extend tab → đã chọn: khớp shell thật của repo27/04/2026Locked
DEC-032Sale có Trang xem an toàn (Sale): chỉ tầng 1+2 đã diễn giải an toàn, dùng dictionary safe_alert_*, CTA chuyển BSA) Sale dùng full UI BS → leak tầng 3; B) Trang riêng + dictionary → đã chọn27/04/2026Locked
DEC-033BS có Bàn việc bác sĩ với 6 nhóm việc, task_key ổn định, queue_owner_*, CTA tiếp theo. need_record chỉ sinh khi appointment/order thật sự có dịch vụ mapped_requires_baA) BS đi từng tab rời → bỏ sót; B) Bàn việc gom 6 bucket → đã chọn: tránh false-positive task27/04/2026Locked
DEC-034KH có Phiếu khách tự khai dùng one-time token 15 phút. BS review/nhận mới ghi vào phiếu biểu mẫu BA với source auditA) KH gửi tự do → tablet bị dùng sai KH/CN; B) Token + validate → đã chọn27/04/2026Locked

Z3) Quyết định kỹ thuật

IDQuyết địnhLý do (≥2 phương án so sánh)NgàyTrạng thái
DEC-013Mã hồ sơ BA chính = DVA-[TM|DL]-[CN_CODE]-[YYYY]-[NNNNN], không reset theo nămA) Reset năm → trùng mã giữa các năm; B) Monotonic → đã chọn21/04/2026Locked
DEC-014STT sổ khám bệnh = DVA-[CN_CODE]-[NNNNN]/[YYYY], reset nămQuy chuẩn BYT21/04/2026Locked
DEC-015STT sổ thủ thuật = DVA-[CN_CODE]-TT-[NNNNN]/[YYYY], reset nămQuy chuẩn BYT21/04/2026Locked
DEC-016ICD-10 = dropdown searchable, full ~22.000 mã. 1 primary required + 0–5 secondary optional. Diagnosis description = free text riêngA) Free text → corruption report; B) Dropdown chuẩn → đã chọn21/04/2026Locked
DEC-017Bản ICD-10 2015 BYT làm mặc định; admin import bản mớiBản 2015 phổ biến nhất21/04/2026Tinh giản cho ngày phát hành đầu
DEC-018Phiếu tiền sử dị ứng hardcode template TT51/2017, không cho admin edit. Chỉ cấu hình header CNMẫu pháp lý cố định21/04/2026Locked
DEC-037Clinical tables Day-1 ở source ecommerce. account thuộc default, lưu TEXT id + remote_relationshipA) Source default để FK account → migration không chạy được do FK cross-source; B) ecommerce + remote_relationship → đã chọn28/04/2026Locked
DEC-038Hasura action/event/scheduler dùng endpoint chung /actions /events /schedulers; dispatch theo name/table/payload bên trongKhớp ecommerce-api vận hành; webhook không cấu hình /actions/foo nếu Gin không expose đường dẫn đó28/04/2026Locked
DEC-039Field-level medical writes phải đi qua action/service hoặc DB trigger/check. Raw mutation không bypass JSON schema, sensitivity tag, scan checklist, auditJSONB linh hoạt nhưng dễ bypass nếu chỉ validate FE/action28/04/2026Locked
DEC-040Raw clinical tables không cấp select/insert/update trực tiếp cho vận hành role user. Runtime đọc qua action/secure viewA) Cấp raw → bypass DEC-008/009/010; B) Secure view/action → đã chọn28/04/2026Locked
DEC-041Runtime live gate = clinic_module_publication.status='live' AND effective_at<=now() cho CN. branch.features.clinic_enabled=true chỉ là setup flagA) clinic_enabled đủ → bật vận hành nhầm; B) Publication gate → đã chọn28/04/2026Locked
DEC-042Rollback/downtime quay lại giấy: bản ghi reconcile dùng source_type='paper_fallback' + audit reasonA) Gộp manual → khó audit; B) Tách flag → đã chọn28/04/2026Locked

Z4) Quyết định QA

IDQuyết địnhLý doNgàyTrạng thái
DEC-Q0199 core test case + regression boundary D6Cover full functional + regression theo Impact Boundary Matrix28/04/2026Locked
DEC-Q02Test grant/revoke/portal split bắt buộc cho Permission v2Sale/y tá/QL CN có thể đổi quyền vận hành28/04/2026Locked
DEC-Q03Test boundary: allergy unknown chặn + high chặn + override luồngAn toàn y khoa28/04/2026Locked

Z5) Quyết định an toàn

IDQuyết địnhLý doNgàyTrạng thái
DEC-008Phân 3 tầng dữ liệu: Tầng 1 (an toàn) toàn chuỗi · Tầng 2 (kinh doanh) toàn chuỗi · Tầng 3 (y tế nhạy) strict CN tạo, cross-branch chỉ theo policy Day-1Cân bằng an toàn KH + privacy + Nghị định 13/202321/04/2026Locked
DEC-009Sale tuyệt đối không xem tầng 3, kể cả cùng CNLuật KCB 2023 điều 3421/04/2026Locked
DEC-010Cross-branch tầng 3 Day-1: không build luồng xin quyền/đồng ý in-app. Quy trình ngoài hệ thống; chỉ Mở quyền khẩn cấp + audit mạnhGiảm scope pháp lý cho pilot21/04/2026Locked
DEC-011Allergy safety = config-driven. allergy_risk_levelunknown/low/medium/high; unknownhigh đều chặnAn toàn y khoa không 1-rule-fits-all21/04/2026Locked
DEC-012Dị ứng lần đầu BẮT BUỘC trước thủ thuật xâm lấn. Tái khám: BS tick "không đổi" hoặc điền mớiTránh điền lại nhưng vẫn xác nhận chủ động21/04/2026Locked
DEC-023Lifecycle bệnh án lượt khám: Bản nháp → Đã hoàn thành → Đã in → Đã ký + scan đủ → Bản thay thế → Đã lưu trữ. Không cho xóa BA y tế đã completed/printed/signed. Sửa sai sau in = in v2TT46/2018 yêu cầu không xoá BA21/04/2026Locked
DEC-024KH từ chối ký: ghi "Lý do không ký" + y tá ký làm chứng. Từ chối thủ thuật → hủy DV trong đơn + ghi sổ khámCover legal edge case21/04/2026Locked

Z6) Quyết định phân quyền

IDQuyết địnhLý doNgàyTrạng thái
DEC-043Permission v2 là quy ước vận hành: effective_permission = role_module.actions + portal + branch_mode + backend enforcement. Tên vai trò chỉ là cấu hình mẫu ban đầu. Backend không tin view_mode từ FE và phải trả dữ liệu tối thiểuA) Hard-code role → cứng nhắc + dễ lỗi; B) Dynamic + backend enforce → đã chọn28/04/2026Locked
DEC-044Impact Boundary Matrix là ranh giới scope chính thức. Module trực tiếp có owner/task/test; gián tiếp có guard; không ảnh hưởng phải có smoke regressionFeature L dễ scope creep28/04/2026Locked
DEC-025Go-live 3 giai đoạn: T1-2 song song giấy + PM; T3-4 PM chính + in ký; T5+ full PM + tải bản scan đã kýGiảm rủi ro pháp lý21/04/2026Locked
DEC-026Lưu trữ 10 năm theo TT46/2018 (ngoại trú). Hot MinIO 1 năm + cold archive 9 nămGiảm cost storage dài hạn21/04/2026Tinh giản cho ngày phát hành đầu
DEC-027Pilot 2 CN: Cao Lãnh (DL) + Tân Bình II (TM)2 loại PK khác nhau, đủ cover case21/04/2026Locked
DEC-031Kiểm tra sẵn sàng đạt 100% R-01..R-10 trước phát hànhĐảm bảo CN tự phục vụ cấu hình nhưng không gây lỗi pháp lý/vận hành27/04/2026Locked
DEC-035Ops có Trang điều phối phòng khám toàn chuỗiThấy rollout lệch trước khi thành lỗi27/04/2026Locked
DEC-036Mỗi CN đang hoạt động phải Chốt ngày phòng khám; cutoff theo cấu hình CN, mặc định 20:00 Asia/Ho_Chi_MinhPháp lý nằm ở hồ sơ in-ký-scan hoàn chỉnh27/04/2026Locked

A) PRD

A0) Tổng quan tính năng

Đọc 2 phút nắm hết. Chi tiết xem A1-A8.

Ý tưởng cốt lõi: Số hóa hồ sơ bệnh án ngoại trú cho chuỗi spa/phòng khám Diva theo TT46/2018: tách clinical_profile (Hồ sơ bệnh án chính, mã DVA-[TM\|DL]-[CN]-[YYYY]-[NNNNN]) và clinical_record (Bệnh án lượt khám theo appointment + form_type); 7 biểu mẫu thực tế lưu JSONB linh hoạt; bản giấy ký tay vẫn là gốc pháp lý + scan đối chiếu; phân quyền 3 tầng (an toàn / kinh doanh / y tế nhạy) qua Permission v2; pilot 2 CN trước khi mở rộng.

Kiến trúc tổng thể:

┌─────────────────────────────────────────────────────────────────────┐
│ Branch Detail (tab Phòng khám) — 5 bước cấu hình per CN              │
│   Bước 1 Loại PK → Bước 2 Giấy phép → Bước 3 Danh mục KT             │
│   → Bước 4 Phân loại dịch vụ — kỹ thuật → Bước 5 Phân quyền                       │
│           ↓ (Kiểm tra sẵn sàng đạt 100% R-01..R-10)                 │
│   clinic_module_publication: off → setup_draft → ready_to_publish    │
│           → scheduled → live ↔ paused                                │
└──────────────────────────────────┬──────────────────────────────────┘
                                   ↓ (CN đang hoạt động)
┌─────────────────────────────────────────────────────────────────────┐
│ Customer Detail (CRM) — tab thứ 17 "Hồ sơ bệnh án"                   │
│   ├─ Hồ sơ bệnh án chính (clinical_profile, mã DVA-...)              │
│   └─ Bệnh án lượt khám (clinical_record, theo appointment+form_type) │
│      ├─ Phiếu biểu mẫu (7 loại: BA DL/TM, cam đoan, ...)             │
│      ├─ Đơn thuốc (prescription)                                     │
│      ├─ Phiếu theo dõi điều trị (TM only)                            │
│      └─ File: bản scan đã ký, ảnh trước-sau điều trị                 │
└──────────────────────────────────┬──────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ Vận hành hằng ngày                                                   │
│   ├─ Bàn việc bác sĩ — 6 nhóm việc trong ngày                        │
│   ├─ Phiếu khách tự khai — token 15 phút                             │
│   ├─ Trang xem an toàn (Sale) — chỉ tầng 1+2                         │
│   ├─ Phiếu chuyển bác sĩ tư vấn (Sale → BS)                          │
│   ├─ Trang điều phối phòng khám — Ops toàn chuỗi                     │
│   └─ Chốt ngày phòng khám — cutoff 20:00 hoặc theo CN                │
└─────────────────────────────────────────────────────────────────────┘

Mapping FR ↔ SCR ↔ Dev:

KhốiFRSCRDev section
Module bật/tắtFR-001SCR-04C4.1, C4.3
Cấu hình 5 bướcFR-002SCR-04a..eC4.2, C4.4, C4.5
Phân loại dịch vụ — kỹ thuậtFR-003SCR-04dC4.5
Tạo lượt khám + walk-inFR-004SCR-05C2 (refactor), C4 appointment
BA Da liễuFR-005SCR-06C4.6, C4.7, C4.8, C4.9
BA Thẩm mỹFR-006SCR-07C4.6..C4.10
5 biểu mẫu sharedFR-007SCR-08C4.6, C4.9, C4.11
Tự lưu nhápFR-008SCR-06, SCR-07C5 action autosaveClinicalForm
In + scan kýFR-009SCR-09, SCR-10C5 action printClinicalRecord + uploadClinicalScan
An toàn dị ứngFR-010(cross-màn hình)C4.13, C4.14
Permission 3 tầngFR-011(cross-màn hình)C4.15, C8
Sổ khám + sổ thủ thuậtFR-012SCR-11, SCR-12C5 query, C9 export
NotificationFR-013(cross)C5 events
Sequence generatorFR-014(cross)C5 action generateSequence
Kiểm tra sẵn sàng + phát hànhFR-015SCR-04fC4.3
Trang xem an toàn (Sale)FR-016SCR-13C4.16
Bàn việc bác sĩFR-017SCR-14C5 query doctor_workbench_view
Phiếu khách tự khaiFR-018SCR-15C4.17, C5 token
Trang điều phối + Chốt ngàyFR-019SCR-16, SCR-17C4.18, C5 cron

Quyết định chính theo chủ đề:

NhómTóm tắtTham chiếu
Kiến trúcTách clinical_profileclinical_record; JSONB form data; source ecommerce; remote_relationship cho account; Permission v2 là quy ước vận hànhDEC-002, DEC-005, DEC-037, DEC-040, DEC-041, DEC-043
Nghiệp vụWalk-in nullable; classification 3 trạng thái; bản giấy ký tay là gốc; 3 tầng dữ liệu; allergy unknown+high chặnDEC-001..DEC-012, DEC-022
UXBA TM auto-hide; autosave 30s; Bàn việc bác sĩ 6 nhóm việc; Phiếu khách tự khai token; Trang xem an toàn (Sale)DEC-019..DEC-022, DEC-029, DEC-032..DEC-034
Vận hànhLifecycle 6 trạng thái module; pilot 2 CN; go-live 3 giai đoạn; Chốt ngày 20:00 mặc định; Impact Boundary MatrixDEC-025..DEC-027, DEC-030, DEC-031, DEC-035, DEC-036, DEC-044

Bảng FR tóm tắt:

FRMô tả (1 dòng)Ưu tiênGiai đoạn
FR-001Bật/tắt module Phòng khám per CN, mặc định tắtMust1
FR-002Cấu hình 5 bước khi bật Phòng khámMust1
FR-003Phân loại dịch vụ — kỹ thuật 3 trạng thái per CNMust1
FR-004Tạo lượt khám với walk-in supportMust1
FR-005Điền BA Da liễuMust1
FR-006Điền BA Thẩm mỹ với 4 phân hệ structuredMust1
FR-007Điền 5 biểu mẫu sharedMust1
FR-008Tự lưu nháp 30 giâyMust1
FR-009In bộ hồ sơ + tải bản scan đã kýMust1
FR-010Kiểm tra an toàn dị ứngMust1
FR-011Permission 3 tầng + audit cross-branch tầng 3Must1
FR-012Sổ khám bệnh + sổ thủ thuậtMust1
FR-013Notification "cần BA" + "BA thiếu"Must1
FR-014Sequence generator genericMust1
FR-015Kiểm tra sẵn sàng + an toàn phát hành per CNMust1
FR-016Trang xem an toàn (Sale) + Phiếu chuyển bác sĩ tư vấnMust1
FR-017Bàn việc bác sĩ với 6 nhóm việcMust1
FR-018Phiếu khách tự khai (token 15 phút)Should1
FR-019Trang điều phối phòng khám + Chốt ngày phòng khámMust1

Mô hình dữ liệu tóm tắt:

BảngLoạiMục đích
branch.features JSONBSỬA (ALTER)Feature flag per CN
appointment.OrderItemID Go structSỬA (refactor *uuid.UUID)Walk-in support
clinical_profileMỚIHồ sơ bệnh án chính per KH/CN/loại PK
clinical_recordMỚIBệnh án lượt khám theo appointment+form_type
clinical_form_instanceMỚI1 phiếu biểu mẫu (JSONB form_data)
form_templateMỚIKhuôn biểu mẫu (JSON Schema)
technical_categoryMỚIDanh mục kỹ thuật per CN
service_clinical_classificationMỚIPhân loại dịch vụ — kỹ thuật per CN
branch_technical_configMỚICấu hình clinical per CN (cutoff, ...)
clinic_module_publicationMỚIBản phát hành phòng khám per CN
prescription + prescription_itemMỚIĐơn thuốc structured
treatment_progress_entryMỚIPhiếu theo dõi điều trị (TM only)
icd10_codeMỚI + SEEDCatalog ICD-10 ~22.000 mã
allergy_check_policyMỚIRule allergy theo KT
allergy_check_skip_logMỚILog skip allergy check
medical_record_access_logMỚIAudit truy cập tầng 3
clinical_sales_handoffMỚIPhiếu chuyển bác sĩ tư vấn
customer_clinical_intakeMỚIPhiếu khách tự khai (token 15ph)
clinic_daily_closeMỚIChốt ngày phòng khám per CN/date
sequence_generatorMỚIGeneric sequence per entity_type+branch_id+year

Ảnh hưởng code hiện tại: xem dev-spec.md C2 chi tiết. High-risk: pkg/store/appointment.go:52 (refactor), 5 call site null-guard (appointment_update.go:191, appointment_insert.go:81/176/202, ticket.go:526).


A1) Bản thiết kế tóm tắt

TrườngGiá trị
Tính năngHồ sơ bệnh án (Da liễu + Phẫu thuật Thẩm mỹ)
LoạiTính năng mới
Nền tảngWeb Admin (diva-admin — User/CRM/Settings/Ecommerce/POS modules)
Module ảnh hưởngFE: crm, user, ecommerce, settings, clinical (mới); BE: ecommerce-api (chính), auth, notification-v2-api
DatabaseSource ecommerce (18 bảng mới); Source default (Permission v2 extend: 8 module + 22 action mới)

A2) Bối cảnh

Hiện trạng (As-Is):

  • Phòng khám Diva (DL + TM) điền BA bằng giấy tay theo mẫu BYT
  • Khi thanh tra Sở YT: nhân viên phải lục thư mục giấy theo CN; bản giấy mất / ố mực / ghi sai khó tra cứu
  • BS chi nhánh khác không nắm tiền sử dịch vụ + dị ứng KH; Sale không có nguồn dữ liệu y tế tóm tắt khi tư vấn
  • KH phải khai lại tiền sử mỗi lần đến; BS phải tự nhớ "hôm nay còn BA nào chưa hoàn thành"
  • Ops không có cách thấy CN nào triển khai phòng khám đang lệch cấu hình
  • Đơn thuốc viết tay khó truy vết; xem tầng 3 khác CN chưa có cơ chế audit

Kỳ vọng (To-Be):

  • Bệnh án digital theo TT46/2018, TT51/2017, TT52/2017; bản giấy ký tay vẫn là gốc pháp lý + scan đối chiếu
  • 7 biểu mẫu thực tế: 2 unique (BA DL, BA TM 4 phân hệ) + 4 shared (cam đoan, cam kết, đơn thuốc, dị ứng) + 1 TM-only (phiếu theo dõi điều trị)
  • Phân quyền 3 tầng theo Permission v2; Sale chặn cứng tầng 3; cross-branch tầng 3 chỉ qua Mở quyền khẩn cấp + audit
  • Bàn việc bác sĩ 6 nhóm việc trong ngày; Phiếu khách tự khai trước khám; Trang xem an toàn cho Sale; Trang điều phối Ops; Chốt ngày phòng khám
  • Pilot 2 CN: Cao Lãnh (DL) + Tân Bình II (TM)

Tại sao làm bây giờ:

  • Compliance: Sở YT có thể thanh tra bất kỳ lúc nào; phạt hành chính nếu tra cứu chậm
  • An toàn: dị ứng cross-CN không được cảnh báo → rủi ro y khoa
  • Mở rộng chuỗi: muốn mở thêm CN có giấy phép phòng khám nhưng không có công cụ kiểm soát rollout

Ràng buộc:

  • Pháp lý: TT46/2018 cấm xóa BA; TT51/2017 cố định mẫu phiếu dị ứng; Luật KCB 2023 điều 34 hạn chế Sale xem y tế; Nghị định 13/2023 BVDL cá nhân
  • Kỹ thuật: Hasura vận hành role user không được expose raw clinical (DEC-040); cross-source FK qua remote_relationship; PDF generation external

Đồng thuận stakeholder:

  • PO/BA: Sơn Thọ — chốt phạm vi + DEC
  • Tech Lead: TBD — sign-off architecture, refactor Appointment.OrderItemID
  • QA Lead: TBD — sign-off test coverage 99 core TC + regression boundary
  • Medical Lead: TBD — sign-off allergy_risk_level cho danh mục KT
  • Legal: TBD — sign-off retention 10 năm + luồng giấy ký tay
  • Ops: TBD — sign-off pilot setup CN

Không thuộc phạm vi (Non-goals):

#MụcLý do
1Bệnh án điện tử đầy đủ TT46/2018 (chữ ký số, QR, liên thông Sở YT)Scope pháp lý + kỹ thuật vượt Day-1
2Bộ hồ sơ đầy đủ cho khách chỉ khám không làm dịch vụDay-1 chỉ ghi visit-only log nếu CN cần
3Hồi cứu BA giấy cũ trước khi bật moduleCost làm sạch khổng lồ
4Luồng xin quyền + đồng ý tầng 3 cross-branch trong app với SLA + consent số hoáCần legal sign-off riêng — Phase 2
5Portal/mobile cho KH tự khai từ xa ngoài CNPhase 1 dùng tablet/quầy nội bộ
6Sale xem chẩn đoán/form BA/scan/ảnh y tế tầng 3Quy định cứng — không override qua Permission v2
7Tự động chẩn đoán AI / suggest điều trịKhông scope
8Telemedicine (khám từ xa video)Không scope
9Xuất BA theo format BHYT thanh toánKhông scope

A3) Mục tiêu, persona và chỉ số thành công

Mục tiêu:

Mục tiêuCách đoMục tiêu đo
Compliance pháp lý TT46/2018Audit Sở YT đạt100% pilot (2 CN)
An toàn KH (cảnh báo dị ứng cross-CN)Tỷ lệ thủ thuật xâm lấn không bị chặn do unknown allergy< 5% sau pilot 30 ngày
Tỷ lệ BA hoàn thành đúng ngàyFORMULA-004≥ 90% sau pilot 30 ngày
Thời gian BS điền 1 BA (TB)FORMULA-005≤ 8 phút BA DL; ≤ 12 phút BA TM
Tỷ lệ BA đủ chữ ký + scan đủFORMULA-006≥ 95% sau pilot 30 ngày
Thời gian Ops thấy drift cấu hìnhTrang điều phối phòng khám< 1 giờ

Persona:

PersonaVai tròJTBDTần suất
Bác sĩ Da liễuBS DLKhám và điền BA DL, kê đơn, theo dõi liệu trình DLMỗi lượt khám DL
Bác sĩ Thẩm mỹBS TMKhám, tư vấn TM, điền BA TM 4 phân hệ, theo dõi tiến trình điều trịMỗi lượt khám TM
Y tá hỗ trợNSHỗ trợ phần hành chính, scan ký, làm chứngMỗi lượt khám
Lễ tânRCPTạo đơn POS, mở phiên khách tự khai, đẩy thông báo "cần BA"Mỗi lượt KH đến
Tư vấn viên (Sale)SLSTư vấn dịch vụ phù hợp tình trạng KH (qua Trang xem an toàn)Mỗi lượt tư vấn
Quản lý chi nhánhBMDuyệt sổ khám/thủ thuật, giám sát BA chưa hoàn thành, chốt ngàyHằng ngày
Admin/OpsADM/OPSBật/tắt module, cấu hình 5 bước, theo dõi rollout toàn chuỗiTuần
Khách hàngCUSTự khai phiếu trước khám tại quầy/tabletLần đầu / khi có thay đổi

A4) Yêu cầu chức năng

Mọi FR phải trả lời đủ 6 yếu tố (vai trò + dữ liệu + điều kiện + hành động + kết quả + ngoại lệ) và tham chiếu DEC.

FR-001 — Bật/tắt module Phòng khám per CN

Tham chiếu: DEC-001, DEC-030, DEC-041 | Ưu tiên: Must | SCR: SCR-04

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin/Ops; QL CN xem CN mình
  • Dữ liệu: branch.features.clinic_enabled, clinic_module_publication.status/effective_at
  • Điều kiện: Branch type = clinic-eligible (có giấy phép phòng khám) hoặc Admin force mở
  • Hành động: Vào Branch Detail → tab Phòng khám (chỉ hiện nếu có quyền clinic_module.access); bật toggle "Bật module Phòng khám"; chuyển trạng thái sang setup_draft
  • Kết quả: branch.features.clinic_enabled=true; clinic_module_publication có row mới status='setup_draft'; CN spa thuần mặc định false, tab Phòng khám không hiện
  • Ngoại lệ: Không có quyền configure → ẩn toggle; CN đã live muốn pause → cần quyền phát hành + lý do; rollback: paused, không xóa publication

AC:

  • [ ] Khi Admin bật module ở CN Cao Lãnh chưa từng có publication → tạo clinic_module_publication với status='setup_draft', branch.features.clinic_enabled=true
  • [ ] Khi role staff (Lễ tân) vào Branch Detail CN Cao Lãnh → tab Phòng khám không hiện (không có clinic_module.access)
  • [ ] Khi CN đang live chuyển sang paused → menu/UI clinical hiện banner "Module đã tạm dừng từ {DD/MM/YYYY HH:mm}"; không tạo BA mới được
  • [ ] Khi rollback từ live về paused → BA cũ vẫn xem được (read-only); cron cronClinicalDailyClose skip CN này

FR-002 — Cấu hình 5 bước khi bật Phòng khám

Tham chiếu: DEC-002, DEC-004, DEC-029, DEC-031 | Ưu tiên: Must | SCR: SCR-04a..SCR-04e

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin/Ops cấu hình; QL CN xem readonly
  • Dữ liệu: branch_technical_config (loại PK, license_header, cutoff), technical_category (danh mục KT), service_clinical_classification (phân loại dịch vụ — kỹ thuật), Permission v2 seed
  • Điều kiện: Module ở setup_draft hoặc paused
  • Hành động: Wizard 5 bước theo thứ tự: (1) Loại phòng khám DL/TM/cả hai · (2) Giấy phép Sở YT (upload file scan + license_number + expiry) · (3) Danh mục kỹ thuật (chọn từ master + tự thêm) · (4) Phân loại dịch vụ — kỹ thuật (mỗi DV active phải có 1 trong 3 trạng thái) · (5) Phân quyền nhân sự (gán role + module) · Bước 6 (gate): Kiểm tra sẵn sàng (R-01..R-10)
  • Kết quả: Wizard hoàn tất = status='ready_to_publish'; chưa tự live
  • Ngoại lệ: Bước 4 còn unclassified → chặn sang bước 5; bước 6 không đạt R-xx → highlight mục lỗi + deeplink sang màn cần sửa + nút "Quay lại Phòng khám"

AC:

  • [ ] Khi Admin chọn loại "Cả DL+TM" → bước 3 hiện cả 2 nhóm KT; bước 4 cho phép phân loại theo DL hoặc TM
  • [ ] Khi bước 4 còn 5 dịch vụ unclassified → bước 5 bị khóa; banner "Còn 5 dịch vụ chưa phân loại → Đến danh mục dịch vụ"
  • [ ] Khi bước 6 R-03 (giấy phép) hết hạn → highlight đỏ + deeplink sang bước 2; không cho phát hành
  • [ ] Khi hoàn tất 6 bước + tất cả R-xx đạt → CTA "Phát hành" hiện; chuyển sang ready_to_publish

FR-003 — Phân loại dịch vụ — kỹ thuật 3 trạng thái

Tham chiếu: DEC-006, DEC-031 | Ưu tiên: Must | SCR: SCR-04d

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin/Ops cấu hình per CN
  • Dữ liệu: service_clinical_classification(branch_id, product_id, classification_status, technical_category_id)
  • Điều kiện: CN đã chọn loại PK ở bước 1; có dịch vụ active
  • Hành động: Hiển thị bảng dịch vụ active theo CN với cột "Phân loại"; mỗi dòng cho phép chọn 1 trong 3: (a) mapped_requires_ba (cần BA — phải chọn KT); (b) explicitly_no_ba (xác nhận không cần BA); (c) unclassified (chưa phân loại — mặc định cho DV mới)
  • Kết quả: Lưu phân loại; nếu mapped_requires_ba thì link với technical_category_id
  • Ngoại lệ: Còn unclassified → chặn phát hành; sau khi live, dịch vụ mới active mặc định unclassified → cảnh báo Ops "Lệch cấu hình: 3 dịch vụ chưa phân loại tại CN Cao Lãnh"

AC:

  • [ ] Khi Admin có 50 dịch vụ active tại CN → table hiển thị 50 dòng; mặc định tất cả unclassified
  • [ ] Khi Admin chọn mapped_requires_ba cho dịch vụ "Liệu trình trị mụn" + KT "Áp lạnh nitơ" → lưu thành công
  • [ ] Khi Admin để 5 dịch vụ unclassified và bấm "Tiếp" → chặn + banner "Còn 5 dịch vụ chưa phân loại"
  • [ ] Sau khi CN live, Admin thêm dịch vụ "Filler Hyaluronic" → mặc định unclassified + Trang điều phối hiển thị cảnh báo "Lệch cấu hình"

FR-004 — Tạo lượt khám với walk-in support

Tham chiếu: DEC-003 | Ưu tiên: Must | SCR: SCR-05 | CRITICAL: Walk-in safety

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Lễ tân, BS, Y tá tạo; Khách hàng có thể tham gia qua Phiếu khách tự khai
  • Dữ liệu: appointment.order_item_id (NULL được), order.reference_appointment_id, Appointment.OrderItemID *uuid.UUID (Go struct)
  • Điều kiện: CN ở live; KH đã có hoặc tạo mới (walk-in) hoặc chưa có
  • Hành động: Hai đường: (a) Tạo từ lịch hẹn có sẵn → order_item_id đã có; (b) Walk-in: tạo appointment với customer_id=NULL hoặc tạo KH mới + order_item_id=NULL; sau khám tạo đơn → đơn có reference_appointment_id link muộn, không backfill appointment.order_item_id
  • Kết quả: appointment row mới; nếu walk-in → order_item_id IS NULL; sau khám tạo orderorder.reference_appointment_id link
  • Ngoại lệ: Code Go cũ chưa null-guard appointment.OrderItemID → cần refactor *uuid.UUID + null-guard 5 call site (Phase 0 blocker); CN spa thuần (features.clinic_enabled=false) → walk-in vẫn tạo được nhưng không gắn clinical record

AC:

  • [ ] Lễ tân tạo walk-in cho khách Nguyễn Thị Lan tại CN Tân Bình II 14:30 ngày 02/05/2026 → appointment insert với customer_id={Nguyễn Thị Lan}, order_item_id=NULL, branch_id={Tân Bình II}
  • [ ] Sau khám 30 phút BS bấm "Tạo đơn" → order insert với reference_appointment_id={appointment.id}; appointment.order_item_id vẫn NULL
  • [ ] Khi appointment_update.go:191 query với order_item_id IS NULL → null-guard if appointment.OrderItemID != nil trước khi build query (test: walk-in không crash event handler)
  • [ ] Khi gọi từ Bàn việc bác sĩ với appointment walk-in → need_record task hiện đầy đủ (task_key=ba_DLM_2026_001, queue_owner_type=doctor, queue_owner_id={BS})

FR-005 — Điền BA Da liễu

Tham chiếu: DEC-004, DEC-005, DEC-016, DEC-020, DEC-023 | Ưu tiên: Must | SCR: SCR-06

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Bác sĩ Da liễu cùng CN
  • Dữ liệu: clinical_record.form_type='DL', clinical_form_instance.form_template_id={form_template.DL}, form_data JSONB; ICD-10 code via icd10_code
  • Điều kiện:clinical_record ở trạng thái Bản nháp hoặc Đã hoàn thành; BS có quyền clinical_record.edit_medical_form
  • Hành động: Mở form BA DL với section expandable (Hành chính → Tiền sử → Khám hiện tại → Chẩn đoán → Y lệnh → Theo dõi); BS điền theo từng section; chẩn đoán dùng dropdown ICD-10 searchable (1 primary required + 0-5 secondary optional); diagnosis description = free text riêng; tự lưu nháp 30s (FR-008); BS bấm "Hoàn thành" để chuyển trạng thái
  • Kết quả: clinical_record.status='Đã hoàn thành'; clinical_form_instance.form_data lưu đầy đủ JSON; completed_at=NOW()
  • Ngoại lệ: Allergy unknown cho KT thủ thuật → chặn "Hoàn thành" (FR-010); thiếu primary ICD-10 → validation lỗi inline; BS không cùng CN → 401 (Permission v2 enforcement); KH từ chối ký → luồng DEC-024

AC:

  • [ ] BS DL CN Cao Lãnh mở BA cho khách Lê Thị Hương → form hiện 6 section, mặc định các field rỗng
  • [ ] BS chọn ICD-10 primary L70.0 (mụn trứng cá thông thường) + secondary L73.2 → cả 2 lưu vào form_data.diagnosis_codes
  • [ ] BS bấm "Hoàn thành" mà chưa chọn primary ICD → validation lỗi inline focus vào field "Chẩn đoán chính"
  • [ ] BS không cùng CN cố mở BA → API trả 401, FE hiện "Bạn không có quyền xem nội dung này"

FR-006 — Điền BA Thẩm mỹ với 4 phân hệ structured

Tham chiếu: DEC-004, DEC-005, DEC-019, DEC-023 | Ưu tiên: Must | SCR: SCR-07

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Bác sĩ Thẩm mỹ cùng CN
  • Dữ liệu: clinical_record.form_type='TM'; clinical_form_instance.form_template_id={form_template.TM}; 4 phân hệ Mắt/Mũi/Môi/Khác
  • Điều kiện:clinical_record form_type=TM
  • Hành động: Form auto-hiện section theo dịch vụ POS đã chọn (DEC-019); BS có thể bật thêm phân hệ; mỗi phân hệ có 60+ field structured (đo đạc Mắt: hẹp mí trên/dưới, sa trễ; đo Mũi: chiều cao sống, độ rộng cánh; đo Môi: tỷ lệ trên/dưới); chẩn đoán + y lệnh chi tiết
  • Kết quả: clinical_form_instance.form_data JSON; lifecycle như BA DL
  • Ngoại lệ: BS muốn bật thêm phân hệ "Khác" mà không có dịch vụ → cho phép + cảnh báo "Phân hệ này không được chọn từ dịch vụ POS"

AC:

  • [ ] Khách mua liệu trình "Nâng mũi cấu trúc" → form TM auto-hiện section "Mũi" (đầy đủ 60+ field), ẩn section Mắt/Môi
  • [ ] BS bật thêm section "Khác" để ghi chú → form hiện section thứ 2 + banner "Phân hệ này không được chọn từ dịch vụ POS"
  • [ ] Lưu nháp với 30 field đã điền + 30 field còn rỗng → autosave 30s OK
  • [ ] BS bấm "Hoàn thành" mà thiếu primary ICD-10 → validation lỗi

FR-007 — Điền 5 biểu mẫu shared (kèm rule câu xác nhận tự gõ + luồng từ chối thủ thuật)

Tham chiếu: DEC-004, DEC-018, DEC-021, DEC-024 | Ưu tiên: Must | SCR: SCR-08

Mô tả nghiệp vụ phần mềm:

  • Vai trò: BS điền nội dung; KH ký + tự gõ xác nhận; Y tá hỗ trợ + làm chứng
  • Dữ liệu: 5 biểu mẫu shared: cam đoan, cam kết, đơn thuốc, phiếu tiền sử dị ứng, phiếu theo dõi điều trị (TM only); clinical_record.customer_refused_procedure/refusal_reason/witness_nurse_id/cancelled_order_item_id cho luồng từ chối thủ thuật
  • Điều kiện: clinical_recordBản nháp hoặc Đã hoàn thành
  • Hành động:
    1. Mỗi biểu mẫu là clinical_form_instance riêng với form_template_id tương ứng; cam đoan có placeholder {{branch.license_header}} (khác giữa các CN); phiếu tiền sử dị ứng dùng template hardcode TT51/2017, không cho admin edit.
    2. DEC-021 — Câu xác nhận tự gõ: trong giấy cam đoan, KH tự gõ 1 câu xác nhận theo rule cứng: ≥ 20 ký tự (sau trim), phải chứa "đồng ý" hoặc "thực hiện" (không phân biệt hoa thường), FE chặn paste/copy-paste, FE log nhịp gõ + server lưu typed_attyped_duration_ms để audit nghi vấn auto-typer (>30 ký tự/giây flag suspicious_typing=true).
    3. DEC-024 — Khách từ chối thủ thuật: trong cam đoan có nút "Khách từ chối thủ thuật" → mở hộp thoại bắt nhập refusal_reason ≥ 30 ký tự + chọn witness_nurse_id (y tá cùng CN); xác nhận → hệ thống huỷ order_item của thủ thuật, đánh dấu clinical_record.customer_refused_procedure=true + refusal_type='procedure', ghi sổ khám "Kết quả: Khách từ chối thủ thuật", giữ Bệnh án DL nếu cùng appointment đã có (không touch), KHÔNG tạo Bệnh án thủ thuật mới, gửi noti_clinical_procedure_refused cho QL CN + Sale phụ trách.
  • Kết quả: Mỗi biểu mẫu = 1 clinical_form_instance; gắn vào clinical_record cùng appointment + form_type. Khi từ chối thủ thuật: order_item.status='cancelled' với cancel_reason='customer_refused_procedure', audit medical_record_access_log action='refuse_procedure'.
  • Ngoại lệ:
    • KH không tự gõ câu xác nhận hợp lệ → chặn "Hoàn thành" cam đoan
    • Admin cố edit phiếu dị ứng template → API trả 403
    • User thiếu quyền clinical_record.refuse_procedure → CTA "Khách từ chối thủ thuật" ẩn; gọi action trực tiếp → 403
    • User thiếu quyền order.update → CTA hiện nhưng action trả 422 "Bạn không có quyền huỷ đơn dịch vụ. Liên hệ QL CN."
    • Re-call action trên record đã từ chối → 409 idempotency

AC:

  • [ ] AC-007.1 BS bấm "Tạo đơn thuốc" trong BA → mở phiếu đơn thuốc với 5 dòng thuốc mặc định; điền tên thuốc + liều + cách dùng + số ngày
  • [ ] AC-007.2 BS in giấy cam đoan tại CN Cao Lãnh → header có "Phòng khám Da liễu Diva Cao Lãnh — Giấy phép số {license_number}"
  • [ ] AC-007.3 Admin cố sửa template phiếu dị ứng → API trả 403; UI hiện "Phiếu tiền sử dị ứng tuân theo TT51/2017, không thể sửa"
  • [ ] AC-007.4.1 (DEC-021) KH gõ câu xác nhận <20 ký tự → FE inline lỗi error.confirmation_too_short; nút "Lưu phiếu" khoá; server bypass FE cũng reject 422
  • [ ] AC-007.4.2 (DEC-021) Câu xác nhận đủ 20 ký tự nhưng thiếu cả "đồng ý" và "thực hiện" → cùng lỗi; thêm "đồng ý" → unblock
  • [ ] AC-007.4.3 (DEC-021) KH cố dán nội dung từ clipboard → FE chặn paste event + thông báo nhanh error.confirmation_paste_blocked; câu vẫn rỗng
  • [ ] AC-007.4.4 (DEC-021) Tốc độ gõ >30 ký tự/giây (gợi ý macro) → server log suspicious_typing=true + flag để compliance review; KHÔNG block submission để tránh chặn nhầm KH gõ nhanh thật
  • [ ] AC-007.5 Đơn thuốc có ít nhất 1 thuốc → server enforce required liều/cách dùng + save structured
  • [ ] AC-007.6 Phiếu theo dõi điều trị: thêm entry mới không sửa entry cũ; entry cũ readonly + audit timestamp
  • [ ] AC-007.7.1 (DEC-024) BS bấm "Khách từ chối thủ thuật" → hộp thoại; nhập lý do ≥30 ký tự + chọn y tá làm chứng → xác nhận → clinical_record.customer_refused_procedure=true, cancelled_order_item_id lưu, order_item.status='cancelled' với cancel_reason='customer_refused_procedure', sổ khám hiện cột "Kết quả: Khách từ chối thủ thuật", medical_record_access_log ghi action refuse_procedure, NTF-14 gửi QL CN + Sale phụ trách
  • [ ] AC-007.7.2 (DEC-024) Hộp thoại không cho xác nhận khi witness_nurse_id rỗng → copy error.refusal_witness_required; bypass FE → server reject 422
  • [ ] AC-007.7.3 (DEC-024) Khách từ chối thủ thuật khi đã có Bệnh án DL completed/signed cùng appointment → BA DL không bị thay đổi; chỉ huỷ order_item thủ thuật; preserved_dl_record_id trả về cho FE confirm
  • [ ] AC-007.7.4 (DEC-024 + Permission v2) User thiếu clinical_record.refuse_procedure → CTA ẩn ở SCR-08, gọi action trực tiếp → 403; thiếu order.update → CTA hiện nhưng action → 422 với copy hướng dẫn liên hệ QL CN
  • [ ] AC-007.7.5 (DEC-024) clinical_record.status='printed'/'signed' đã in/ký → action refuse_clinical_procedure reject 422 "Bệnh án đã in/ký, không thể ghi nhận từ chối thủ thuật. Sửa qua bản v2 (DEC-023)."

FR-008 — Tự lưu nháp 30 giây

Tham chiếu: DEC-020 | Ưu tiên: Must | SCR: SCR-06, SCR-07, SCR-08

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (FE auto trigger); BS xem
  • Dữ liệu: clinical_form_instance.form_data (JSONB)
  • Điều kiện: Form đang ở Bản nháp; có thay đổi chưa lưu
  • Hành động: FE debounce 30 giây sau khi BS dừng gõ → POST mutation update_clinical_form_instance với form_data mới; show "Đã lưu nháp lúc HH:mm" cuối form
  • Kết quả: form_data cập nhật; updated_at=NOW()
  • Ngoại lệ: Mất mạng → thông báo nhanh "Mất kết nối. Đã lưu local. Đang thử lại..."; conflict (BS khác cùng record) → hộp thoại "Có thay đổi từ thiết bị khác. Giữ thay đổi của bạn / Lấy bản mới nhất"

AC:

  • [ ] BS gõ 30 field → 30 giây sau dừng gõ → backend nhận mutation, response 200, FE hiện "Đã lưu nháp lúc 14:32"
  • [ ] BS gõ liên tục 5 phút → debounce reset mỗi lần gõ → chỉ lưu 1 lần sau khi dừng 30s
  • [ ] Mất mạng giữa lúc autosave → thông báo nhanh "Mất kết nối. Đã lưu local."; khi có mạng → tự retry
  • [ ] BS A và BS B (cùng record) cùng gõ → BS B lưu sau → hộp thoại conflict cho BS A

FR-009 — In bộ hồ sơ + tải bản scan đã ký

Tham chiếu: DEC-007, DEC-022, DEC-024 | Ưu tiên: Must | SCR: SCR-09, SCR-10

Mô tả nghiệp vụ phần mềm:

  • Vai trò: BS bấm in; Y tá scan + upload; QL CN xác nhận
  • Dữ liệu: clinical_record.status; reference_file với type='clinical_form_instance.scan' hoặc 'clinical_record.before_after'; prescription với type='prescription.scan'
  • Điều kiện: Bệnh án ở Đã hoàn thành mới in được; in xong → Đã in; scan đủ checklist case → Đã ký + scan đủ
  • Hành động: (1) BS bấm "In bộ hồ sơ" → render HTML từ template + variable; PDF render external; lưu HTML vào print_invoice_html pattern; (2) Y tá tải bản scan: từng tờ riêng hoặc 1 file PDF cả bộ; UI cho mapping file ↔ biểu mẫu; (3) Hệ thống check checklist scan bắt buộc theo case (BA chính + cam đoan/cam kết nếu yêu cầu + phiếu dị ứng nếu bắt buộc + đơn thuốc nếu kê + phiếu theo dõi điều trị nếu có) hoặc biên bản từ chối có y tá làm chứng
  • Kết quả: Trạng thái chuyển Đã inĐã ký + scan đủ khi checklist đủ
  • Ngoại lệ: Tải 1 file bất kỳ KHÔNG đủ checklist → trạng thái vẫn Đã in, banner "Còn thiếu: Phiếu cam đoan + Đơn thuốc"; KH từ chối ký → cho phép tải biên bản từ chối + y tá làm chứng → trạng thái Đã ký + scan đủ với flag refusal_witnessed=true

AC:

  • [ ] BS hoàn thành BA + đơn thuốc + cam đoan → bấm "In bộ hồ sơ" → render PDF gồm 3 phiếu; trạng thái = Đã in
  • [ ] Y tá tải 1 file PDF "ba-le-thi-huong-2026-05-02.pdf" cả bộ + UI mapping BA chính ✓ + Cam đoan ✓ + Đơn thuốc ✓ → trạng thái = Đã ký + scan đủ
  • [ ] Y tá tải duy nhất 1 file scan "ba.pdf" mapping chỉ BA chính → trạng thái vẫn Đã in; banner "Còn thiếu: Cam đoan, Đơn thuốc"
  • [ ] KH từ chối ký → Y tá tải "bien-ban-tu-choi.pdf" + check "Y tá làm chứng" → trạng thái Đã ký + scan đủ, refusal_witnessed=true

FR-010 — Kiểm tra an toàn dị ứng

Tham chiếu: DEC-011, DEC-012 | Ưu tiên: Must | SCR: Cross-màn hình | SAFETY CRITICAL

Mô tả nghiệp vụ phần mềm:

  • Vai trò: BS điền dị ứng; Hệ thống check rule
  • Dữ liệu: technical_category.allergy_risk_level (unknown/low/medium/high); allergy_check_policy; allergy_check_skip_log
  • Điều kiện: BA có thủ thuật xâm lấn (KT có allergy_risk_level IN (high, unknown))
  • Hành động: Trước khi cho hoàn thành BA, hệ thống check allergy_risk_level của tất cả KT trong record: (a) low/medium → cho phép hoàn thành (cảnh báo nếu thiếu phiếu dị ứng); (b) high → BẮT BUỘC có phiếu tiền sử dị ứng đã hoàn thành (DEC-012: lần đầu BẮT BUỘC điền mới; tái khám BS tick "không đổi" — reuse); (c) unknown → chặn hoàn thành cho tới khi Medical Lead phân loại lại allergy_risk_level
  • Kết quả: BA hoàn thành OK hoặc bị chặn với lý do rõ ràng
  • Ngoại lệ: Trường hợp khẩn cấp BS muốn skip → tạo allergy_check_skip_log với reason; không thay thế quy trình bình thường

AC:

  • [ ] BA có KT "Filler Hyaluronic" (allergy_risk=high) + chưa có phiếu dị ứng → chặn "Hoàn thành"; banner "Bắt buộc điền Phiếu tiền sử dị ứng trước thủ thuật xâm lấn"
  • [ ] BA có KT "Áp lạnh nitơ" (allergy_risk=low) → cho phép hoàn thành ngay
  • [ ] BA có KT "Laser CO2 mới chưa phân loại" (allergy_risk=unknown) → chặn "Hoàn thành"; banner "Kỹ thuật chưa được phân loại độ rủi ro dị ứng. Vui lòng liên hệ Medical Lead"
  • [ ] BS lần tái khám tick "Không đổi tiền sử dị ứng" → hệ thống reuse phiếu dị ứng cũ; cho phép hoàn thành

FR-011 — Permission 3 tầng + audit cross-branch tầng 3

Tham chiếu: DEC-008, DEC-009, DEC-010, DEC-040, DEC-043 | Ưu tiên: Must | SCR: Cross-màn hình + SCR-18 Compliance Audit | SECURITY CRITICAL

Canonical spec chi tiết: permission-spec.md v1.0.0 (P1-P12). FR này chỉ ghi mô tả nghiệp vụ + AC cấp cao; chi tiết action catalog, default seed, branch_mode, portal isolation, cache, migration, field masking, emergency governance, Compliance UI và 44 TC-PERM-* nằm ở permission-spec.

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Tất cả role có thể access clinical theo effective_permission = role_module.actions + portal + branch_mode + backend enforcement
  • Dữ liệu: role_module.actions, module_permission_action, branch_assignment, medical_record_access_log, emergency_override_session, permission_change_log, secure view masked theo tầng
  • Điều kiện: User request data clinical
  • Hành động: (1) FE check globalStore.hasPermission(moduleId, actionId) → ẩn menu/button (UX); (2) BE resolve qua ResolveClinicalPermission() (xem permission-spec.md P3) — kiểm hard-deny → portal → is_pos_only → action → branch scope → view_mode → field allowlist → rate limit; (3) 3 tầng dữ liệu chỉ trả qua field allowlist matrix (xem permission-spec.md P10); (4) Tầng 3 cross-branch chỉ qua emergency_override + audit; (5) Multi-role user: effective = UNION; branch_mode = max scope.
  • Kết quả: Data trả masked theo view_mode; mọi truy cập tầng 3 ghi log; mọi grant/revoke ghi permission_change_log
  • Ngoại lệ: Hard-deny pairs (permission-spec.md P1.3) override mọi grant — Sale có action view_medical_detail → backend vẫn 403; revoke giữa session → request kế tiếp 403 sau cache TTL 60s; lỗi resolver = deny không default allow

AC:

  • [ ] AC-011.1 BS DL CN Cao Lãnh xem BA của KH cùng CN → 200 OK với view_mode='full_tier3', đầy đủ field allowlist
  • [ ] AC-011.2 BS DL CN Cao Lãnh xem BA của KH ở CN Tân Bình II (khác CN, không emergency) → 200 OK với view_mode='summary'; tầng 3 omit khỏi response (KHÔNG phải null — field bị remove khỏi GraphQL response)
  • [ ] AC-011.3 BS dùng "Mở quyền khẩn cấp" với lý do ≥30 ký tự → tạo emergency_override_session TTL 1 giờ; insert medical_record_access_log với action='emergency_override'; notification ngay tới Medical Lead + QL CN + Compliance + Admin
  • [ ] AC-011.4 Sale CRM cố mở tầng 3 (kể cả admin gán quyền nhầm) → 403 với DenyCode='hard_deny' + log warning + alert Compliance
  • [ ] AC-011.5 Admin revoke view_medical_detail của BS A → trong tối đa 60s (TTL) hoặc ngay (WS push) → request kế tiếp của BS A trả 403; FE force refetch + toast "Quyền của bạn đã thay đổi. Vui lòng tải lại trang."
  • [ ] AC-011.13 Multi-role user (BS + Manager) → effective_permission = UNION action; branch_mode = max scope (all > multi_branch > branch > self); is_pos_only chỉ true khi MỌI role POS-only
  • [ ] AC-011.14 User is_pos_only=true cố mở Admin/CRM URL → server redirect về /pos + log warning; cố call API ngoài POS → 403 DenyCode='pos_only_violation'
  • [ ] AC-011.15 branch_mode='self' chỉ thấy record do user tạo / phụ trách (primary_doctor_id=$user OR created_by=$user)
  • [ ] AC-011.16 branch_mode='multi_branch' thấy union record CN trong branch_assignment; CN ngoài assignment → tầng 1+2 only
  • [ ] AC-011.17 Emergency override rate limit: ≤2/giờ (cảnh báo), ≤5/ngày (block), ≤15/tuần (force compliance review); session TTL 1 giờ; cron auto-expire mỗi 5 phút
  • [ ] AC-011.18 Cache TTL ≤60s sliding ở Redis BE; FE WS push hoặc TTL 60s; mọi mismatch FE ↔ BE → BE truth thắng + FE force refetch
  • [ ] AC-011.19 Migration backward-compat: existing role business (BS/Y tá/Sale/QL CN/Admin/Ops/Medical Lead) tự động được seed 22 action mới theo default seed matrix permission-spec.md P2.1; user không có role business chuẩn → output report cho admin review (KHÔNG tự seed)
  • [ ] AC-011.20 Compliance officer + Admin/Ops + Medical Lead xem medical_record_access_log qua SCR-18 với filter user/CN/action/sensitivity/anomaly; export Excel theo permission-spec.md P11.3; Day-1 hoặc Phase 2 tuỳ PD-PERM-004

FR-012 — Sổ khám bệnh + Sổ thủ thuật

Tham chiếu: DEC-014, DEC-015 | Ưu tiên: Must | SCR: SCR-11, SCR-12

Mô tả nghiệp vụ phần mềm:

  • Vai trò: QL CN, Admin/Ops xem; export
  • Dữ liệu: Derive từ clinical_record + appointment + product/technical_category
  • Điều kiện: Có quyền clinical_record.export_visit_log
  • Hành động: (1) Sổ khám bệnh: list lượt khám trong ngày/tháng theo CN; STT theo FORMULA-002; cột: STT, Ngày, Mã BA, Tên KH, BS, Chẩn đoán chính, Trạng thái BA; (2) Sổ thủ thuật: list ca có KT thủ thuật; STT theo FORMULA-003; thêm cột: KT, Vật tư, Biến chứng (nếu có); export Excel async > 1000 dòng (chunk 100)
  • Kết quả: Table trên màn + export Excel
  • Ngoại lệ: User không có view_medical_detail → cột "Chẩn đoán chính" hiển thị "—"

AC:

  • [ ] QL CN Cao Lãnh xem sổ khám tháng 04/2026 → table 147 lượt khám với STT DVA-CL-00001/2026..DVA-CL-00147/2026
  • [ ] Sổ thủ thuật chỉ list ca có KT thủ thuật → 89 ca trong tháng → STT DVA-CL-TT-00001/2026..00089/2026
  • [ ] Export Excel sổ khám tháng có 1.500 dòng → async; user nhận notification "Đã xuất file. Bấm để tải về."
  • [ ] Sale (không có view_medical_detail) export sổ khám → cột "Chẩn đoán" hiển thị "—" thay vì ICD-10

FR-013 — Notification "cần BA" + "BA thiếu"

Tham chiếu: DEC-013 | Ưu tiên: Must | SCR: Cross-màn hình

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống event trigger; BS, Y tá, QL CN nhận
  • Dữ liệu: notification_template 7 trigger code mới, notification_queue
  • Điều kiện: Event xảy ra (order insert, BA quá hạn, scan thiếu, ...)
  • Hành động: Insert notification_template cho 7 trigger: noti_medical_record_incomplete, _needs_print, _needs_scan, _allergy_warning, _end_of_day, _emergency_override, _handoff_created; event handler enqueue + scheduler send qua ZNS/SMS/Push; dedupe key clinical_record_{id}_{action}
  • Kết quả: BS/Y tá/QL nhận thông báo kịp thời
  • Ngoại lệ: Push deeplink chỉ chứa record_id + branch (không chẩn đoán/form/scan); Sale không nhận notification clinical

AC:

  • [ ] Lễ tân tạo đơn dịch vụ "Filler Hyaluronic" cho khách Lê Hương → event order_insert trigger noti_medical_record_incomplete → BS DL CN Cao Lãnh nhận push "Có 1 BA cần tạo cho khách Lê Hương — bấm để mở Bàn việc bác sĩ"
  • [ ] BA "Bản nháp" quá 24h → cron cronClinicalRecordReminder enqueue thông báo cho BS phụ trách
  • [ ] Cuối ngày 19:30 (trước cutoff 20:00) → cron gửi reminder "Còn 5 BA chưa hoàn thành tại CN Cao Lãnh"
  • [ ] BS A trigger Mở quyền khẩn cấp → notification ngay tới Medical Lead + Ops; deeplink chỉ có record_id, không có chẩn đoán

FR-014 — Sequence generator generic

Tham chiếu: DEC-013, DEC-014, DEC-015 | Ưu tiên: Must | SCR: Cross-màn hình

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (action handler generateSequence)
  • Dữ liệu: sequence_generator(entity_type, branch_id, year, next_value)
  • Điều kiện: Cần sinh STT/mã mới
  • Hành động: Action handler atomically increment + return: (entity_type='clinical_profile', branch_id=X, year=2026) → next NNNNN; áp dụng cho FORMULA-001 (mã BA chính), FORMULA-002 (STT sổ khám), FORMULA-003 (STT sổ thủ thuật)
  • Kết quả: Số liên tục, không trùng, không skip (atomic)
  • Ngoại lệ: Concurrent insert → row lock; sequence overluồng 99999 → fallback 6 chữ số sau 99999

AC:

  • [ ] 100 BA tạo concurrent tại CN Cao Lãnh → 100 mã liên tục DVA-DL-CL-2026-00001..00100, không trùng, không skip
  • [ ] Đầu năm 2027 sinh STT sổ khám → reset về DVA-CL-00001/2027; mã BA chính (FORMULA-001) vẫn tiếp DVA-DL-CL-2027-00001 (không reset, format có year)
  • [ ] STT sổ thủ thuật chỉ sinh khi clinical_record có ít nhất 1 product với KT type='procedure'

FR-015 — Kiểm tra sẵn sàng + an toàn phát hành per CN

Tham chiếu: DEC-031, DEC-041 | Ưu tiên: Must | SCR: SCR-04f

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin/Ops phát hành; Hệ thống tự kiểm tra
  • Dữ liệu: clinic_module_publication.readiness_snapshot (JSONB), clinic_module_publication.status/effective_at
  • Điều kiện: Module ở setup_draft hoặc paused
  • Hành động: Wizard bước 6 chạy 10 kiểm tra sẵn sàng check: R-01 Loại PK · R-02 Giấy phép còn hạn · R-03 Danh mục KT đủ · R-04 Allergy_risk_level không còn unknown · R-05 Classification 100% (không còn unclassified) · R-06 Phân quyền (BS/Y tá/QL CN) · R-07 Print preview test · R-08 BA demo test · R-09 Support owner · R-10 Legal/SYT artifact + scanner/printer/upload-account; mỗi mục đạt/không đạt; chỉ đạt 100% mới cho phát hành
  • Kết quả: readiness_snapshot lưu trạng thái 10 mục; status='ready_to_publish'; CTA "Phát hành (effective DD/MM/YYYY HH:mm)" mở
  • Ngoại lệ: R-xx không đạt → highlight + deeplink sang màn cần sửa; user fix xong quay lại bước 6 re-check

AC:

  • [ ] CN Cao Lãnh wizard bước 6 → 9/10 đạt; R-04 không đạt (1 KT vẫn unknown) → highlight đỏ R-04 + deeplink sang Danh mục kỹ thuật → quay lại
  • [ ] R-04 fixed → re-check → 10/10 đạt → CTA "Phát hành" mở
  • [ ] Admin chọn effective 02/05/2026 09:00clinic_module_publication.effective_at='2026-05-02 09:00:00+07'; trạng thái scheduled
  • [ ] Tới 09:00 ngày 02/05 → cron auto chuyển scheduled → live; vận hành gate (DEC-041) bật

FR-016 — Trang xem an toàn (Sale) + Phiếu chuyển bác sĩ tư vấn

Tham chiếu: DEC-009, DEC-032 | Ưu tiên: Must | SCR: SCR-13

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Sale CRM xem; Sale tạo handoff; BS nhận
  • Dữ liệu: Secure view clinical_sales_view (chỉ tầng 1+2 đã diễn giải an toàn); clinical_sales_handoff(sale_id, doctor_id, customer_id, branch_id, expectations, safe_notes); dictionary safe_alert_level/code/text
  • Điều kiện: Sale có quyền clinical_sales.view_safe_summary; Sale CRM portal
  • Hành động: (1) Trang xem an toàn (Sale) hiện thông tin tóm tắt: dị ứng (safe_alert từ dictionary), bệnh nền (label an toàn), số lượt khám, lịch sử dịch vụ, count BA — KHÔNG có chẩn đoán/form/scan/ảnh y tế; (2) CTA "Chuyển bác sĩ tư vấn" → form handoff: chọn BS, ghi expectations + safe notes; (3) BS nhận notification noti_handoff_created
  • Kết quả: Sale tư vấn đúng ngữ cảnh an toàn; BS nhận phiếu chuyển
  • Ngoại lệ: Sale cố click vào diagnosis/form/scan → 403; safe note có chẩn đoán/y lệnh → backend từ chối với regex check

AC:

  • [ ] Sale CRM xem khách Lê Hương → màn hiển thị: "Dị ứng: Có 1 cảnh báo (chuyển BS để xem chi tiết)"; "Lịch sử: 12 lượt khám tại Cao Lãnh"; KHÔNG hiện ICD-10 hay diagnosis text
  • [ ] Sale bấm "Chuyển bác sĩ tư vấn" → form chọn BS DL CN Cao Lãnh; ghi expectations "Khách muốn liệu trình trị mụn"; safe note "Khách có ngân sách 5tr"
  • [ ] Sale ghi safe note "Khách bị mụn trứng cá nặng L70.0" → backend từ chối 422 "Ghi chú không được chứa thuật ngữ y khoa hoặc mã ICD-10"
  • [ ] BS nhận notification + mở handoff → thấy expectations + safe notes; CTA "Đặt lịch khám" sang luồng tạo appointment

FR-017 — Bàn việc bác sĩ với 6 nhóm việc

Tham chiếu: DEC-033 | Ưu tiên: Must | SCR: SCR-14

Mô tả nghiệp vụ phần mềm:

  • Vai trò: BS, Y tá hỗ trợ
  • Dữ liệu: Server-side query doctor_workbench_view trả 6 bucket: need_record (cần tạo BA), draft (BA nháp), need_print (cần in), need_scan (cần scan), sales_handoff (handoff Sale chờ), intake_review (Phiếu khách tự khai chờ nhận)
  • Điều kiện: BS có quyền doctor_workbench.access; CN ở live
  • Hành động: Mỗi bucket có task_key ổn định, queue_owner_type='doctor', queue_owner_id={BS}; CTA tiếp theo theo loại task; need_record chỉ sinh khi appointment/order thật sự có dịch vụ mapped_requires_ba
  • Kết quả: BS thấy danh sách việc trong ngày; bấm task → luồng tương ứng (tạo BA / mở BA nháp / in / scan / handoff / intake)
  • Ngoại lệ: Dịch vụ unclassified → KHÔNG sinh need_record task (tránh false-positive); cảnh báo Ops thay vào đó (FR-019)

AC:

  • [ ] BS DL CN Cao Lãnh sáng 02/05/2026 mở Bàn việc bác sĩ → 6 bucket với count: need_record=3, draft=2, need_print=1, need_scan=4, sales_handoff=1, intake_review=2
  • [ ] BS bấm task need_record cho khách Lê Hương → chuyển sang luồng tạo BA DL với appointment/customer pre-fill
  • [ ] Lễ tân tạo đơn với dịch vụ unclassified → KHÔNG sinh need_record task; thay vào đó cảnh báo Ops
  • [ ] BS B (khác CN) cố vào Bàn việc bác sĩ CN Cao Lãnh → query trả empty (branch scope)

FR-018 — Phiếu khách tự khai (token 15 phút)

Tham chiếu: DEC-034 | Ưu tiên: Should | SCR: SCR-15

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Lễ tân/Y tá mở session; KH điền; BS review/nhận
  • Dữ liệu: customer_clinical_intake(token, customer_id, branch_id, appointment_id, expires_at, form_data, source_type='customer_intake'); clinical_form_instance.source_type/source_ref_id
  • Điều kiện: KH đến quầy có lịch hẹn; appointment đã tạo
  • Hành động: (1) Lễ tân/Y tá bấm "Mở phiên khách tự khai" cho appointment cụ thể → server tạo token 15 phút + URL /p/customer-clinical-intake/{token}; (2) Mở URL trên tablet/quầy; KH điền dị ứng/bệnh nền/thuốc đang dùng (tier 1+2 only); KH gửi qua token (KHÔNG gửi tự do customer_id/branch_id); server validate appointment/record cùng KH + CN; (3) BS mở BA cho KH → thấy phiếu khách tự khai chờ review; bấm "Nhận vào hồ sơ" → ghi vào clinical_form_instance với source_type='customer_intake' + source_ref_id={intake.id}; audit medical_record_access_log
  • Kết quả: Giảm thời gian BS nhập hành chính; an toàn dị ứng nhanh hơn
  • Ngoại lệ: Token quá 15 phút → 410 Gone, KH phải nhờ Lễ tân mở lại; KH trên tablet đổi sai KH/CN → server từ chối 422

AC:

  • [ ] Lễ tân CN Cao Lãnh mở session cho appointment khách Lê Hương 14:30 → tablet hiển thị form intake cho Lê Hương
  • [ ] KH điền 5 dị ứng + 2 bệnh nền + 3 thuốc đang dùng → gửi qua token → server lưu customer_clinical_intake.form_data
  • [ ] Token hết hạn sau 15 phút → KH bấm gửi → 410 "Phiên đã hết hạn. Vui lòng nhờ lễ tân mở lại."
  • [ ] BS mở BA cho Lê Hương → thấy "Phiếu khách tự khai (chờ review)"; bấm "Nhận vào hồ sơ" → form BA prefill 5 dị ứng + 2 bệnh nền; source_type='customer_intake'

FR-019 — Trang điều phối phòng khám + Chốt ngày phòng khám

Tham chiếu: DEC-035, DEC-036 | Ưu tiên: Must | SCR: SCR-16, SCR-17

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin/Ops xem toàn chuỗi; QL CN xem CN mình + chốt ngày
  • Dữ liệu: clinic_ops_dashboard_view (drift cấu hình, BA tồn, scan thiếu); clinic_daily_close(branch_id, date, draft_count, missing_scan_count, escalated_count, closed_by, closed_at); branch_technical_config.close_cutoff_time/close_reminder_times (mặc định 20:00 Asia/Ho_Chi_Minh)
  • Điều kiện: Admin/Ops có clinic_ops.view_all_dashboard; QL CN có clinic_ops.view_branch_dashboard
  • Hành động: (1) Trang điều phối phòng khám: theo dõi trạng thái CN, dịch vụ unclassified, KT unknown, BA nháp quá hạn, bản in chưa scan, mở quyền khẩn cấp, drift kiểm tra sẵn sàng; (2) Chốt ngày: cron đọc cutoff per CN; pre-cutoff reminder; tới cutoff → cron cronClinicalDailyClose ghi nhận draft_count + missing_scan_count; QL CN bấm "Chốt ngày" → hộp thoại xác nhận "Còn 5 BA nháp + 3 BA chưa scan. Bạn xác nhận chốt?" → log clinic_daily_close; nếu còn P0 chưa giải quyết → báo Ops/Medical Lead
  • Kết quả: Ops kiểm soát rollout chuỗi; QL CN có gate cuối ngày
  • Ngoại lệ: Cron miss → manual close vẫn được; rollback paused → cron skip CN

AC:

  • [ ] Ops mở Trang điều phối → thấy 12 CN active; CN Cao Lãnh có cảnh báo "3 dịch vụ chưa phân loại"; CN Tân Bình II có "5 BA nháp quá hạn"
  • [ ] Pre-cutoff 19:30 → cron gửi reminder "Còn 5 BA chưa hoàn thành tại CN Cao Lãnh trước 20:00"
  • [ ] Đúng 20:00 → cron ghi nhận trạng thái; QL CN bấm "Chốt ngày" → hộp thoại xác nhận với số liệu → log clinic_daily_close.closed_at=20:15
  • [ ] Còn 1 BA P0 chưa scan → cron gửi cảnh báo Ops + Medical Lead qua notification

A5) Vòng đời (Lifecycle)

LIFECYCLE-001 — Module Phòng khám per CN

Trạng tháiÝ nghĩa nghiệp vụĐiều kiện vàoĐiều kiện raTerminal
offModule chưa bậtTrạng thái mặc địnhAdmin bậtKhông
setup_draftĐang cấu hìnhBật moduleWizard đạtKhông
ready_to_publishĐã đạt kiểm tra sẵn sàng, chờ Admin phát hànhWizard đạtAdmin phát hànhKhông
scheduledĐã phát hành, chờ effective_atAdmin chọn effectiveTới effectiveKhông
liveĐang hoạt động vận hànhEffective_at đếnAdmin tạm dừngKhông
pausedTạm dừng vận hànhAdmin tạm dừngAdmin tiếp tục/sửaKhông
TừEventĐiều kiệnSangGiải thích
offmở_moduleAdmin role + branch type clinic-eligiblesetup_draftBật cấu hình
setup_draftwizard_complete6 bước + R-01..R-10 đạtready_to_publishSẵn sàng phát hành
ready_to_publishpublish_nowAdmin role + effective_at đã setlivePhát hành ngay
ready_to_publishpublish_scheduledAdmin role + effective_at tương laischeduledLập lịch phát hành
scheduledcron_effective_reachedCron tới effective_atliveAuto chuyển đang hoạt động
livepauseAdmin role + lý dopausedTạm dừng
pausedresumeAdmin roleliveTiếp tục
pausedresume_with_changesAdmin role + sửa cấu hìnhready_to_publishRe-check trước khi đang hoạt động lại

LIFECYCLE-002 — Bệnh án lượt khám

TừEventGuardSangSide effects
draftcompleteAllergy check đạt + ICD-10 primary cócompletedNotification BS, log audit
completedprintCó quyền printprintedRender PDF, lưu HTML, log
printedupload_scanY tá có quyền upload_scan + checklist scan đủ theo casesignedUpdate flag, notification QL CN
printedcorrect_after_printBS có quyền edit_medical_formsuperseded (bản cũ) + printed (bản v2)Tạo bản v2; bản cũ KHÔNG xóa
signedarchive_after_retentionCron retention 1 nămarchivedMove file scan sang cold storage

Quy tắc cứng: không cho xóa BA y tế đã completed/printed/signed (DEC-023). Sửa sai sau in = in bản v2; bản cũ giữ làm audit.

LIFECYCLE-003 — Phiếu khách tự khai


A6) Giả định và rủi ro

Giả định

IDGiả địnhPhụ trách xác nhận
ASM-001Hot MinIO storage 1 năm đủ + cold archive 9 năm OK với cost projectionInfra Lead
ASM-002ICD-10 bản 2015 BYT đủ cho pilot 30 ngàyMedical Lead
ASM-003Mỗi CN pilot có scanner/printer/account upload sẵn sàngOps Lead
ASM-004Sở YT Đồng Tháp + TPHCM chấp nhận luồng giấy ký tay + scan referenceLegal
ASM-005Pilot 2 CN trong 30 ngày đủ data để đánh giá đại diệnPO
ASM-006Sale chấp nhận không xem tầng 3Sale Lead
ASM-007BS phòng khám thực tế chấp nhận autosave 30s và Bàn việc bác sĩMedical Lead + 2 BS pilot

Rủi ro

IDRủi roẢnh hưởngXác suấtCách giảm thiểu
RSK-001Appointment.OrderItemID non-pointer → silent bug walk-inCaoCaoRefactor *uuid.UUID + null-guard 5 call site (Phase 0 blocker)
RSK-002Sai phân quyền tầng 3 → Sale xem được chẩn đoánCaoTrung bìnhPermission v2 backend enforce; secure view masked; QA test grant/revoke/portal split
RSK-003Raw clinical tables expose cho vận hành userCaoTrung bìnhDEC-040 — không cấp raw select/mutation; secure view/action; security test metadata
RSK-004Allergy unknown bị hiểu là medium → bỏ quaCaoThấpTrạng thái mặc định chặn + bắt Medical Lead phân loại trước khi đang hoạt động
RSK-005Đã ký + scan đủ hiểu sai = chỉ cần 1 scanCaoTrung bìnhChecklist scan theo case; QA phủ refusal evidence
RSK-006Legal/SYT không xác nhận luồng giấy ký + scanCaoThấpVăn bản xác nhận trước go-live; NO-GO nếu chỉ xác nhận miệng
RSK-007Scope creep regression sang module không liên quanTrung bìnhCaoImpact Boundary Matrix v2.0 (DEC-044) làm scope lock; QA D6 smoke regression
RSK-008BS không dùng Bàn việc bác sĩ nếu có task giảTrung bìnhCaoneed_record chỉ join dịch vụ mapped_requires_ba; QA test false-positive
RSK-009Bản scan trễ → hồ sơ pháp lý chưa hoàn chỉnhTrung bìnhCaoChốt ngày + reminder + escalation
RSK-010Rollback chạy down migration sau khi đã có BA thậtTrung bìnhThấpD0+ chỉ pause/code rollback giữ DB; down migration chỉ pre-D0

A7) Bảng thuật ngữ

Tham chiếu canonical: SOURCE_OF_TRUTH.md mục 0 (34 thuật ngữ A1–E34). PRD A7 chỉ ghi tóm tắt cho người đọc PRD.

Tiếng ViệtEN/codeĐịnh nghĩaPhân biệt với
Hồ sơ bệnh án chínhclinical_profileHồ sơ ổn định theo KH/CN/loại PK; giữ mã BA chính≠ Bệnh án lượt khám
Bệnh án lượt khámclinical_recordBản ghi y tế cho 1 appointment + form_type≠ Hồ sơ bệnh án chính / ≠ Lượt khám
Lượt khámappointment1 lần KH đến PK; có thể có 0–2 bệnh án lượt khám
Phiếu biểu mẫuclinical_form_instance1 tờ biểu mẫu cụ thể trong bộ hồ sơ≠ Khuôn biểu mẫu
Khuôn biểu mẫuform_templateĐịnh nghĩa schema JSON≠ Phiếu biểu mẫu
Danh mục kỹ thuậttechnical_categoryMã + tên KT do Sở YT cấp phép cho CN≠ Dịch vụ thương mại
Phân loại dịch vụ — kỹ thuậtservice_clinical_classificationDV thương mại theo CN: cần BA / không cần BA / chưa phân loại
Phòng khámbranch.features.clinic_enabledModule bật/tắt theo CN≠ Chi nhánh
Bản phát hành phòng khámclinic_module_publicationBản ghi quản lý phát hành/tạm dừng module per CN≠ Bật/tắt
Bàn việc bác sĩdoctor_workbench_viewMàn gom 6 nhóm việc BA trong ngày
Phiếu khách tự khaicustomer_clinical_intakeDữ liệu KH tự khai trước khám (token 15 phút)≠ Phiếu biểu mẫu BA
Trang xem an toàn (Sale)(secure view)Sale xem tầng 1+2 đã diễn giải an toàn≠ Hồ sơ bệnh án chính
Phiếu chuyển bác sĩ tư vấnclinical_sales_handoffSale chuyển khách sang BS
Trang điều phối phòng khámclinic_ops_dashboard_viewDashboard rollout toàn chuỗi≠ Branch Detail
Chốt ngày phòng khámclinic_daily_closeChecklist cuối ngày≠ Go-live checklist
Tải bản scan đã kýupload_scan actionTải bản scan đã ký lên hệ thống≠ In / ≠ Tải ảnh trước-sau
Ảnh trước–sau điều trịbefore_after (file type)Ảnh y tế gắn theo bệnh án (tầng 3)≠ Bản scan / ≠ treatment-images (CRM)
Mở quyền khẩn cấpemergency_overrideTạm mở tầng 3 khác CN + audit
Kiểm tra sẵn sàngreadiness_snapshotChecklist 100% mục trước phát hành≠ Go-live checklist tổng
Tầng 1/2/3tier_1/2/3Phân tầng dữ liệu nhạy cảm

A8) Công thức nghiệp vụ

A8 là canonical (PO sở hữu). Dev Spec C3 chỉ ghi SQL implementation delta, ref A8.

FORMULA-001: Mã hồ sơ BA chính

  • Mô tả: Định danh duy nhất cho hồ sơ y tế ổn định của 1 KH tại 1 CN theo loại PK.
  • Công thức: DVA-[type]-[branch_code]-[year]-[sequence_5_digit]
  • Biến số:
    • type: 'DL' hoặc 'TM' — nguồn clinical_profile.form_type
    • branch_code: mã ngắn CN (VD: CL, TB2) — nguồn branch.code
    • year: năm tạo profile đầu tiên — nguồn clinical_profile.created_at YEAR
    • sequence_5_digit: monotonic per CN+type, padded 5 chữ số; không reset theo năm
  • Đơn vị: chuỗi ký tự
  • Ví dụ: KH đầu tiên tạo BA DL tại CN Cao Lãnh năm 2026 → DVA-DL-CL-2026-00001
  • Trường hợp cá biệt:
    • 1 KH có dịch vụ DL+TM tại cùng CN → 2 mã (khác type)
    • KH chuyển CN khác → mã mới tại CN đó (khác branch_code)
    • Sequence overluồng 99999 → fallback 6 chữ số sau 99999
  • Reset: Không reset

FORMULA-002: STT sổ khám bệnh

  • Mô tả: Số thứ tự lượt khám trong sổ khám của CN, reset theo năm.
  • Công thức: DVA-[branch_code]-[sequence_5_digit]/[year]
  • Biến số:
    • branch_codebranch.code
    • yearappointment.from YEAR
    • sequence_5_digit: STT trong năm
  • Ví dụ: Lượt khám thứ 147 năm 2026 tại CN Cao Lãnh → DVA-CL-00147/2026
  • Reset: 00:00 ngày 1/1 mỗi năm

FORMULA-003: STT sổ thủ thuật

  • Mô tả: Số thứ tự ca có KT thủ thuật, reset theo năm.
  • Công thức: DVA-[branch_code]-TT-[sequence_5_digit]/[year]
  • Biến số: Như FORMULA-002
  • Ví dụ: Ca thủ thuật thứ 89 năm 2026 tại CN Cao Lãnh → DVA-CL-TT-00089/2026
  • Trường hợp cá biệt: Chỉ sinh khi clinical_record có ít nhất 1 product với KT technical_category.type='procedure'. Ca không thủ thuật không có STT này.
  • Reset: Theo năm

FORMULA-004: % BA hoàn thành đúng ngày

  • Mô tả: Tỉ lệ BA bấm "Hoàn thành" cùng ngày với appointment.from.
  • Công thức: count_completed_same_day / count_total_appointment_need_ba × 100
  • Biến số:
    • count_completed_same_day: số clinical_record với completed_at::date = appointment.from::date
    • count_total_appointment_need_ba: số appointment/form_type trong ngày có product mapping KT
  • Đơn vị: % (2 chữ số thập phân, dấu phẩy: 90,00%)
  • Ví dụ: 90 BA hoàn thành / 100 appointment cần BA → 90,00%
  • Trường hợp cá biệt:
    • Appointment không cần BA (explicitly_no_ba) hoặc visit_only=true → exclude khỏi denominator
    • Product unclassified → KHÔNG được tính là "không cần BA"; phải vào alert/config backlog
    • Walk-in luôn có appointment; nếu appointment.from chưa set thì dùng clinical_record.created_at::date và flag data quality
    • BA hoàn thành qua ngày (VD: khám 18h, hoàn thành 22h) → vẫn tính same day
    • Denominator = 0 → hiển thị

FORMULA-005: Thời gian TB BS điền 1 BA

  • Mô tả: Average duration từ tạo clinical_record tới completed_at.
  • Công thức: AVG(completed_at - created_at) WHERE status IN ('completed','printed','signed')
  • Đơn vị: phút (integer)
  • Ví dụ: 100 BA, tổng 900 phút → 9 phút/BA
  • Trường hợp cá biệt:
    • BA draft quá 24h (BS quên) → exclude khỏi average
    • BA hoàn thành <30s → flag outlier

FORMULA-006: Tỉ lệ BA đủ chữ ký + scan đủ

  • Mô tả: % BA đạt trạng thái Đã ký + scan đủ theo checklist case.
  • Công thức: count_signed / count_completed × 100
  • Biến số:
    • count_signedclinical_record WHERE status='signed'
    • count_completedclinical_record WHERE status IN ('completed','printed','signed')
  • Đơn vị: % (2 chữ số thập phân)
  • Trường hợp cá biệt:
    • BA refusal (KH từ chối ký) có biên bản y tá làm chứng → tính là signed với flag refusal_witnessed=true
    • Denominator = 0 →

FORMULA-007: Allergy risk level → action

  • Mô tả: Map technical_category.allergy_risk_level → action khi hoàn thành BA.
  • Công thức (decision table):
allergy_risk_levelAction
lowCho hoàn thành; cảnh báo nếu thiếu phiếu dị ứng
mediumCho hoàn thành; bắt buộc có phiếu dị ứng (lần đầu) hoặc tick "không đổi" (tái khám)
highBẮT BUỘC có phiếu dị ứng đã hoàn thành; nếu không → chặn "Hoàn thành"
unknownchặn "Hoàn thành" cho tới khi Medical Lead phân loại lại
  • Trường hợp cá biệt: Khẩn cấp → BS có thể tạo allergy_check_skip_log với reason; không thay thế quy trình bình thường

A11) Truy vết (Traceability)

Mapping FR → Component FE → Artifact BE → TC. Canonical: dev-spec.md C11.

FRComponent FE chínhArtifact BE chínhQA TC
FR-001BranchDetail.tsx (tab Phòng khám)clinic_module_publication table + enableClinicModule actionTC-001-* (4 TC)
FR-002BranchDetailClinicConfig.tsx (5-step wizard)branch_technical_config, technical_category, service_clinical_classificationTC-002-* (4 TC)
FR-003BranchDetailClinicConfig.tsx step 4service_clinical_classificationTC-003-* (4 TC)
FR-004AppointmentForm.tsx (walk-in toggle)Appointment.OrderItemID refactor + null-guardTC-004-* (4 TC) [CRITICAL]
FR-005ClinicalRecordFormDL.tsxclinical_record, clinical_form_instance, icd10_codeTC-005-* (5 TC)
FR-006ClinicalRecordFormTM.tsx (4 phân hệ)Tương tự FR-005TC-006-* (5 TC)
FR-007SharedFormsDialog.tsxform_template shared, prescriptionTC-007-* (6 TC)
FR-008(autosave hook)update_clinical_form_instance mutationTC-008-* (5 TC)
FR-009PrintClinicalRecordDialog.tsx, UploadScanDialog.tsxprintClinicalRecord, uploadClinicalScan actions; reference_fileTC-009-* (6 TC)
FR-010(cross-màn hình guard)allergy_check_policy, allergy_check_skip_logTC-010-* (6 TC) [SAFETY CRITICAL]
FR-011(cross-màn hình)Permission v2 secure view + medical_record_access_logTC-011-* (11 TC) [SECURITY CRITICAL]
FR-012MedicalVisitLog.tsx, ProcedureLog.tsxHasura view derive từ clinical_recordTC-012-* (4 TC)
FR-013(notification UI)7 notification trigger mới + event handlersTC-013-* (3 TC)
FR-014sequence_generator + action generateSequenceTC-014-* (4 TC)
FR-015BranchDetailClinicConfig.tsx step 6clinic_module_publication.readiness_snapshot + cron cronEffectiveReachedTC-015-* (5 TC)
FR-016CustomerSalesSafeView.tsx, SalesHandoffDialog.tsxclinical_sales_view secure view + clinical_sales_handoffTC-016-* (7 TC)
FR-017DoctorWorkbench.tsxdoctor_workbench_view queryTC-017-* (5 TC)
FR-018CustomerClinicalIntakeForm.tsx (POS portal)customer_clinical_intake + token validationTC-018-* (6 TC)
FR-019ClinicOpsDashboard.tsx, ClinicDailyCloseDialog.tsxclinic_ops_dashboard_view, clinic_daily_close + cron cronClinicalDailyCloseTC-019-* (5 TC)

Tổng: 99 core test case (xem qa-test-plan.md D2 chi tiết).


A12) Ma trận ranh giới ảnh hưởng (Impact Boundary Matrix)

Canonical: SOURCE_OF_TRUTH.md mục 2.4. PRD A12 ref ngắn.

Ảnh hưởng trực tiếp: Settings/Branch Detail · Permission v2 · Appointment · POS/Order · Product/DV · Customer/CRM · Clinical (BUILD) · Reference File/MinIO · Print/PDF · Notification v2 · Export · Bàn việc bác sĩ (BUILD) · Trang xem an toàn (Sale) (BUILD) · Phiếu khách tự khai (BUILD) · Trang điều phối + Chốt ngày (BUILD).

Ảnh hưởng gián tiếp (smoke regression bắt buộc): Dashboard/Report chung · Ticket/Task/Project · Mobile/Push · Export API · Support/Incident · Complaint/CSKH.

Không ảnh hưởng Day-1 (smoke regression chứng minh): Wallet · Salary/KPI/Timekeeping · Affiliate/Achievement/Gamification · CMS/Geo/Conversation · Finance/P&L · Warehouse/Inventory.

QA D6 phủ regression theo 3 lớp này.


End of PRD v2.0.0. File này là quy ước nghiệp vụ chuẩn. Mọi DEC/FR/AC được truyền xuống ui-spec.md (B sections) + dev-spec.md (C sections) + qa-test-plan.md (D sections). Nếu tài liệu dẫn xuất xung đột với PRD A8 (công thức), PRD thắng; nếu xung đột với SoT mục 0 (thuật ngữ canonical), SoT thắng.