Skip to content

Đặc tả giao diện (UI Spec) — Chấm công đa đơn vị trên shared tenant

v3.1.5 — 21/04/2026

Thay đổiSectionẢnh hưởng
Bổ sung product execution contract: mục tiêu màn, CTA hierarchy, interaction behavior, action feedback cho các màn xương sốngSCR-00, SCR-00C, SCR-01, SCR-02, SCR-03, SCR-05, SCR-06FE Admin, FE Mobile, UI/UX, QA
Bổ sung Copy Text Dictionary, Responsive + Accessibility Rules, Tooltip Dictionary để giảm phần FE/UI phải tự quyếtB6, B7, B9FE, UI/UX, QA
Chuẩn hóa SCR-00 thành setup center: checklist deeplink được cả màn ngoài SCR-00, bổ sung Setup Navigatorwizard-style flowB2) SCR-00FE Admin, QA, Ops
Tách Tab Quy định theo tư duy SaaS self-serve: Cơ bản / Nâng cao, preset, copy config, draft/review/publishTab Quy địnhFE Admin, UX, QA
Bổ sung bulk actions cho tenant lớn ở Nhân viên áp dụng: gán theo scope, đồng bộ theo cơ cấu, review deltaTab Nhân viên áp dụngFE Admin, Ops
Bổ sung preview runtime trong form ca để nhìn trước CTA mobile, day detail và export contractSCR-00CFE Admin, FE Mobile, QA
Bổ sung cơ chế an toàn khi đổi config đang live: draft, impact preview, effective date và publish từ ngày maiTab Quy địnhFE Admin, BE, Ops
Chuyển cách tính công xuống Ca làm việc, bỏ config mode ở Tab Quy định, và thêm screen contract mới cho Work ShiftSCR-00C, Tab Quy địnhFE Admin, BE, QA
Chốt GPS UX theo 2 lớp: tọa độ chi nhánh dùng branch master data hiện có, bán kính/rule GPS nằm ở SCR-00 > Quy địnhB2) SCR-00FE Admin, UI/UX, QA
Chốt SCR-00 là màn mới trong settings, có route/menu rõ ràng và Readiness ChecklistB2) SCR-00FE Admin, QA
Chuẩn hóa Chi nhánh áp dụng / Phòng ban áp dụng / Nhân viên áp dụng / Triển khai, thêm STT và nhãn tiếng Việt cho field codeB2) SCR-00FE Admin, UI/UX
Bổ sung flow bulk assign/reassign/unassign, import/paste mã NV và gating Bật áp dụngB2) SCR-00FE Admin, QA

Tham chiếu (Ref): PRD v3.1.4 | Ngày (Date): 21/04/2026

Changelog

VersionDateAuthorThay đổi
3.1.521/04/2026PO/BANâng ui-spec lên mức product execution-ready: thêm objective/CTA hierarchy, interaction behavior, action feedback, responsive/a11y, copy text và tooltip contracts
3.1.421/04/2026PO/BAĐiều chỉnh ui-spec theo hướng SaaS-ready: setup center, config basic/advanced, bulk scope actions, preview runtime và publish-safe flow
3.1.315/04/2026PO/BAĐổi UI Công chuẩn sang nhóm áp dụng công chuẩn, hiển thị phòng ban áp dụng, bỏ hoàn toàn cách hiểu cũ theo branch_label_id
3.007/04/2026PO/BAInitial UI spec cho settings/timekeeping/mobile/export multi-unit
3.0.107/04/2026PO/BAĐồng bộ contract nghỉ giữa ca fixed/flex, annual leave Day-1 và remote nhóm nhỏ
3.113/04/2026PO/BAChuẩn hóa UX vận hành: route/menu SCR-00, readiness checklist, tab Triển khai, vocabulary tiếng Việt, STT/cột hiển thị chuẩn, bulk assignment và soft-disable mapping
3.1.113/04/2026PO/BALàm rõ GPS UX: không tạo màn GPS master riêng; dùng branch master data cho tọa độ chi nhánh và đặt gps_radius_meters làm override per-unit trong SCR-00, có deeplink Xem/Sửa tọa độ chi nhánh gốc
3.1.214/04/2026PO/BAChuyển cách tính công từ Đơn vị chấm công xuống Ca làm việc, đổi Số ngày công thành Công tối đa của ca, thêm Giờ chuẩn của cagps_required ở shift để quyết định Bắt buộc / Không bắt buộc GPS theo từng ca

B1) Bản đồ màn hình (Screen Map)

SCRTênReuse shell hiện cóMô tả delta
SCR-00Cấu hình đơn vị chấm côngNew page in settings moduleCấu hình timekeeping_unit, map branch/department/user trong phạm vi chấm công
SCR-00CWork Shift / Shift Group SettingsShiftSetting.tsxThêm filter timekeeping_unit, reuse field giờ nghỉ trưa hiện có, chọn mode nghỉ fixed/flex, cấu hình 2 mốc/4 mốc
SCR-00DTimekeeping Approver SettingsApproverPage.tsxChỉ mở delta cho request type thuộc timekeeping
SCR-01Working ScheduleWorkingSchedule.tsxThêm filter timekeeping_unit, ca 2 mốc/4 mốc
SCR-02Working Time SheetWorkingTimeSheet.tsxThêm filter timekeeping_unit, multi-record day
SCR-03Working Time Sheet Day DetailPopup hiện cóChi tiết 2 mốc/4 mốc và request overlay
SCR-04Employee Timekeeping HistoryEmployeeProfileTimekeepingHistory.tsxHiển thị summary theo ngày đúng với timekeeping runtime mới
SCR-05Staff Mobile Attendanceattendance_screen.dartPN 2 mốc + 4 mốc, Daisy 2 mốc + 4 mốc theo từng ca
SCR-06Timekeeping ExportExport modal hiện cóChỉ export vận hành chấm công, không có payroll bridge
SCR-07Request ListRequestApproval / danh sách đơn hiện cóScope lại request theo timekeeping_unit, giữ shell hiện tại
SCR-08Employee Profile BadgeEmployeeProfile hiện cóHiện badge readonly Đơn vị chấm công trên hồ sơ NV

UI Principles

  • Không tạo module quản trị org mới ngoài scope chấm công.
  • Không sửa UI dashboard/report legacy.
  • Reuse shell hiện có của settings, timekeeping, user, mobile attendance.
  • Mọi filter mới chỉ là timekeeping_unit, không thay company scope toàn hệ thống.
  • Day-1 chỉ mở on-site attendance; feature này không tạo CTA/request entry remote mới cho rollout unit. Flow remote legacy vẫn tồn tại ngoài phạm vi UI change nếu tenant đang dùng.
  • Mọi bảng list/table trong admin mặc định có cột STT ở vị trí đầu tiên.
  • Mọi field contract có field code tiếng Anh phải đi kèm Tên hiển thị (VI) đúng với nhãn/cột dùng trên UI.

B1.1) Hành trình vận hành thật (End-to-End User Journey)

Journey A — System Admin chuẩn bị rollout

BướcMàn hìnhNgười thao tácViệc làm chínhOutput cho màn sau
1SCR-00 > Đơn vịSystem AdminTạo timekeeping_unit, đặt tên, phase, cờ admin/mobileCó unit để cấu hình
2SCR-00 > Quy địnhSystem AdminKhai báo quota đơn từ, GPS radius, OT threshold, penalty, công chuẩnCó rule nền cho unit
3SCR-00 > Chi nhánh áp dụngSystem AdminMap branch thật vào unitMở khóa scope branch
4SCR-00 > Phòng ban áp dụngSystem AdminMap department theo branch đã chọnMở khóa scope department
5SCR-00 > Nhân viên áp dụngSystem Admin / HRGán user vào unit với primary_branch_id, primary_department_id, hiệu lựcCó headcount thật để xếp lịch
6SCR-00CHR / Admin timekeepingTạo ca, chọn 2 mốc hay 4 mốc, GPS bắt buộc hay không, mode tính côngCó shift template đúng nghiệp vụ
7SCR-00DHR / Admin timekeepingCấu hình approver theo unit/branch/departmentCó luồng duyệt đúng scope
8SCR-01HR / Admin timekeepingImport hoặc tạo lịch làm việc theo tuần/ngàytime_slot_user cho runtime
9SCR-00 > Triển khaiSystem Admin / QA / OpsKiểm readiness, lưu snapshot, bật rolloutAdmin/mobile bắt đầu dùng thật

Journey B — HR vận hành hàng ngày

BướcMàn hìnhViệc làm chínhKết quả mong đợi
1SCR-02Chọn unit, tháng, branch/department để xem bảng côngCó bức tranh vận hành theo unit
2SCR-03Mở chi tiết một ngày khi thấy Thiếu mốc, Đi trễ, Về sớmXem đủ 2 mốc/4 mốc + request overlay
3SCR-07Tra đơn quên chấm, trễ/sớm, OT, đổi caBiết đơn nào đã/đang chờ duyệt
4SCR-00DNếu thấy duyệt sai scope, quay lại chỉnh approverSửa được luồng duyệt gốc
5SCR-06Xuất bảng công tháng / chi tiết ngày / trễ-sớm / OT / quên chấmChốt vận hành, không cần Excel ngoài

Journey C — Nhân viên dùng mobile

BướcMàn hìnhViệc làm chínhHệ thống phản hồi
1SCR-05Mở app chấm công hôm nayCTA quyết định theo ca được xếp trong SCR-01
2SCR-05Bấm Vào ca / Ra nghỉ / Vào lại / Ra vềGhi event theo state machine 2 mốc hoặc 4 mốc
3Request Center legacyTạo đơn quên chấm / trễ-sớm / OT khi có sự cốĐơn đi vào approver cùng unit
4SCR-05 historyXem lịch sử tháng, running total, request overlayTự đối soát tình trạng chấm công

B1.2) Sơ đồ phụ thuộc màn hình (Screen Dependency Map)

Ý nghĩa vận hành:

  • SCR-00 là nơi khóa scope dữ liệu, không phải màn cấu hình đứng một mình.
  • SCR-00C quyết định contract runtime của mobile và bảng công.
  • SCR-01 là cầu nối giữa cấu hình và ngày công thực tế.
  • SCR-02 là cockpit vận hành; SCR-03 chỉ là màn drill-down của SCR-02.
  • SCR-06 không phải nguồn sự thật; nó chỉ tổng hợp từ dữ liệu đã được setup và punch ở các màn trước.

B1.3) Ma trận liên kết màn hình (Screen Linking Matrix)

Màn hìnhVào từ đâuHành động chínhĐi tiếp đâuNếu thiếu điều kiện
SCR-00Menu Internal SettingsTạo unit, map scope, readinessSCR-00C, SCR-00D, SCR-01, tab Triển khaiHiện checklist , deeplink sang tab còn thiếu
SCR-00CDeeplink từ SCR-00 > Quy định, menu shiftTạo shift, chốt 2 mốc/4 mốc, mode tính côngSCR-01, SCR-02, SCR-05Nếu chưa có unit active thì không cho tạo shift
SCR-00DDeeplink từ SCR-00, menu approverChốt approver theo unitSCR-07, SCR-02Nếu chưa map user/department thì không cho save scope
SCR-01Menu timekeeping, deeplink từ readinessXếp lịch/import lịchSCR-05, SCR-02Nếu chưa có shift hoặc user assignment thì empty state hướng dẫn
SCR-05App mobilePunch theo state machine, xem historySCR-07 legacyNếu chưa có lịch hoặc rollout chưa bật thì giữ behavior legacy / báo không có ca
SCR-02Menu timekeepingQuan sát vận hành theo thángSCR-03, SCR-06, SCR-07Nếu thiếu unit/filter scope thì empty state
SCR-03Click từ SCR-02Xem chi tiết ngày, request overlayQuay lại SCR-02, mở SCR-07Không mở độc lập từ menu
SCR-06Nút export từ SCR-02Chốt báo cáo vận hànhFile exportNếu dữ liệu chưa đủ scope thì báo theo filter hiện tại
SCR-07Menu request, link từ popup/detailTheo dõi và xử lý đơnSCR-02, SCR-03Nếu approver ngoài unit thì không thấy đơn

B1.4) Nguyên tắc liên kết dữ liệu (Single-Flow Rules)

Rule IDRule
FLOW-001SCR-00 là nguồn sự thật cho unit scope; các màn sau không tự map thêm branch/department ngoài SCR-00
FLOW-002SCR-00C là nguồn sự thật cho contract 2 mốc/4 mốc, gps_required, workday_calculation_mode; mobile và working sheet chỉ render theo cấu hình đã khóa ở đây
FLOW-003SCR-01 là nguồn sự thật cho "hôm nay user thuộc ca nào"; mobile không tự suy diễn ca từ unit
FLOW-004SCR-05 chỉ ghi nhận event chấm công; không tự chốt payroll hay thay rule công
FLOW-005SCR-02SCR-03 là lớp quan sát/đối soát; không phải nơi sửa cấu hình gốc
FLOW-006SCR-06 luôn xuất theo filter thật mà SCR-02 đang đứng trên đó, để HR không bị nhảy context khi chốt công
FLOW-007Nếu checklist/readiness phát hiện thiếu dữ liệu, hệ thống phải ưu tiên deeplink về đúng màn gốc cần sửa thay vì mở modal hướng dẫn chung chung

B2) SCR-00: Cấu hình đơn vị chấm công

Mục tiêu

Đây là màn mới trong settings module, dùng để quản trị timekeeping_unit cho feature này. Nó không thay vai trò của module quản trị tổ chức toàn hệ thống.

Vai trò trong flow: SCR-00 là "trạm điều phối rollout", nơi gom toàn bộ điều kiện để các màn còn lại dùng được thật. HR/System Admin không nên phải nhớ thứ tự setup bằng đầu; màn này phải dẫn đường bằng checklist, deeplink và trạng thái readiness.

Input / Output của màn

InputOutput
Master data branch/department/user hiện cótimekeeping_unit, mapping scope, assignment user, rollout snapshot
Quyết định nghiệp vụ đã chốt ở PRD/dev-specĐiều kiện để SCR-00C, SCR-00D, SCR-01 và mobile dùng đúng scope

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Tạo hoặc chỉnh 1 timekeeping_unit, biết còn thiếu bước nào, và điều phối rollout từ cùng một setup center
Primary CTABật áp dụng ở tab Triển khai; chỉ enable khi checklist không còn
Secondary CTAThêm đơn vị, Mở bước còn thiếu, Lưu nháp, Xem ảnh hưởng, Lưu triển khai
Khối thông tin phải nổi bậtReadiness Checklist, Setup Navigator, tên unit hiện tại và phạm vi ảnh hưởng của draft
Điều không được gây hiểu nhầmBadge checklist không phải text tĩnh; mọi badge phải click được hoặc nói rõ vì sao chưa click được

As-Is Audit

  • Hiện tại admin đã có các màn settings liên quan nhưng đang tách rời:
    • ShiftSetting.tsx quản lý Work Shift / Shift Group
    • ApproverPage.tsx quản lý approver theo từng request type
    • AnnualLeave.tsx quản lý app_setting.request_leave ở cấp global
  • Chưa có màn riêng để CRUD timekeeping_unit, map branch/department/user, hoặc cấu hình timekeeping per unit trên cùng một screen.
  • Vì vậy SCR-00 là new page, nhưng vẫn phải reuse shell/navigational pattern của settings module để tránh tạo trải nghiệm lạ.
ItemGiá trị đề xuất
Modulesettings
Menu groupInternal Settings
Menu labelĐơn vị chấm công
Route nameROUTE_SETTING_TIMEKEEPING_UNIT
Path/s/internal-settings/timekeeping-unit
ContainerInternalSetting.tsx

Rule: Tab Nhân viên áp dụng để gán user vào timekeeping_unit nằm bên trong SCR-00, không nằm trong màn UserCreate / EmployeeProfileCreate.

Layout

text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ CHẤM CÔNG                                                                                        |
| [Tab Đơn vị] [Tab Quy định] [Tab Chi nhánh áp dụng] [Tab Phòng ban áp dụng] [Tab Nhân viên áp dụng] [Tab Triển khai] |
|------------------------------------------------------------------------------------------------------------------|
| [Tìm theo mã/tên...] [Trạng thái ▼]                                                            [Thêm đơn vị]   |
| STT | Mã | Tên đơn vị | Trạng thái | Số nhân viên | Số chi nhánh áp dụng | Số phòng ban áp dụng | Giai đoạn | Thao tác |
| 1   | PN | Phương Nam | Đang áp dụng | 247        | 4                   | 5                    | R1A      | [Chi tiết] |
| 2   | DS | Daisy      | Đang áp dụng | 150        | 6                   | 6                    | R1B      | [Chi tiết] |
+------------------------------------------------------------------------------------------------------------------+

Readiness Checklist

text
+------------------------------------------------------------------------------------------------------------------+
| ĐIỀU KIỆN BẬT ÁP DỤNG — DAISY                                                                                   |
| [Chi nhánh đã map: 6/6 ✅] [Phòng ban đã map: 6/6 ✅] [Nhân viên đã gán: 148/150 ⚠️] [Luồng duyệt đã đủ: 5/6 ⚠️] |
| [Ca làm việc đã cấu hình: 17/17 ✅] [Quy định nền: Đủ ✅] [Xuất báo cáo thử: ⚠️]            [Mở bước còn thiếu] |
+------------------------------------------------------------------------------------------------------------------+
Trạng thái (Status)Ý nghĩa
Đủ điều kiện để bật áp dụng
⚠️Đã cấu hình một phần, còn việc cần xử lý
Chưa đủ điều kiện hoặc chưa có bằng chứng

Điều hướng nhanh: click từng badge trong checklist để nhảy thẳng sang đúng nơi cần xử lý. Nếu hạng mục nằm ngoài SCR-00 như Ca làm việc hoặc Người duyệt, hệ thống deeplink sang SCR-00C / SCR-00D, đồng thời giữ breadcrumb để quay lại SCR-00 > Triển khai.

Quy tắc đặt tên badge: ưu tiên động từ hoặc trạng thái dễ hiểu cho vận hành như đã map, đã gán, đã cấu hình, đã đủ, đã chạy thử; tránh nhãn quá nén như GPS/Quy định hoặc Kiểm tra xuất báo cáo vì khó hiểu trạng thái thực sự đang được đo là gì.

Quy ước trạng thái vận hành: Chờ hiệu lực chỉ dùng cho entity có effective_from/effective_to thực sự ở Day-1, tức là timekeeping_unit_user trong tab Nhân viên áp dụng. Các màn Đơn vị, Chi nhánh áp dụng, Phòng ban áp dụng chỉ dùng Đang áp dụng / Ngưng áp dụng.

Setup Navigator (wizard-style)

Bước setupBadge readiness tương ứngĐiểm đến khi clickĐiều hệ thống kiểm tra
1. Chọn scope chi nhánhChi nhánh đã mapSCR-00 > Chi nhánh áp dụngCó đủ branch active cần rollout
2. Chọn scope phòng banPhòng ban đã mapSCR-00 > Phòng ban áp dụngDepartment đã map theo branch đúng
3. Gán nhân sựNhân viên đã gánSCR-00 > Nhân viên áp dụngHeadcount active/future đủ theo target
4. Cấu hình rule nềnQuy định nền và các badge ruleSCR-00 > Quy địnhRule cơ bản đã publish cho unit
5. Cấu hình ca làm việcCa làm việc đã cấu hìnhSCR-00CCó đủ shift template/shift group cho scope rollout
6. Cấu hình người duyệtLuồng duyệt đã đủSCR-00DRequest type Day-1 có approver hợp lệ cùng unit
7. Xếp lịch và chạy thửXuất báo cáo thử / Đã chạy thửSCR-01 rồi SCR-02Có lịch, có dữ liệu kiểm tra và export pass
8. Bật rolloutKhông còn badge SCR-00 > Triển khaiCho phép lưu snapshot và bật rollout

Nguyên tắc SaaS: SCR-00 là setup hub, nhưng không ép tất cả cấu hình nằm trong một màn. Checklist được phép điều hướng sang màn khác miễn là luôn có đường quay về SCR-00 > Triển khai để hoàn tất rollout.

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Save mode theo tabĐơn vị, Chi nhánh, Phòng ban, Nhân viên áp dụng, Triển khai dùng save tay theo tab; Quy định dùng flow Lưu nháp -> Xem ảnh hưởng -> Áp dụng từ ngày mai
Dirty stateTab nào có thay đổi chưa lưu phải hiện chấm vàng Có thay đổi chưa lưu ở nhãn tab
Unsaved changesKhi rời tab hoặc deeplink sang màn khác mà còn draft chưa lưu, hiện confirm modal Lưu nháp / Bỏ thay đổi / Ở lại
Search / filter applyÔ tìm kiếm debounce 300ms; dropdown filter apply ngay; đổi Đơn vị chấm công reset toàn bộ filter con đang phụ thuộc
Cascade resetGỡ branch phải chặn silent remove nếu còn department/user/schedule/approver phụ thuộc; bắt buộc mở dialog dependency
Bulk actionGán toàn bộ theo scope đang lọcĐồng bộ theo cơ cấu hiện tại phải có preview delta trước khi lưu
Long-running jobImport mã NV hoặc sync lớn chạy background, hiển thị progress + số bản ghi thành công/thất bại

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Lưu tab thành côngToast xanh + badge readiness refreshGiữ nguyên context unit hiện tại
Validation lỗiInline error tại field + banner tổng hợp ở đầu popup/tabFocus về field lỗi đầu tiên
Mở bước còn thiếuDeeplink + breadcrumb Quay lại Triển khaiUser quay về đúng unit và đúng tab trước đó
Bulk sync / import xongBanner kết quả Tạo mới / Chuyển đơn vị / Thất bại + nút export danh sách affected usersCho phép retry phần lỗi
Bật áp dụngConfirm modal cuối cùng hiển thị checklist snapshotSau khi xác nhận, chuyển unit sang trạng thái Đang áp dụng
Lỗi publish / saveToast đỏ + giữ nguyên draftCó nút Thử lại hoặc Quay về draft

Tab contract

TabChức năngGhi chú
Đơn vịTạo/sửa/ngưng áp dụng đơn vị chấm côngThông tin cơ bản
Quy địnhCấu hình quy định chấm công per đơn vịMới — settings module
Chi nhánh áp dụngThêm chi nhánh hiện có vào timekeeping_unitKhông đổi branch master data
Phòng ban áp dụngThêm phòng ban hiện có vào timekeeping_unitKhông đổi department master data
Nhân viên áp dụngThêm nhân viên vào timekeeping_unitChỉ phục vụ scope chấm công
Triển khaiBật/tắt admin/mobile theo unitPN + Daisy cùng 1B

Linked setup surfaces

Hạng mục rolloutNằm ở đâuVì sao không nhét vào SCR-00
Ca làm việcSCR-00CReuse màn shift hiện có, tránh duplicate form ca
Người duyệtSCR-00DReuse shell approver hiện có, giữ consistency với flow request
Lịch làm việcSCR-01Đây là nghiệp vụ vận hành riêng sau khi scope và shift đã sẵn sàng

SCR-00 phải hiển thị các hạng mục này như các bước thuộc cùng một setup flow, không coi chúng là phần tách rời về mặt trải nghiệm.

Tab "Đơn vị" — Field contract

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Quy tắc (Rule)
codeMã đơn vịQInputYesUnique trong bảng timekeeping_unit
nameTên đơn vịQInputYesTên hiển thị
rollout_phaseGiai đoạn triển khaiQSelectYesR1A / R1B / R1C / OFF
allow_admin_timekeepingBật màn admin chấm côngQToggleYesBật màn admin trong scope này
allow_mobile_self_serviceBật chấm công trên mobileQToggleYesBật mobile attendance trong scope này

Tab "Quy định" — Cấu hình quy định chấm công (Settings Module)

Mỗi đơn vị có bộ quy định riêng. System Admin cấu hình trên UI, backend đọc config từ DB — không hardcode trong code.

Lưu ý về as-is: phần rule phép năm global (min_working_day, reset_date, leave_expiry_date) vẫn tiếp tục nằm ở AnnualLeave.tsx. Tab này chỉ thêm config thuộc scope timekeeping-unit như quota, OT threshold và GPS radius mặc định; không thêm toggle riêng cho báo cáo phép năm.

GPS cũng tách thành 2 lớp:

  • tọa độ chi nhánh tiếp tục lấy từ branch master data hiện có
  • bán kính GPS là rule override theo timekeeping_unit trong tab này

Cách tiếp cận cho SaaS self-serve

Tab Quy định không được ép tenant mới phải hiểu toàn bộ domain ngay ở lần cấu hình đầu tiên. Vì vậy màn này chia thành 2 lớp:

Chế độDùng khi nàoHiện gì
Cơ bảnOnboarding tenant mới, rollout nhanh theo presetQuota đơn từ, OT threshold, GPS radius, preset config, copy config
Nâng caoTenant có ngoại lệ nghiệp vụ hoặc đã qua pilotCông chuẩn theo scope, penalty engine chi tiết, OT rate chi tiết, audit/publish controls

Nguyên tắc: tenant mới bắt đầu từ Preset hoặc Sao chép cấu hình, chỉ vào Nâng cao khi thật sự cần lệch so với mẫu chuẩn.

Layout

text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ > Phương Nam > QUY ĐỊNH CHẤM CÔNG                                                                |
|------------------------------------------------------------------------------------------------------------------|
| [Mức cấu hình: Cơ bản ▼] [Preset cấu hình ▼] [Sao chép từ đơn vị khác] [Xem lịch sử publish]                   |
|------------------------------------------------------------------------------------------------------------------|
|                                                                                                                    |
| ┌─ THIẾT LẬP CƠ BẢN ─────────────────────────────────────────────────────────────────────────┐                   |
| │                                                                                               │                   |
| │ Max đơn đi trễ/về sớm     [3  ] lần/tháng           ⓘ Vượt vẫn cho tạo, HR quyết approve   │                   |
| │ Max phút per đơn trễ/sớm  [Không giới hạn ▼]        ⓘ Daisy = 60 phút. Null = không giới hạn│                   |
| │ Max đơn quên chấm          [3  ] lần/tháng                                                   │                   |
| │ Tự động tạo lịch tuần      [Bật ▼]                 ⓘ Tắt nếu đơn vị có ca xoay              │                   |
| │ OT tối thiểu               [30 ] phút               ⓘ Request OT < threshold sẽ bị bỏ qua   │                   |
| │ Bán kính GPS mặc định      [200] mét               ⓘ Trống = dùng global                    │                   |
| │                                                                                               │                   |
| └───────────────────────────────────────────────────────────────────────────────────────────────┘                   |
|                                                                                                                    |
| ┌─ THIẾT LẬP NÂNG CAO (collapse mặc định ở mode Cơ bản) ────────────────────────────────────┐                   |
| │                                                                                               │                   |
| │ Công chuẩn theo scope     [2 nhóm áp dụng]          [Mở chi tiết]                           │                   |
| │ Penalty engine            [4 rules đang active]     [Mở chi tiết]                           │                   |
| │ Đơn giá OT                [2 role rates]            [Mở chi tiết]                           │                   |
| │                                                                                               │                   |
| └───────────────────────────────────────────────────────────────────────────────────────────────┘                   |
|                                                                                                                    |
| ┌─ TỌA ĐỘ CHI NHÁNH GỐC ─────────────────────────────────────────────────────────────────────┐                   |
| │                                                                                               │                   |
| │ Tọa độ chi nhánh         [Xem/Sửa tọa độ chi nhánh gốc]                                      │                   |
| │                                                                                               │                   |
| └───────────────────────────────────────────────────────────────────────────────────────────────┘                   |
|                                                                                                                    |
| Draft hiện tại: chưa publish | Ảnh hưởng dự kiến: 4 CN, 6 PB, 148 NV, 17 ca                                      |
|                                                                  [Lưu nháp] [Xem ảnh hưởng] [Áp dụng từ ngày mai] |
+------------------------------------------------------------------------------------------------------------------+

Chốt hiển thị: trong wireframe của Tab Quy định, block Tọa độ chi nhánh gốc phải luôn nằm cùng cấp với phần rule cấu hình; đồng thời Công chuẩnPenalty engine không nên ném hết field ra mặt đầu tiên khi tenant đang ở mode Cơ bản.

Field contract — Tab Quy định

Readonly summary — Cách tính công ở cấp ca làm việc:

text
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ CÁCH TÍNH CÔNG ĐƯỢC CẤU HÌNH Ở CA LÀM VIỆC                                                  │
│ Day-1 không chọn "Theo ngày công / Theo giờ" ở cấp Đơn vị chấm công.                       │
│ Mỗi ca làm việc tự quyết định mode tính công để hỗ trợ mixed-mode trong cùng một đơn vị.   │
│                                                       [Mở Ca làm việc]                     │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Control bar — SaaS setup helpers:

ControlMục đíchHành vi
config_modeChuyển Cơ bản / Nâng caoCơ bản là mặc định cho tenant mới
preset_keyChọn bộ cấu hình mẫuDiva mặc định / Phương Nam chuẩn / Daisy chuẩn / Tự cấu hình
copy_from_unit_idSao chép cấu hình từ đơn vị khácChỉ copy vào draft, chưa publish ngay
draft_effective_dateNgày bắt đầu áp dụngMặc định = ngày mai
review_impactXem phạm vi bị ảnh hưởngHiện CN/PB/NV/ca bị tác động trước khi publish

Nhóm Đơn từ:

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Mặc định (Default)Quy tắc (Rule)Giải thích (Tooltip)
max_late_early_requests_per_monthSố đơn đi trễ/về sớm tối đa trong thángQInput numberYes3Min 0, max 31Số đơn trễ/sớm tối đa mỗi tháng. Vượt vẫn cho tạo đơn, hiện cảnh báo
late_early_max_duration_minutesSố phút tối đa mỗi đơn đi trễ/về sớmQInput numberNonullNull = không giới hạn. Min 1, max 480Max phút per lần xin trễ/sớm. Daisy = 60. PN = null
max_forget_clock_requests_per_monthSố đơn quên chấm tối đa trong thángQInput numberYes3Min 0, max 31Số đơn quên chấm tối đa mỗi tháng

Nhóm Ca làm việc:

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Mặc định (Default)Quy tắc (Rule)Giải thích (Tooltip)
auto_schedule_disabledTắt tự động tạo lịch tuầnQToggleYesfalseTắt = cron KHÔNG tạo lịch tuần tự động cho đơn vị này
ot_min_threshold_minutesOT tối thiểu để tínhQInput numberYes0Min 0, max 480Request OT có duration < threshold sẽ bị bỏ qua khi export

Nhóm Công chuẩn (v4 — theo nhóm áp dụng công chuẩn):

Công chuẩn được cấu hình theo nhóm áp dụng công chuẩn, không theo branch. Mỗi phòng ban trong unit sẽ được map vào đúng 1 nhóm áp dụng để resolve công chuẩn tháng.

Hiển thị dạng bảng — mỗi row = 1 nhóm áp dụng công chuẩn trong unit:

text
┌──────────────────────────────────────────────────────────────────┐
│ CÔNG CHUẨN                                                        │
│                                                                    │
│ Nhóm áp dụng         │ Công thức                     │ Phòng ban áp dụng         │
│ ─────────────────────┼───────────────────────────────┼───────────────────────────│
│ PN - Khối Dịch vụ    │ [Ngày − CN ▼]                 │ Bác sĩ, Điều dưỡng...     │
│ PN - Khối Văn phòng  │ [Ngày − CN − 0.5×T7 ▼]       │ TC-HC, Kế toán, MKT...    │
│                                                                    │
│ ⓘ Mỗi phòng ban chỉ thuộc 1 nhóm áp dụng công chuẩn trong cùng   │
│   đơn vị. HR có thể thêm nhóm mới khi có ngoại lệ nghiệp vụ.     │
└──────────────────────────────────────────────────────────────────┘
Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Ý nghĩa
standard_workday_scope_keyNhóm áp dụngQInput / readonly codeKey nghiệp vụ của nhóm áp dụng công chuẩn. VD: PN_OFFICE, DAISY_SERVICE
scope_nameTên nhóm áp dụngQInputTên hiển thị cho HR. VD: PN - Khối Văn phòng, Daisy - VP Kế toán
formulaCông thức công chuẩnQSelectNgày − CN / Ngày − CN − 0.5×T7 / 26 ngày cố định / Tuỳ chỉnh
fixed_valueGiá trị cố địnhQInput numberChỉ hiện khi formula = "Tuỳ chỉnh"
department_idsPhòng ban áp dụngQSelect multipleChọn các phòng ban dùng chung công thức công chuẩn này

Action: Day-1 cho phép:

  • Thêm nhóm áp dụng
  • Gán phòng ban
  • Sửa công thức

Rule: Mỗi department_id chỉ được thuộc 1 standard_workday_scope_key active trong cùng unit.

Nhóm Vi phạm & Phạt (v3 — NEW, bảng con timekeeping_penalty_rule):

Mỗi đơn vị có bảng rule riêng. Admin CRUD rows trong bảng này.

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Ý nghĩa
violation_typeLoại vi phạmQSelectlate_early / forget_start / forget_end / forget_break
penalty_modeCách tính phạtQSelectper_minute (đ/phút) / fixed_amount (đ/lần) / deduct_workday (trừ công)
penalty_amountSố tiền phạtQInput numberSố tiền (VD: 10000, 30000, 50000)
penalty_workdaySố công bị trừQInput numberSố công trừ (VD: 0.5) — chỉ hiện khi mode = deduct_workday
exempt_countSố lần miễnQInput numberSố lần miễn/tháng (0 = không miễn)
exempt_poolNhóm dùng chung số lần miễnQSelectindividual (đếm riêng — PN) / shared (gộp chung — Daisy)

Nhóm Tăng ca (v3 — thêm rate):

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Mặc định (Default)Quy tắc (Rule)Giải thích (Tooltip)
ot_rate_defaultĐơn giá OT mặc địnhQInput numberYes0Min 0Đơn giá OT mặc định (đ/giờ). PN = 50.000, Daisy = 35.000
ot_rate_doctorĐơn giá OT bác sĩQInput numberYes0Min 0Đơn giá OT Bác sĩ (đ/giờ). Cả PN + Daisy = 150.000

Nhóm GPS (v3 — NEW):

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Mặc định (Default)Quy tắc (Rule)Giải thích (Tooltip)
gps_radius_metersBán kính GPS mặc địnhQInput numberNoTrống = dùng globalMin 50, max 5000 khi nhậpBán kính GPS mặc định của đơn vị (mét). Dùng làm radius mặc định cho các ca thuộc unit

Readonly helper:

ItemNội dungAction
Tọa độ chi nhánhTọa độ GPS của từng chi nhánh không nhập ở đây. Hệ thống dùng latitude / longitude trong branch master data để check khoảng cáchXem/Sửa tọa độ chi nhánh gốc

Day-1 chưa mở config cộng phép/reset/hết hạn theo từng đơn vị. Các rule này vẫn đọc từ app_setting.request_leave global và chỉ verify không ảnh hưởng PN/Daisy. Báo cáo phép năm PN được mở trực tiếp ở màn report hiện có, không cấu hình qua toggle trong tab này.

Seed data mặc định

Đơn vịMax trễ/sớmMax phútMax quênAuto scheduleOT minPhép năm
Diva3null3true0true
Phương Nam3null3true30true
Daisy3603false0false

Rules

Rule IDRule
CFG-001Backend đọc config từ timekeeping_unit row, KHÔNG hardcode trong Go/TS code
CFG-002Cronjob log_time_keeping_flag đọc config snapshot đầu ngày (cache at batch start), dùng cho cả batch
CFG-003logTimeKeeping action đọc config snapshot đầu ngày (cache per day, invalidate at midnight). Đảm bảo NV punch sáng và chiều dùng cùng config
CFG-004Đổi config chỉ ảnh hưởng record từ ngày hôm sau (vì cả cronjob + action dùng snapshot đầu ngày)
CFG-005Chỉ System Admin (ITLeader, ITStaff) được sửa tab Quy định
CFG-006Mỗi lần lưu config, ghi audit log (ai đổi, đổi gì, lúc nào)
CFG-007Tab Quy định không còn field Cách tính công; nếu admin cần đổi mode thì phải vào Ca làm việc để chỉnh trên từng shift
CFG-008Preset chỉ là điểm khởi tạo draft; sau khi publish, unit được coi là Tự cấu hình nếu có chỉnh tay
CFG-009Sao chép từ đơn vị khác phải hiện diff trước khi lưu nháp, tránh ghi đè mù
CFG-010Mode Cơ bản mặc định ẩn các block Công chuẩn, Penalty engine, OT rate; chỉ hiện summary + CTA Mở chi tiết
CFG-011Nút chính không phải Lưu ngay mà là Lưu nháp -> Xem ảnh hưởng -> Áp dụng từ ngày mai
CFG-012Trước khi publish, UI phải hiển thị ít nhất số chi nhánh, phòng ban, nhân viên, ca làm việc sẽ bị ảnh hưởng

Dialog — Review impact trước khi publish

text
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ XEM ẢNH HƯỞNG CẤU HÌNH                                                                     │
│----------------------------------------------------------------------------------------------│
│ Đơn vị: Daisy                                                                               │
│ Hiệu lực từ: 22/04/2026                                                                     │
│                                                                                              │
│ Phạm vi bị ảnh hưởng:                                                                        │
│ - 6 chi nhánh                                                                               │
│ - 9 phòng ban                                                                               │
│ - 148 nhân viên                                                                             │
│ - 17 ca làm việc                                                                            │
│                                                                                              │
│ Thay đổi chính:                                                                             │
│ - Max đơn trễ/sớm: 3 -> 4                                                                    │
│ - GPS radius: 200m -> 250m                                                                   │
│ - Preset: Daisy chuẩn -> Tự cấu hình                                                        │
│                                                                                              │
│ Vì hệ thống snapshot đầu ngày, cấu hình mới chỉ áp dụng từ 00:00 ngày hiệu lực.            │
│                                                                                              │
│                                                   [Quay lại draft] [Xác nhận áp dụng]       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Tab Chi nhánh áp dụng

As-Is relation với branch master data:

  • Reuse data source / filter pattern / table pattern từ màn branch hiện có /s/internal-settings/branch?tab=branch.
  • Tab Chi nhánh áp dụng trong SCR-00 không thay thế màn branch master data và không dùng thẳng route đó làm UI mapping.
  • Mục tiêu của tab này chỉ là map branch global vào timekeeping_unit.
  • Nếu branch thiếu metadata gốc (địa chỉ, branch_label_id, trạng thái...), user dùng action deeplink Xem chi nhánh gốc để sang màn branch master data và quay lại.
text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ CHẤM CÔNG > DAISY > CHI NHÁNH ÁP DỤNG                                                           |
| [Tab Đơn vị] [Tab Quy định] [Tab Chi nhánh áp dụng] [Tab Phòng ban áp dụng] [Tab Nhân viên áp dụng] [Tab Triển khai] |
|------------------------------------------------------------------------------------------------------------------|
| [Đơn vị chấm công: Daisy ▼] [Trạng thái ▼] [Tìm chi nhánh...]                    [Thêm chi nhánh vào đơn vị] |
|------------------------------------------------------------------------------------------------------------------|
| STT | Mã CN | Tên chi nhánh      | Loại chi nhánh | Địa chỉ ngắn    | Đã thêm vào đơn vị | Loại áp dụng | Thao tác      |
| 1   | DA01  | Daisy Q10          | office         | 285 CMT8, Q10   | Đang áp dụng       | office       | [Xem chi nhánh gốc] [Xóa khỏi đơn vị] |
| 2   | DA02  | Daisy Nha Trang    | general        | 12 LTK, Nha Trang | Đang áp dụng     | general      | [Xem chi nhánh gốc] [Xóa khỏi đơn vị] |
|------------------------------------------------------------------------------------------------------------------|
| [<] 1 2 [>]                                                                                                     |
+------------------------------------------------------------------------------------------------------------------+
text
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ THÊM CHI NHÁNH VÀO ĐƠN VỊ CHẤM CÔNG                                                                             │
│ Đơn vị chấm công: [Daisy ▼]                                                                                     │
│------------------------------------------------------------------------------------------------------------------│
│ [Tìm theo mã/tên chi nhánh...] [Loại CN ▼]                                                                      │
│------------------------------------------------------------------------------------------------------------------│
│ [ ] Daisy Q10              office       285 CMT8, Q10                                                           │
│ [ ] Daisy Nha Trang        general      12 LTK, Nha Trang                                                       │
│ [ ] Daisy Q3               branch       18A 3/2, Q3                                                             │
│------------------------------------------------------------------------------------------------------------------│
│ Rule hiển thị: chỉ hiện branch active, chưa bị disable ở master data                                            │
│                                                                                                                  │
│                                                                       [Hủy] [Lưu chi nhánh áp dụng]            │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Rules — Tab Chi nhánh áp dụng

Rule IDRule
BR-001timekeeping_unit_branch chỉ là bảng map cho scope chấm công; không đổi ownership của branch trong org global
BR-002Một branch có thể được map vào nhiều timekeeping_unit nếu nghiệp vụ cần, nhưng Day-1 UI chỉ cho map khi đã được PO/TL chốt
BR-003Mapping branch không còn là input trực tiếp của công chuẩn; công chuẩn resolve theo department -> standard_workday_scope_key
BR-004Khi gỡ map branch, hệ thống cảnh báo nếu branch đó đang được dùng bởi department map, user assignment, working schedule hoặc GPS scope
BR-005Nút Xem chi nhánh gốc deeplink sang /s/internal-settings/branch?tab=branch hoặc branch detail để sửa master data; quay lại SCR-00 không mất context đơn vị hiện tại
BR-006Action Xóa khỏi đơn vị ở Day-1 thực hiện soft-disable mapping (disabled = true), không hard delete row
BR-007List mặc định chỉ hiện branch Đang áp dụng; dùng filter Trạng thái để xem Ngưng áp dụng

Dialog — Xóa chi nhánh khỏi đơn vị

text
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ XÁC NHẬN XÓA CHI NHÁNH KHỎI ĐƠN VỊ                                                          │
│----------------------------------------------------------------------------------------------│
│ Chi nhánh: Daisy Q10                                                                        │
│                                                                                              │
│ Ảnh hưởng hiện tại:                                                                          │
│ - 3 phòng ban đang map                                                                       │
│ - 24 nhân viên đang active                                                                   │
│ - 12 lịch làm việc trong tương lai                                                           │
│ - 2 approver scope theo chi nhánh                                                            │
│                                                                                              │
│ Hành động đề xuất: xử lý hết dependency trước khi xóa khỏi đơn vị.                           │
│                                                                                              │
│                                                           [Quay lại] [Xem dependency]       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Tab Phòng ban áp dụng

text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ CHẤM CÔNG > DAISY > PHÒNG BAN ÁP DỤNG                                                            |
| [Tab Đơn vị] [Tab Quy định] [Tab Chi nhánh áp dụng] [Tab Phòng ban áp dụng] [Tab Nhân viên áp dụng] [Tab Triển khai] |
|------------------------------------------------------------------------------------------------------------------|
| [Đơn vị chấm công: Daisy ▼] [Chi nhánh ▼] [Trạng thái ▼] [Tìm phòng ban...]               [Thêm phòng ban vào đơn vị] |
|------------------------------------------------------------------------------------------------------------------|
| STT | Mã PB | Tên phòng ban  | Chi nhánh     | Đã thêm vào đơn vị | Nguồn gợi ý | Số NV đang áp dụng | Thao tác |
| 1   | MKT01 | Marketing      | Daisy Q10     | Đang áp dụng       | Tự động      | 12               | [Xóa khỏi đơn vị] |
| 2   | TV01  | Tư vấn         | Daisy Nha Trang | Đang áp dụng     | Thủ công     | 18               | [Xóa khỏi đơn vị] |
|------------------------------------------------------------------------------------------------------------------|
| [<] 1 2 [>]                                                                                                     |
+------------------------------------------------------------------------------------------------------------------+
text
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ THÊM PHÒNG BAN VÀO ĐƠN VỊ CHẤM CÔNG                                                                             │
│ Đơn vị chấm công: [Daisy ▼]    Chi nhánh: [Daisy Q10 ▼]                                                         │
│------------------------------------------------------------------------------------------------------------------│
│ [Tìm theo mã/tên phòng ban...]                                                                                  │
│------------------------------------------------------------------------------------------------------------------│
│ [ ] Marketing         Daisy Q10        12 NV                                                                    │
│ [ ] Kế toán           Daisy Q10         4 NV                                                                    │
│ [ ] Điều trị          Daisy Q10        20 NV                                                                    │
│------------------------------------------------------------------------------------------------------------------│
│ Rule hiển thị: chỉ hiện department active có `branch_id` thuộc branch đã map vào unit                          │
│                                                                                                                  │
│                                                                  [Hủy] [Lưu phòng ban áp dụng]                 │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Rules — Tab Phòng ban áp dụng

Rule IDRule
DEPT-001Dropdown Chi nhánh chỉ hiện branch đã được map ở tab Chi nhánh áp dụng của đúng unit
DEPT-002Danh sách department được gợi ý từ department.branch_id thuộc tập branch đã map; align DEC-028
DEPT-003Tên department có thể trùng giữa nhiều unit; phân biệt bằng branch_id và assignment active, không đổi tên master data
DEPT-004Khi gỡ map department, hệ thống cảnh báo nếu department đó đang được dùng trong approver scope, working schedule hoặc user assignment
DEPT-005Tab Phòng ban áp dụng không dùng trạng thái Chờ hiệu lực; Day-1 chỉ có Đang áp dụng / Ngưng áp dụng
DEPT-006Action Xóa khỏi đơn vị ở Day-1 thực hiện soft-disable mapping (disabled = true), không hard delete row
DEPT-007List mặc định chỉ hiện department Đang áp dụng; dùng filter Trạng thái để xem Ngưng áp dụng

Dialog — Xóa phòng ban khỏi đơn vị

text
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ XÁC NHẬN XÓA PHÒNG BAN KHỎI ĐƠN VỊ                                                          │
│----------------------------------------------------------------------------------------------│
│ Phòng ban: Marketing | Chi nhánh: Daisy Q10                                                 │
│                                                                                              │
│ Ảnh hưởng hiện tại:                                                                          │
│ - 12 nhân viên đang active                                                                   │
│ - 1 approver scope theo phòng ban                                                            │
│ - 4 lịch làm việc trong tương lai                                                            │
│                                                                                              │
│ Hành động đề xuất: chuyển user sang phòng ban khác hoặc gỡ assignment trước khi unmap.      │
│                                                                                              │
│                                                           [Quay lại] [Xem dependency]       │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Tab Nhân viên áp dụng

text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ CHẤM CÔNG > DAISY > NHÂN VIÊN ÁP DỤNG                                                           |
| [Tab Đơn vị] [Tab Quy định] [Tab Chi nhánh áp dụng] [Tab Phòng ban áp dụng] [Tab Nhân viên áp dụng] [Tab Triển khai] |
|------------------------------------------------------------------------------------------------------------------|
| [Đơn vị chấm công: Daisy ▼] [Chi nhánh ▼] [Phòng ban ▼] [Tìm nhân viên...]             [Thêm nhân viên vào đơn vị] |
|------------------------------------------------------------------------------------------------------------------|
| STT | Mã NV   | Họ tên         | Đơn vị chấm công | Chi nhánh | Phòng ban | Từ ngày | Trạng thái | Thao tác |
| 1   | DS001   | Nguyễn A       | Daisy            | Q10       | Marketing | 01/05/2026 | Đang áp dụng | [Sửa] |
| 2   | DS002   | Trần B         | Daisy            | Q10       | Marketing | 01/05/2026 | Đang áp dụng | [Sửa] |
| 3   | DS003   | Lê C           | Daisy            | Nha Trang | Tư vấn    | 15/05/2026 | Chờ hiệu lực | [Sửa] |
+------------------------------------------------------------------------------------------------------------------+
| [<] 1 2 3 [>]                                                                                                   |
+------------------------------------------------------------------------------------------------------------------+

Entry point: SettingsInternal SettingsĐơn vị chấm công → tab Nhân viên áp dụng. Không thêm field timekeeping_unit vào form tạo/sửa nhân viên hiện tại.

text
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ THÊM NHÂN VIÊN VÀO ĐƠN VỊ CHẤM CÔNG                                                                             │
│ Đơn vị chấm công: [Daisy ▼]                                                                                     │
│------------------------------------------------------------------------------------------------------------------│
│ [Chi nhánh ▼] [Phòng ban ▼] [Trạng thái áp dụng ▼] [Tìm theo mã/tên...]                                         │
│ [ ] Chỉ hiện nhân viên chưa gán   [ ] Chỉ hiện nhân viên đang thuộc unit khác                                   │
│ [Gán toàn bộ theo scope đang lọc] [Đồng bộ theo cơ cấu hiện tại] [Nhập file mã NV] [Dán danh sách mã NV]       │
│ [Xóa nhân viên khỏi đơn vị] [Xem nhân sự mới chưa được gán]                                                      │
│------------------------------------------------------------------------------------------------------------------│
│ [✓] DS001  Nguyễn A        Q10         Marketing     Chưa gán         —                                          │
│ [✓] DS002  Trần B          Q10         Marketing     Phương Nam       01/03/2026 → nay                          │
│ [ ] DS010  Phạm D          Q3          Điều trị      Daisy            01/05/2026 → nay                          │
│------------------------------------------------------------------------------------------------------------------│
│ Đã chọn: 2 nhân viên                                                                                             │
│ [Chọn tất cả theo bộ lọc] [Bỏ chọn tất cả]                                                                       │
│------------------------------------------------------------------------------------------------------------------│
│ Hiệu lực từ [01/05/2026 📅]   Hiệu lực đến [          ]   Lý do thay đổi [............................]         │
│                                                                                                                  │
│ Rule hiển thị: chỉ hiện user thuộc branch/department đã map vào unit đang chọn                                  │
│ Nếu NV đang active ở unit khác, hệ thống sẽ đóng assignment cũ và tạo assignment mới theo ngày hiệu lực         │
│                                                                                                                  │
│                                                                  [Hủy] [Lưu nhân viên áp dụng]                 │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Hành vi lọc

  • Dropdown Phòng ban chỉ hiện department đã được map trong tab Phòng ban áp dụng của đúng unit đang mở.
  • Danh sách nhân viên được resolve theo giao giữa:
    • timekeeping_unit đang chọn
    • department_user
    • branch_user
  • Cùng tên phòng ban như Marketing có thể xuất hiện ở nhiều unit; danh sách nhân viên được phân biệt bằng assignment active của timekeeping_unit_user, không phân biệt bằng tên phòng ban.
  • Filter Trạng thái áp dụng có tối thiểu các giá trị:
    • Tất cả
    • Chưa gán
    • Đang thuộc đơn vị này
    • Đang thuộc đơn vị khác
  • Bảng luôn hiển thị thêm 2 cột thông tin vận hành:
    • Đơn vị chấm công hiện tại
    • Thời gian áp dụng hiện tại
  • Nếu user đã thuộc unit khác, row vẫn được chọn để chuyển unit; UI phải hiển thị rõ đây là hành động re-assignment, không phải create mới thuần túy.
  • Gán toàn bộ theo scope đang lọc áp dụng cho toàn bộ user đang thuộc giao của Chi nhánh + Phòng ban + trạng thái hiện tại, không cần tick từng dòng.
  • Đồng bộ theo cơ cấu hiện tại tạo preview delta gồm:
    • Nhân sự mới trong scope nhưng chưa gán
    • Nhân sự đã rời scope nhưng còn assignment active
    • Nhân sự đổi branch/department chính
  • Xem nhân sự mới chưa được gán là shortcut cho tenant lớn để xử lý biến động hàng ngày mà không phải lọc lại từ đầu.
  • Nhập file mã NV cho phép upload file Excel/CSV chỉ gồm staff_code.
  • Dán danh sách mã NV cho phép dán nhiều mã cách nhau bởi dấu phẩy/xuống dòng để lọc nhanh batch lớn.
  • Xóa nhân viên khỏi đơn vị chỉ khả dụng khi đang filter Đang thuộc đơn vị này.

Bulk actions cho tenant lớn

ActionDùng khi nàoKết quả
Gán toàn bộ theo scope đang lọcSetup mới cho 1 branch/department lớnTạo bulk assignment theo filter hiện tại
Đồng bộ theo cơ cấu hiện tạiTenant có biến động nhân sự thường xuyênHiện delta add/remove/change trước khi apply
Xem nhân sự mới chưa được gánVận hành hàng ngày sau onboardingChỉ hiện danh sách cần xử lý mới
Dán danh sách mã NVCó danh sách do HR cung cấp ngoài hệ thốngChọn nhanh theo batch

Trạng thái assignment

Trạng tháiĐiều kiệnHiển thị ở đâu
Chưa gánUser chưa có row timekeeping_unit_user nào active hoặc futurePopup Thêm nhân viên vào đơn vị
Chờ hiệu lựceffective_from > todayTab Nhân viên áp dụng, popup confirm/review
Đang áp dụngeffective_from <= todayeffective_to rỗng hoặc >= todayTab Nhân viên áp dụng, các màn admin/mobile runtime
Đã hết hiệu lựceffective_to < todayHistory/audit hoặc popup tra cứu, không cần badge nổi bật ở list Day-1
text
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ XÁC NHẬN CHUYỂN ĐƠN VỊ CHẤM CÔNG                                                           │
│----------------------------------------------------------------------------------------------│
│ 1 nhân viên đang active ở đơn vị khác:                                                     │
│ - DS002 | Trần B | Phương Nam | 01/03/2026 → nay                                           │
│                                                                                              │
│ Hệ thống sẽ thực hiện:                                                                       │
│ - kết thúc assignment hiện tại vào 30/04/2026                                                │
│ - tạo assignment mới cho Daisy từ 01/05/2026                                                 │
│                                                                                              │
│                                                        [Quay lại] [Xác nhận chuyển]         │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Rules

Rule IDRule
TKU-001Một user chỉ có 1 active timekeeping_unit tại một thời điểm
TKU-002Branch/department được map vào timekeeping_unit chỉ để phục vụ timekeeping
TKU-003Ngưng áp dụng timekeeping_unit chỉ chặn write mới trong timekeeping scope
TKU-004Không sửa dữ liệu branch/department global từ screen này
TKU-005Đổi timekeeping_unit ở filter admin thì reset branch, department, shift group về rỗng trước khi query lại
TKU-006UserCreateEmployeeProfileCreate không có field timekeeping_unit; mọi assignment Day-1 thực hiện tại SCR-00 > Tab Nhân viên áp dụng
TKU-007Save bulk assignment phải hỗ trợ cả create mớire-assignment trong cùng một thao tác
TKU-008re-assignment = đóng row active cũ (effective_to = new_effective_from - 1 day) rồi insert row mới
TKU-009Nếu effective_from chồng lấn với assignment active hiện tại và admin không xác nhận chuyển unit, hệ thống không cho lưu
TKU-010Bảng list phải hiển thị Đơn vị chấm công hiện tại để HR tránh gán nhầm hàng loạt
TKU-011Nhập file mã NVDán danh sách mã NV chỉ là cách chọn nhanh user; vẫn áp dụng đầy đủ validation branch/department/unit
TKU-012Xóa nhân viên khỏi đơn vị bắt buộc có confirm dialog và hiển thị số NV bị ảnh hưởng trước khi lưu
TKU-013Chờ hiệu lực chỉ áp dụng cho timekeeping_unit_user; không reuse trạng thái này cho unit/branch/department mapping Day-1
TKU-014Gán toàn bộ theo scope đang lọc phải hiển thị preview số user tạo mới / re-assignment trước khi lưu
TKU-015Đồng bộ theo cơ cấu hiện tại không auto gỡ assignment ngay; luôn hiện delta và cần confirm
TKU-016Readiness badge Nhân viên đã gán phải đọc được cả khoảng chênh giữa headcount trong scope và headcount đã assign để gợi ý thao tác bulk phù hợp
TKU-017Sau mỗi lần sync bulk, UI phải cho phép export danh sách affected users để HR lưu đối chiếu

Tab Triển khai

text
+------------------------------------------------------------------------------------------------------------------+
| CẤU HÌNH ĐƠN VỊ CHẤM CÔNG > DAISY > TRIỂN KHAI                                                                  |
| [Tab Đơn vị] [Tab Quy định] [Tab Chi nhánh áp dụng] [Tab Phòng ban áp dụng] [Tab Nhân viên áp dụng] [Tab Triển khai] |
|------------------------------------------------------------------------------------------------------------------|
| Giai đoạn triển khai       [R1B ▼]                                                                              |
| Bật admin chấm công        [Bật]                                                                                 |
| Bật chấm công mobile       [Bật]                                                                                 |
|------------------------------------------------------------------------------------------------------------------|
| ĐIỀU KIỆN BẬT ÁP DỤNG                                                                                           |
| [Chi nhánh đã map: 6/6 ✅] [Phòng ban đã map: 6/6 ✅] [Nhân viên đã gán: 148/150 ⚠️] [Luồng duyệt đã đủ: 5/6 ⚠️] |
| [Ca làm việc đã cấu hình: 17/17 ✅] [Quy định nền: Đủ ✅] [Xuất báo cáo thử: ⚠️]                                 |
|------------------------------------------------------------------------------------------------------------------|
| Ghi chú triển khai [.........................................................................................]  |
|                                                                                                                  |
|                                            [Xem checklist chi tiết] [Lưu triển khai] [Bật áp dụng]             |
+------------------------------------------------------------------------------------------------------------------+

Rules — Tab Triển khai

Rule IDRule
ROL-001Bật áp dụng chỉ khả dụng khi checklist không còn badge
ROL-002Nhân viên áp dụng đạt khi số assignment Đang áp dụng + Chờ hiệu lực đủ headcount mục tiêu của unit
ROL-003Người duyệt đạt khi tất cả request type Day-1 có approver hợp lệ trong cùng unit
ROL-004Bật áp dụng không tự seed dữ liệu; chỉ bật cờ cho admin/mobile sau khi dữ liệu đã sẵn sàng
ROL-005Lưu triển khai lưu trạng thái checklist snapshot + ghi chú triển khai để QA/Ops đối chiếu khi go-live
ROL-006Badge Ca làm việc, Người duyệt, Xuất báo cáo thử có thể deeplink sang màn ngoài SCR-00, nhưng khi quay lại phải giữ context unit hiện tại

B2) SCR-00C: Cấu hình ca làm việc / nhóm ca (Work Shift / Shift Group Settings)

Vai trò trong flow

SCR-00C là nơi khóa contract runtime của toàn feature. Đây là màn quyết định mobile sẽ hiện 2 CTA hay 4 CTA, bảng công render trạng thái nào, export có mấy cột mốc, và ca nào bắt buộc GPS.

Input / Output của màn

InputOutput
timekeeping_unit đã được tạo ở SCR-00, shift shell hiện có, rule nghiệp vụ từ PRDshift_template/shift_group chuẩn hóa theo unit, preview runtime rõ ràng cho mobile/admin/export

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Tạo hoặc chỉnh ca sao cho runtime contract đúng nghiệp vụ của từng unit
Primary CTACập nhật / Tạo ca mới trong popup ca
Secondary CTATạo nhóm ca, Xem trước kết quả runtime, filter theo Đơn vị chấm công
Khối thông tin phải nổi bậtCách tính công, Công tối đa của ca, GPS cho ca này, Yêu cầu chấm 4 mốc, panel preview runtime
Điều không được gây hiểu nhầmCông tối đa của ca là nhãn UI của field workday; preview runtime là contract hiển thị, không phải mock trang riêng

As-Is Reuse

  • Reuse ShiftSetting.tsx
  • Reuse tab Work Shift
  • Reuse tab Shift Group

Delta

SurfaceDelta
Work Shift listThêm filter Đơn vị chấm công, cột Đơn vị, cột 4 mốc
Shift formThêm field Đơn vị chấm công, canonical_key, reuse break_time + start_break_time + end_break_time, thêm break_clocking_required, break_mode, break_flex_minutes, workday_calculation_mode, standard_hours, gps_required; đổi label Số ngày công thành Công tối đa của ca
Shift groupChỉ nhận shift thuộc cùng timekeeping_unit

Layout delta

text
+------------------------------------------------------------------------------------------------------------------+
| CÀI ĐẶT CA LÀM VIỆC                                                                                              |
| [Tab Work Shift] [Tab Shift Group]                                                                              |
| [Đơn vị chấm công ▼] [Tìm ca...] [Trạng thái ▼]                                                  [Tạo ca mới]  |
| STT | Mã ca | Tên ca | Đơn vị | Giờ làm việc | Cách tính công | 4 mốc | Nhóm ca | Thao tác                |
+------------------------------------------------------------------------------------------------------------------+
text
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ CHỈNH SỬA CA LÀM VIỆC                                                                                          │
│------------------------------------------------------------------------------------------------------------------│
│ Mã ca làm việc *            [BVPN_01................]    Đơn vị chấm công *   [Phương Nam ▼]                   │
│ Tên ca làm việc *           [Bảo vệ VP Phú Lợi......]    Cách tính công *     [Theo giờ ▼]                     │
│ Thời gian làm việc *        [07:00] [17:00]              Công tối đa của ca * [1.0] công                       │
│                                                       ⓘ Reuse field `workday` hiện có, chỉ đổi label hiển thị   │
│ Giờ chuẩn của ca            [8.0] giờ                  ⓘ Chỉ hiện khi `Cách tính công = Theo giờ`              │
│ GPS cho ca này              [Bắt buộc ▼]               ⓘ Chọn ca này có bắt buộc GPS khi chấm công hay không   │
│ Thời gian giữa ca           [Bật]                       Yêu cầu chấm 4 mốc      [Bật]                           │
│ Khung nghỉ mặc định         [12:00] [13:00]             Cách áp dụng giờ nghỉ   [Linh hoạt ▼]                  │
│ Biên độ linh hoạt           [60] phút                  ⓘ Chỉ hiện khi `Cách áp dụng giờ nghỉ = Linh hoạt`      │
│                                                       ⓘ VD: khung 12:00-13:00, biên độ 60p → ra nghỉ từ 11:00, │
│                                                          vào lại đến 14:00 vẫn hợp lệ                           │
│ Đang hoạt động              [Bật]                                                                                 │
│------------------------------------------------------------------------------------------------------------------│
│ ĐỐI TƯỢNG ÁP DỤNG                                                                                               │
│ STT | Tên phân quyền | Số lượng                                                                                 │
│ ...                                                                                                             │
│------------------------------------------------------------------------------------------------------------------│
│ XEM TRƯỚC KẾT QUẢ RUNTIME                                                                                        │
│ Mobile CTA: [Vào ca] [Ra nghỉ] [Vào lại] [Ra về]                                                                │
│ Popup ngày công: 4 dòng mốc + trạng thái `Thiếu giữa trưa` nếu thiếu segment 2                                 │
│ Export chi tiết ngày: 4 cột mốc                                                                                 │
│                                                                                                   [Cập nhật]    │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

Preview runtime trong form ca

Khi admin đổi các field break_clocking_required, break_mode, workday_calculation_mode, gps_required, form phải cập nhật ngay panel preview để người cấu hình thấy:

Loại previewMục đích
Mobile CTA previewBiết ca sẽ hiện 2 CTA hay 4 CTA trên app
Working Sheet / Day Detail previewBiết popup ngày công sẽ hiển thị 2 mốc hay 4 mốc, status nào có thể xuất hiện
Export previewBiết file chi tiết ngày sẽ có 2 cột hay 4 cột mốc
Policy summaryNhắc lại Theo giờ / Theo ngày công, GPS bắt buộc / không bắt buộc, Fixed / Flex

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Save modeManual save trong popup; không auto-save giữa chừng
Dirty stateBất kỳ đổi field nào trong popup làm hiện trạng thái Có thay đổi chưa lưu
Unsaved changesĐóng popup khi còn thay đổi chưa lưu phải hỏi Bỏ thay đổi / Tiếp tục chỉnh
Conditional displaystandard_hours, break_mode, break_flex_minutes ẩn/hiện tức thời theo rules ở dưới
Preview runtimeĐổi field break_clocking_required, break_mode, workday_calculation_mode, gps_required phải refresh preview ngay, không cần bấm lưu
Async validationcanonical_key validate unique theo timekeeping_unit trước khi submit; submit lock CTA để tránh double-click
Impacted shift đang liveNếu shift đã được dùng trong lịch tương lai, hiển thị impact banner nêu số lịch bị ảnh hưởng trước khi save

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Lưu ca thành côngToast xanh + refresh list + highlight row vừa lưuGiữ filter unit hiện tại
Validation lỗiInline error ngay dưới field + focus field lỗi đầu tiênKhông đóng popup
Shift đang được dùng trong lịch tương laiWarning banner + confirm modal trước khi saveUser xác nhận rồi mới cập nhật
Preview runtime thay đổiPanel preview cập nhật realtimeKhông cần action phụ
Lỗi lưu caToast đỏ + giữ nguyên toàn bộ inputCó nút Thử lại

Rules

Rule IDRule
SHIFT-001canonical_key unique trong cùng timekeeping_unit
SHIFT-002break_clocking_required = true thì Working Schedule/Sheet/Mobile phải render contract 4 mốc
SHIFT-002Abreak_clocking_required = false nhưng break_time = true vẫn là ca 2 mốc; hệ thống trừ break_duration theo schedule, không mở CTA Ra nghỉ / Vào lại
SHIFT-002Bbreak_mode chỉ hiện khi break_clocking_required = true; không dùng 2 checkbox độc lập để tránh chọn đồng thời fixedflex
SHIFT-002Cbreak_mode = flex bắt buộc phải có start_break_time, end_break_time, break_flex_minutes > 0; Day-1 các field này chỉ là metadata/tham chiếu, không dùng để block/phạt mốc Ra nghỉ / Vào lại
SHIFT-003Không update shift theo name; dùng canonical_key
SHIFT-004Cách tính công được quyết định ở cấp shift, không lấy từ timekeeping_unit
SHIFT-005Công tối đa của ca là label hiển thị mới của field workday hiện có; Day-1 không đổi tên cột DB để giảm rủi ro
SHIFT-006Nếu workday_calculation_mode = 'hourly' thì bắt buộc có standard_hours > 0
SHIFT-007gps_required được quyết định ở cấp shift; timekeeping_unit chỉ giữ bán kính GPS mặc định
SHIFT-008gps_required = false nghĩa là ca này không chặn punch bởi GPS; logic remote_onetime / remote_weekly vẫn là flow request riêng, không phải option trong dropdown
SHIFT-009Panel Xem trước kết quả runtime phải refresh tức thời khi admin đổi field quan trọng của ca
SHIFT-010Preview chỉ là contract hiển thị; không thay thế validation backend, nhưng phải phản ánh đúng behavior mà mobile / working sheet / export sẽ dùng

Shift form — Contract chuẩn hóa

Trường (Field code)Tên hiển thị (VI)Kiểu (Type)Bắt buộc (Required)Quy tắc (Rule)
timekeeping_unit_idĐơn vị chấm côngQSelectYesChỉ chọn unit active
canonical_keyMã ca chuẩnQInputYesUnique trong cùng unit
workday_calculation_modeCách tính côngQSelectYesfixed / hourly
workdayCông tối đa của caQInput numberYesReuse field workday hiện có; min 0.25, max 1.0, step 0.25
standard_hoursGiờ chuẩn của caQInput numberConditionalChỉ hiện khi workday_calculation_mode = 'hourly'; min 0.5, max 24
gps_requiredGPS cho ca nàyQSelectYesHiển thị 2 option Bắt buộc / Không bắt buộc; lưu tương ứng true / false
break_timeCó nghỉ giữa caQToggleYesBật nếu ca có nghỉ giữa ca theo schedule
start_break_timeGiờ bắt đầu khung nghỉ mặc địnhQTimeConditionalBắt buộc khi break_time = true
end_break_timeGiờ kết thúc khung nghỉ mặc địnhQTimeConditionalBắt buộc khi break_time = true, phải > start_break_time
break_clocking_requiredYêu cầu chấm 4 mốcQToggleYesBật = contract 4 mốc
break_modeCách áp dụng giờ nghỉQSelectConditionalChỉ hiện khi break_clocking_required = true; giá trị fixed / flex. flex = Day-1 chỉ ghi nhận 4 mốc + tính actual hours, không hard-validate mốc nghỉ
break_flex_minutesBiên độ linh hoạtQInput numberConditionalChỉ hiện khi break_mode = flex; min 1, max 120; Day-1 chỉ lưu metadata tham chiếu

Shift form — Conditional display

Điều kiệnẨn/Hiện
workday_calculation_mode = 'fixed'Ẩn standard_hours
workday_calculation_mode = 'hourly'Hiện standard_hours
break_time = falseẨn start_break_time, end_break_time, break_clocking_required, break_mode, break_flex_minutes
break_time = trueHiện start_break_time, end_break_time, break_clocking_required
break_clocking_required = falseẨn break_mode, break_flex_minutes
break_clocking_required = trueHiện break_mode
break_mode = fixedẨn break_flex_minutes
break_mode = flexHiện break_flex_minutes

B2) SCR-00D: Cấu hình người duyệt chấm công (Timekeeping Approver Settings)

Vai trò trong flow

SCR-00D đảm bảo các request timekeeping sau rollout luôn đi đúng người duyệt trong cùng timekeeping_unit. Đây là mắt xích chặn việc setup xong dữ liệu nhưng luồng vận hành vẫn đổ sai approver.

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Khóa routing approver theo unit/branch/department cho các request Day-1
Primary CTALưu cấu hình người duyệt
Secondary CTAThêm dòng cấu hình, filter Đơn vị chấm công, deeplink quay lại SCR-00
Khối thông tin phải nổi bậtRequest type, loại phạm vi, phạm vi áp dụng, approver đang active
Điều không được gây hiểu nhầmTab remote là legacy, feature này không redesign và không tạo request type mới

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Save modeManual save theo form approver
Dirty stateRời tab khi còn thay đổi chưa lưu phải hỏi confirm
Filter unitNếu vào từ readiness deeplink thì auto-select đúng unit và khóa đến khi quay lại
Cascade resetĐổi Loại phạm vi phải reset field Phạm vi áp dụng để tránh save sai scope
Scope validationKhông cho chọn approver hoặc scope ngoài timekeeping_unit đang mở

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Lưu cấu hình thành côngToast xanh + row status refreshCho phép quay lại SCR-00 > Triển khai
Scope không hợp lệInline error + helper text giải thích lý doFocus về field lỗi
Gỡ rule đang activeWarning confirm modal hiển thị request type/phạm vi bị ảnh hưởngUser xác nhận rồi mới gỡ

As-Is Reuse

  • Reuse ApproverPage.tsx
  • Reuse layout tab hiện có của approver settings, bao gồm cả tab remote đang tồn tại trong hệ thống
  • Chỉ mô tả delta cho các tab timekeeping:
    • clock in/out
    • forget clock
    • late/early
    • change shift
    • overtime
    • leave

Delta

SurfaceDelta
Filter barThêm filter Đơn vị chấm công
TableThêm cột Đơn vị, Loại phạm vi, Phạm vi áp dụng
FormChỉ cho chọn approver/scope nằm trong cùng timekeeping_unit
Tab gatingChỉ mở delta cho request type thuộc Day-1 scope; tab remote là flow legacy, không redesign trong feature này

Rules

Rule IDRule
APR-001Approver config timekeeping không được làm thay đổi flow approval ngoài timekeeping
APR-002Chỉ resolve approver trong cùng timekeeping_unit
APR-003Nếu request type không thuộc scope timekeeping, UI không thay đổi
APR-004Tab remote vẫn tồn tại theo UI hiện tại; feature này chỉ ràng scope approver cho timekeeping-unit, không tạo tab mới cũng không mô tả redesign flow remote

B2) SCR-01: Lịch làm việc (Working Schedule)

Reuse

  • Reuse WorkingSchedule.tsx hiện có (filter bar, bảng tuần, tạo/sửa lịch)
  • Reuse WorkingScheduleImport.tsx + useWorkingScheduleImport.ts — import Excel đã hoàn chỉnh (parse template, upsert time_slot_user). Chỉ cần scope theo unit
  • Reuse WorkingScheduleForm.tsx — form tạo/sửa lịch NV

Vai trò trong flow

SCR-01 là màn "biến cấu hình thành lịch làm thật". Nếu SCR-00SCR-00C chưa xong thì SCR-01 phải fail sớm bằng empty state có hướng dẫn, không cho HR tạo lịch trong trạng thái nửa vời.

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Gán ca đúng người, đúng tuần, đúng unit để mobile và bảng công chạy được
Primary CTAImport Excel cho setup hàng loạt; Tạo lịch cho case lẻ
Secondary CTAXuất Excel, filter theo Đơn vị / Chi nhánh / Phòng ban / Nhóm ca
Khối thông tin phải nổi bậtUnit đang thao tác, tuần hiện tại, nhóm ca đang áp dụng, cảnh báo thiếu prerequisite
Điều không được gây hiểu nhầmImport Excel không bypass validation unit/branch/department/shift; mọi row vẫn phải thuộc scope đã map

Delta

  • Thêm filter Đơn vị chấm công + Chi nhánh (chưa có trên màn hiện tại)
  • Sau khi chọn unit:
    • chỉ hiện branch/department đã map vào unit đó
    • chỉ hiện shift template thuộc unit đó
    • reset các filter con trước khi load data mới
    • nếu unit chưa map branch/department thì hiển thị empty state hướng dẫn về SCR-00

Layout

text
+---------------------------------------------------------------------------------------------------------------+
| LỊCH LÀM VIỆC                                                                                                 |
| [Đơn vị chấm công ▼] [Chi nhánh ▼] [Phòng ban ▼] [Nhóm ca ▼] [Tuần ▼] [Tìm nhân viên...]                     |
| [Import Excel] [Xuất Excel] [Tạo lịch]                                                                        |
+---------------------------------------------------------------------------------------------------------------+

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Save modeManual save cho form tạo/sửa lịch; import là batch job riêng
Unsaved changesĐóng form tạo/sửa lịch khi còn thay đổi phải hỏi confirm
Filter applyĐổi Đơn vị chấm công reset Chi nhánh, Phòng ban, Nhóm ca, Từ khóa rồi mới query
Search timingÔ tìm kiếm debounce 300ms; đổi tuần apply ngay
Import batchImport chạy background, có row result thành công / thất bại / bị bỏ qua
Empty prerequisiteNếu thiếu branch/department/shift thì hiển thị empty state + deeplink về đúng màn gốc

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Tạo / sửa lịch thành côngToast xanh + row/calendar refreshGiữ nguyên filter hiện tại
Import thành công một phầnBanner summary + file/error table tải lại đượcCho phép retry các dòng lỗi
Import lỗi toàn bộToast đỏ + giữ nguyên context importCó nút Tải template / Xem lỗi
Thiếu prerequisiteEmpty state có CTA Mở bước còn thiếuQuay lại đúng SCR-00 hoặc SCR-00C

B2) SCR-02: Bảng công (Working Time Sheet)

Vai trò trong flow

SCR-02 là cockpit vận hành hàng ngày của HR/Manager. Màn này không chỉ hiển thị dữ liệu, mà còn là điểm xuất phát cho 3 hành động tiếp theo:

  • drill-down sang SCR-03 để xem chi tiết ngày
  • sang SCR-07 để đối soát request
  • sang SCR-06 để chốt export theo đúng filter hiện tại

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Phát hiện bất thường theo ngày/tháng và đi tiếp sang màn xử lý đúng
Primary CTAClick ô ngày / row để mở SCR-03
Secondary CTACác nút export, mở request liên quan, đổi filter scope
Khối thông tin phải nổi bậtFilter Đơn vị chấm công, ô ngày có anomaly, tổng hợp trạng thái theo tháng
Điều không được gây hiểu nhầmExport phải lấy đúng filter đang đứng; SCR-02 là cockpit quan sát, không phải nơi sửa cấu hình

Delta

  • Thêm filter Đơn vị chấm công
  • Ngày có thể có:
    • Đủ mốc
    • Thiếu đầu ca
    • Thiếu giữa trưa
    • Thiếu cuối ca
    • Nghỉ
  • Không đổi shell bảng hiện có
  • Khi đổi timekeeping_unit, reset branch, department, nhóm ca, từ khóa tìm nhân viên

Layout

text
+---------------------------------------------------------------------------------------------------------------+
| BẢNG CÔNG                                                                                                     |
| [Đơn vị chấm công ▼] [Chi nhánh ▼] [Phòng ban ▼] [Nhóm ca ▼] [Tháng ▼] [Tìm nhân viên...]                   |
| [Xuất chi tiết ngày] [Xuất tổng hợp tháng] [Xuất báo cáo đi trễ/về sớm] [Xuất báo cáo tăng ca] [Xuất báo cáo quên chấm] |
+---------------------------------------------------------------------------------------------------------------+

Filter rules

Rule IDRule
WS-001Chi nhánh dropdown chỉ hiện branch đã map vào timekeeping_unit đang chọn
WS-002Phòng ban dropdown chỉ hiện department đã map vào timekeeping_unit đang chọn
WS-003Nhóm ca dropdown chỉ hiện shift group thuộc timekeeping_unit đang chọn
WS-004Đổi Đơn vị chấm công → reset tất cả filter con về rỗng
WS-005Đổi Chi nhánh → reset Phòng ban (vì department scoped theo branch)

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Filter applyDropdown apply ngay; ô tìm kiếm debounce 300ms
Cascade resetĐổi Đơn vị chấm công reset toàn bộ filter con; đổi Chi nhánh reset Phòng ban
Drill-downClick ô ngày hoặc trạng thái mở SCR-03; focus quay lại đúng ô khi đóng popup
Export handoffMọi CTA export mở SCR-06 với filter hiện tại ở trạng thái readonly, không cho đổi unit trong modal export
LoadingBảng giữ header/filter sticky, row skeleton trong lúc query lại

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Đổi filter xongRefresh table + giữ vị trí scroll đầu bảngUser tiếp tục drill-down hoặc export
Không có dữ liệu theo scopeEmpty state + helper text nêu thiếu lịch/chưa rollout/chưa có punchCTA sang SCR-01 hoặc SCR-00 nếu phù hợp
Export được khởi tạoToast hoặc banner Đang tạo file exportCó link mở SCR-06 hoặc trạng thái job

B2) SCR-03: Chi tiết ngày công (Working Time Sheet Day Detail)

Contract

  • Reuse popup chi tiết ngày
  • Với ca 2 mốc: hiển thị Vào ca, Ra về
  • Với ca 4 mốc: hiển thị đủ 4 dòng
  • Overlay request timekeeping ngay trong popup
  • Đây là popup drill-down của SCR-02, không phải màn độc lập trong navigation

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Hiểu chính xác ngày đó thiếu mốc ở đâu, có request gì, và có cần đi tiếp sang danh sách đơn không
Primary CTAMở danh sách đơn liên quan khi ngày có request hoặc cần tạo request follow-up
Secondary CTAĐóng popup
Khối thông tin phải nổi bật2 mốc/4 mốc thực tế, trạng thái ngày, ngày công, request overlay
Điều không được gây hiểu nhầmThiếu giữa trưa chỉ là trạng thái quan sát; Day-1 không auto suy ra 0.5 công trong popup

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Open popupFocus vào heading popup, không mất context row/cell phía sau
Close popupEsc hoặc Đóng trả focus về đúng ô vừa click ở SCR-02
Request drillClick request mở SCR-07 ở context cùng NV/ngày/unit
Readonly modeKhông chỉnh dữ liệu trực tiếp trong popup này

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Không có request liên quanInline helper Chưa có đơn liên quanCTA Mở danh sách đơn nếu role được phép
Thiếu mốc / partialWarning banner theo trạng thái ngàyUser drill sang request list nếu cần

Layout — Ca 2 mốc

text
┌─────────────────────────────────────────────────┐
│ Nguyễn Văn A — 06/04/2026 (Thứ Hai)            │
│ Ca: Ca hành chính (08:00 - 17:00)               │
│ Đơn vị: Phương Nam | CN: TT Y khoa Đà Lạt      │
│─────────────────────────────────────────────────│
│ Vào ca:  08:15  ⏰ Trễ 15 phút                  │
│ Ra về:   17:02  ✅                               │
│─────────────────────────────────────────────────│
│ Ngày công: 1.0                                   │
│ Trạng thái: Đủ mốc                              │
│─────────────────────────────────────────────────│
│ Đơn liên quan:                                   │
│ 📋 REQ-2026-0412 Đi trễ — Đã duyệt bước 1      │
└─────────────────────────────────────────────────┘

Layout — Ca 4 mốc

text
┌─────────────────────────────────────────────────┐
│ Trần Thị B — 06/04/2026 (Thứ Hai)              │
│ Ca: Bác sĩ Ca 1 (08:00 - 17:00, 4 mốc)        │
│ Đơn vị: Daisy | CN: 140 Hai Bà Trưng           │
│─────────────────────────────────────────────────│
│ Vào ca:    08:00  ✅                             │
│ Ra nghỉ:   12:10  ✅                             │
│ Vào lại:   —      ❌ Thiếu mốc                  │
│ Ra về:     —      ❌ Thiếu mốc                  │
│─────────────────────────────────────────────────│
│ Ngày công: Chưa đủ dữ liệu để kết luận tự động │
│ Trạng thái: Thiếu giữa trưa                     │
│─────────────────────────────────────────────────│
│ Đơn liên quan: (chưa có)                        │
└─────────────────────────────────────────────────┘

B2) SCR-04: Lịch sử chấm công nhân viên (Employee Timekeeping History)

Delta

  • Reuse màn history hiện có
  • Hiển thị theo day summary, không collapse sai theo raw row
  • Đơn vị chấm công dạng readonly để giải thích scope
  • Nếu user không thuộc rollout unit thì giữ nguyên history hiện tại, không mở thêm nhãn 4 mốc

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Tra cứu lịch sử cá nhân hoặc theo hồ sơ nhân viên để đối soát nhanh
Primary CTAKhông có CTA ghi dữ liệu; đây là màn readonly
Secondary CTAXem ngày chi tiết nếu shell hiện có hỗ trợ
Khối thông tin phải nổi bậtBadge Đơn vị chấm công, summary theo ngày, trạng thái thực tế

B2) SCR-05: Chấm công trên mobile cho nhân viên (Staff Mobile Attendance)

Vai trò trong flow

SCR-05 là mặt tiền của toàn bộ feature đối với nhân viên. Toàn bộ cảm giác "dùng thật" trên mobile phụ thuộc vào việc màn này luôn trả lời được 3 câu hỏi:

  • Hôm nay tôi phải bấm gì?
  • Hệ thống đang hiểu tôi ở trạng thái nào của ca?
  • Nếu bị lỗi/chưa đủ mốc thì tôi phải đi đâu tiếp theo?

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Chấm đúng mốc tiếp theo của ca hôm nay và tự hiểu trạng thái hiện tại
Primary CTACTA động theo state machine: Vào ca / Ra nghỉ / Vào lại / Ra về
Secondary CTALịch sử, Tạo đơn, mở calendar/thống kê cá nhân
Khối thông tin phải nổi bậtCa hôm nay, trạng thái hiện tại, GPS hợp lệ hay không, quota đơn còn lại nếu đang ở flow request
Điều không được gây hiểu nhầmCTA được quyết định bởi shift hôm nay, không phải bởi tên unit; Day-1 không có CTA remote mới

Rollout contract (Updated)

UnitReleaseCTAGhi chú
PN ca thường (Ca HC, Ca 1–8, Ca tối)1BVào ca, Ra về2 mốc
PN ca gãy (6 ca)1BVào ca, Ra nghỉ, Vào lại, Ra về4 mốc — PN xác nhận cần track trễ sáng + chiều (DEC-014)
Daisy khối DV (BS, Phụ tá, Điều trị)1BVào ca, Ra nghỉ, Vào lại, Ra về4 mốc, ca có break_clocking_required = true
Daisy khối VP + ca xuyên trưa1BVào ca, Ra về2 mốc, ca có break_clocking_required = false

Thay đổi lớn vs v2.0: PN không còn đi trước (1A) với 2 mốc. PN ca gãy cần 4 mốc → PN và Daisy cùng rollout 1B.

CTA được quyết định bởi shift_template.break_clocking_required của ca user hôm đó, không phải theo timekeeping_unit.

Reuse

  • Reuse attendance_screen.dart + attendance_bloc.dart — state machine 2 mốc (CheckIn/CheckOut) đã hoàn chỉnh
  • Reuse GPS check trong log_time_keeping.goCalculateDistance() + radius check đã có. Remote bypass (remote_onetime, remote_weekly) đã có
  • Reuse calendar view lịch sử chấm công trên mobile — đã có hiển thị chấm công + đơn theo tháng

As-Is Audit

  • attendance_screen.dart hiện có 2 tab Timekeeping / Statistic, calendar theo tháng, danh sách lịch sử yêu cầu và stats cards cho trễ/sớm, quên chấm, phép năm【không redesign layout tổng thể trong phase này】.
  • App hiện vẫn có flow request remote trong RequestApprovalScreen, và calendar attendance đang xem request remote approved như một điều kiện normalize trạng thái ngày chấm công.
  • Vì vậy proposal của SCR-05 là: reuse shell mobile hiện tại, chỉ thêm state 4 mốc + quota display + GPS scope theo unit; không viết lại request center.

Delta (build mới)

  • State machine 4 mốc: thêm 3 state + 2 event (BreakOut, BreakIn) vào attendance_bloc.dart
  • GPS scope: thay query ALL branches → chỉ branch trong timekeeping_unit_branch
  • Quota "X/3 lần": hiện chưa có UI, cần thêm
  • Running total tháng: cần thêm

Day-1 guardrail

  • Chỉ mở chấm công on-site có GPS.
  • Remote: reuse logic remote_onetime hiện có — Daisy Ca 3 Marketing dùng đơn remote approved, logic bypass GPS đã có sẵn trong log_time_keeping.go (DEC-016).
  • Feature này không tạo request entry remote mới trên attendance shell. Nếu tenant/user vẫn được cấp flow remote legacy ở request center hiện tại thì flow đó tiếp tục chạy theo behavior cũ.
  • Nếu GPS ngoài branch hợp lệ trong timekeeping_unit, hiển thị lỗi và không ghi event.
  • Nếu user chưa được map vào unit rollout, mobile giữ behavior legacy hiện có.

State matrix — Ca 2 mốc (break_clocking_required = false)

StateĐiều kiệnCTA
no_eventChưa có record hôm nayVào ca
has_clock_inCó clock_in, chưa có clock_outRa về
completeCó clock_in + clock_outẨn CTA, chỉ xem lịch sử

State matrix — Ca 4 mốc (break_clocking_required = true)

StateĐiều kiệnCTA
no_eventChưa có recordVào ca
seg0_openseg0 có clock_in, chưa clock_outRa nghỉ
break_openseg0 complete, chưa có seg1Vào lại
seg1_openseg1 có clock_in, chưa clock_outRa về
completeseg0 + seg1 đều completeẨn CTA, chỉ xem lịch sử

Quyết định CTA dựa trên: shift_template.break_clocking_required của ca NV hôm đó. Flutter dev check field này trong response từ API, KHÔNG dùng timekeeping_unit để quyết định.

Mobile request entry

Day-1 chỉ mô tả và mở delta cho các entry request sau trên shell hiện có:

  • Quên chấm công
  • Đi trễ / Về sớm
  • Đổi ca
  • OT

Không mở thêm trong phạm vi feature này:

  • Remote
  • luồng annual leave report riêng

Mobile — Quota đơn còn lại (hiển thị cho NV)

Khi NV mở form tạo đơn trễ/sớm hoặc quên chấm:

text
┌─────────────────────────────────────────────────┐
│ ĐƠN ĐI TRỄ / VỀ SỚM                           │
│                                                   │
│ Bạn đã sử dụng: 2/3 lần trong tháng 04/2026    │
│ ⓘ Từ lần thứ 4 sẽ bị tính phạt theo quy định   │
│                                                   │
│ [Loại: Đi trễ ▼]                                │
│ [Ngày: 06/04/2026]                              │
│ [Từ: 08:00]  [Đến: 08:45]                       │
│ [Lý do: ...........................]             │
│                                                   │
│             [Gửi đơn]                             │
└─────────────────────────────────────────────────┘
Rule IDRule
MOB-REQ-001Hiển thị "X/3 lần" — count scoped theo timekeeping_unit + tháng hiện tại
MOB-REQ-002Count chỉ đếm request có status approved hoặc pending (không đếm rejected/canceled)
MOB-REQ-003Vẫn cho phép tạo đơn kể cả khi > 3 lần — HR quyết định approve/reject

Mobile — Lịch sử chấm công cá nhân (NV self-service)

NV xem lịch sử chấm công của mình trên app:

text
┌─────────────────────────────────────────────────┐
│ LỊCH SỬ CHẤM CÔNG — Tháng 04/2026              │
│                                                   │
│ 06/04 (T7)  Ca sáng 8h                          │
│   ✅ Vào ca: 07:58   Ra về: 17:05                │
│   Ngày công: 1.0                                  │
│                                                   │
│ 05/04 (T6)  Ca sáng 8h                          │
│   ⏰ Vào ca: 08:32   Ra về: 17:00                │
│   Đi trễ: 32 phút                                │
│   Ngày công: 1.0                                  │
│   📋 Đơn đi trễ: Đã duyệt                       │
│                                                   │
│ 04/04 (T5)  Ca BS 4 mốc                         │
│   ✅ Vào: 08:00  Ra nghỉ: 12:05                  │
│      Vào lại: 13:55  Ra về: 17:02                │
│   Ngày công: 1.0                                  │
│                                                   │
│ Tổng tháng: 5/22 ngày công                       │
│ Trễ: 1 lần | Sớm: 0 lần | Quên: 0 lần          │
└─────────────────────────────────────────────────┘
Rule IDRule
MOB-HIST-001NV chỉ xem data của mình (self-only, scope timekeeping_unit tự resolve)
MOB-HIST-002Ca 4 mốc hiển thị 4 dòng, ca 2 mốc hiển thị 2 dòng
MOB-HIST-003Hiển thị running total tháng: ngày công, lần trễ, lần sớm, lần quên
MOB-HIST-004Overlay request nếu có (đơn đi trễ, quên chấm, nghỉ phép...)

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Punch CTAKhi user bấm CTA, lock button hiện tại + spinner cho đến khi API trả kết quả
GPS / permission checkKiểm tra quyền vị trí trước khi gọi API; nếu chưa cấp quyền thì chặn và mở helper xin quyền
State refreshSau punch thành công, state machine và lịch sử hôm nay refresh ngay trên màn
Unsaved / offlineDay-1 không có offline queue; nếu mất mạng thì không ghi local draft punch
RetryNếu API lỗi tạm thời, giữ nguyên CTA hiện tại và cho phép bấm lại sau khi lỗi biến mất
Request follow-upTừ trạng thái lỗi/thiếu mốc, CTA phụ dẫn sang request flow hiện có mà không làm mất context ngày

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Punch thành côngSuccess toast/snackbar + badge trạng thái mớiCTA chuyển sang mốc kế tiếp hoặc ẩn nếu complete
GPS ngoài phạm viError banner/snackbar nêu chi nhánh hợp lệ gần nhất hoặc lý do bị chặnUser di chuyển vị trí rồi thử lại
Chưa cấp quyền vị tríBottom sheet/helper modal hướng dẫn cấp quyềnSau khi cấp quyền, quay lại CTA cũ
Lỗi mạng / serverError snackbar + nút Thử lạiKhông đổi state machine local
Không có lịch hôm nayEmpty helper Hôm nay chưa có ca + link lịch sửKhông hiện CTA chấm công

Guardrails

  • Không đổi mobile behavior của user Diva hiện tại nếu không thuộc rollout unit.
  • Không mở CTA 4 mốc cho PN ca thường (Ca HC, Ca 1–8, Ca tối). PN ca gãy = 4 mốc (DEC-014 confirmed).
  • Không mở CTA 2 mốc đơn giản cho Daisy nếu ca được đánh dấu 4 mốc (break_clocking_required = true).
  • Daisy VP user có ca 2 mốc → CTA giống PN, không ép 4 mốc.
  • Không mở CTA/request entry remote mới trên attendance shell của Daisy/PN.
  • Daisy Ca 3 Marketing Trực Page (chấm từ xa): Day-1 dùng workaround remote_onetime request approved trước (DEC-016).

B2) SCR-06: Xuất dữ liệu chấm công (Timekeeping Export)

Vai trò trong flow

SCR-06 là điểm "chốt sổ vận hành", không phải nơi tự sửa dữ liệu. HR phải đi vào export từ SCR-02 với đúng filter tháng/unit/branch/department đã đứng sẵn, để tránh tình trạng xem một scope nhưng export ra scope khác.

Scope

Export báo cáo vận hành chấm công. Export tính tiền phạt + tiền OT tự động từ config per unit (DEC-012 v3, DEC-026). HR KHÔNG cần tính tay trên Excel.

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Xuất đúng loại báo cáo theo đúng scope đang đứng ở SCR-02 để chốt vận hành
Primary CTAXuất file / Tạo file export
Secondary CTAChọn loại file, xem preview cột, quay lại SCR-02
Khối thông tin phải nổi bậtScope export hiện tại (unit/tháng/branch/department), loại báo cáo, trạng thái job export
Điều không được gây hiểu nhầmExport không được tự đổi scope so với SCR-02; đây không phải payroll bridge

File types — Reuse map

FileMục đíchReuse codebaseDelta
Bảng công thángChốt công theo thángReuse ExportWorkingTimeSheet.tsx (hiện có 3 cột + grid). Override column mapping thêm 19 cộtExtend column mapping + filter unit + cột Tiền phạt + Tiền OT
Chi tiết ngàyĐiều tra từng ngàyReuse ExportWorkingTimeSheetByDay.tsx (hiện đã có 14 cột: STT, Ngày, Họ tên, Mã NV, Phòng ban, CN vào, CN ra, Ca, Giờ vào, Giờ ra, Trễ phút, Sớm phút, Đơn, OT. Logic getTimeLateArrival() + getTimeEarlyLeave() đã có)Thêm filter unit + 4 cột cho ca 4 mốc + cột Tiền phạt
Tổng côngTổng ngày công thángReuse ExportTotalWorkingTimeSheetByDay.tsx (hiện đã có đủ 5 cột + logic calculateWorkDays())Chỉ thêm filter unit + cột đơn vị
Báo cáo trễ/sớmTheo dõi vi phạmReuse logic từ getTimeLateArrival() + getTimeEarlyLeave() trong ExportByDayTạo component mới, reuse core logic
Báo cáo OTTheo dõi OTReuse logic từ getOverTime() trong ExportByDayTạo component mới, reuse core logic
Báo cáo quên chấmTheo dõi thiếu mốcBuild mớiCount missing per type per tháng
Báo cáo phép nămPhép năm PNReuse AnnualLeaveReport.tsx + AnnualLeaveReportFilter.tsx + AnnualLeaveReportTable.tsx (hiện đã có filter năm/phòng ban/chi nhánh + export Excel + DB view report_annual_leave)Chỉ thêm filter unit

Column spec — Export bảng công tháng (tổng hợp)

CộtNội dung
Mã NVuser_id
Họ têndisplay_name
Đơn vị chấm côngtimekeeping_unit.name
Chi nhánhbranch.name (primary branch trong unit)
Phòng bandepartment.name
Nhóm cashift_group.name
Tổng ngày côngsum(workday) trong tháng
Tổng ngày nghỉ (off)Số ngày off = true
Tổng ngày nghỉ phépSố ngày có request leave approved
Tổng lần đi trễCount ngày có late_arrival = true
Tổng phút đi trễsum(late_minutes)
Tổng lần về sớmCount ngày có leave_early = true
Tổng phút về sớmsum(early_minutes)
Tổng lần quên chấmmissing_start_count + missing_end_count + missing_break_count
Tổng giờ OTsum(ot_hours)
Số đơn đi trễ/về sớm đã dùngCount request late_arrival_early_leave approved trong tháng
Số đơn quên chấm đã dùngCount request forget_clock_in_out approved trong tháng
Công chuẩnstandard_workday (tự tính từ timekeeping_standard_workday_rule theo standard_workday_scope_key)
Tiền phạtTự tính từ timekeeping_penalty_rule per unit (DEC-012 v3). Đơn vị: VNĐ
Tiền OTot_hours × ot_rate (BS dùng ot_rate_doctor, còn lại dùng ot_rate_default). Đơn vị: VNĐ

Column spec — Export báo cáo trễ/sớm

CộtNội dung
Mã NVuser_id
Họ têndisplay_name
Chi nhánhbranch.name
Phòng bandepartment.name
Ngàydate
Cashift_template.name
Giờ quy định vàostart_working_time
Giờ thực tế vàoclock_in
Phút đi trễlate_minutes
Giờ quy định raend_working_time
Giờ thực tế raclock_out
Phút về sớmearly_minutes
Có đơn xin phépCó / Không (request late_arrival_early_leave approved)
Tổng lần đi trễ/về sớm thángRunning count trong tháng (để HR biết NV đã dùng mấy lần)
Tiền phạtTự tính per violation từ timekeeping_penalty_rule (VD: 15p × 10.000đ = 150.000đ)

Column spec — Export chi tiết ngày

Ca 2 mốc (PN, Daisy VP):

CộtNội dung
Mã NVuser_id
Họ têndisplay_name
Đơn vị chấm côngtimekeeping_unit.name
Ngàycreated_at
Cashift_template.name
Giờ vàoclock_in
Giờ raclock_out
Phút đi trễlate_minutes
Phút về sớmearly_minutes
Ngày côngworkday
OT (giờ)ot_hours (từ request OT approved; PN bỏ qua nếu <30 phút)
Trạng tháicomplete / missing_start / missing_end / off
Tiền phạtTự tính per day từ timekeeping_penalty_rule
Tiền OTot_hours × ot_rate per day
Ghi chúRequest overlay nếu có (đơn quên chấm, đổi ca, nghỉ phép...)

Ca 4 mốc (Daisy DV, PN ca gãy):

CộtNội dung
Mã NVuser_id
Họ têndisplay_name
Đơn vị chấm côngtimekeeping_unit.name
Ngàycreated_at
Cashift_template.name
Giờ vào casegment_0.clock_in
Giờ ra nghỉsegment_0.clock_out
Giờ vào lạisegment_1.clock_in
Giờ ra vềsegment_1.clock_out
Phút trễ đầu caseg0_late_minutes
Phút sớm cuối caseg1_early_minutes
Phút trễ vào lạiseg1_late_minutes
Phút sớm ra nghỉseg0_early_minutes
Ngày côngworkday
OT (giờ)ot_hours
Trạng tháicomplete / missing_start / missing_break / missing_end / partial
Tiền phạtTự tính per day từ timekeeping_penalty_rule
Tiền OTot_hours × ot_rate per day
Ghi chúRequest overlay

Rules

Rule IDRule
EXP-001Không có payroll bridge, không có action payroll
EXP-002Daisy export chi tiết ngày hiện 4 mốc nếu ca break_clocking_required = true
EXP-003Daisy ca VP (2 mốc) export giống format PN
EXP-004PN export OT bỏ qua request OT < ot_min_threshold_minutes (PN = 30 phút)
EXP-005Export tính tiền phạt + tiền OT tự động từ config per unit (DEC-012 v3, DEC-026)
EXP-006Tiền phạt: đọc timekeeping_penalty_rule, áp dụng exempt_count + exempt_pool (PN = individual, Daisy = shared)
EXP-007Tiền OT: NV Bác sĩ dùng ot_rate_doctor, còn lại dùng ot_rate_default
EXP-008Báo cáo phép năm: Day-1 cho PN (DEC-023). Daisy chưa mở entry báo cáo phép năm trong rollout này

Interaction Behavior Contract

Tình huốngBehavior mong muốn
Entry from SCR-02Modal/screen export nhận filter hiện tại theo dạng readonly summary; user không đổi Đơn vị chấm công ở đây
File type switchĐổi loại báo cáo refresh preview cột tương ứng, không mất scope
Long-running exportNếu quá ngưỡng async, tạo background job và cho phép đóng modal mà không hủy job
Re-trigger exportNếu cùng một file type + scope đang chạy job, hiện trạng thái Đang tạo file, không tạo job trùng

Action Feedback Contract

Action / SituationFeedback UIHành động tiếp theo
Tạo export thành công (sync)Toast xanh + link tải fileUser ở lại hoặc đóng modal
Tạo export asyncBanner/job card Đang tạo fileKhi xong gửi notification/in-app status
Không đủ dữ liệuInline warning nêu scope hiện tại không có bản ghiUser quay lại SCR-02 để đổi filter
Lỗi exportToast đỏ + nút Thử lạiGiữ nguyên loại file và scope

B2) SCR-07: Danh sách đơn từ chấm công (Request List)

Delta

  • Reuse màn danh sách đơn từ hiện có
  • Thêm filter Đơn vị chấm công (auto-select theo user)
  • Approver chỉ thấy đơn trong timekeeping_unit mình, KHÔNG cross-unit
  • Diva user (không có unit) → giữ nguyên, không thấy filter unit

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Duyệt hoặc đối soát đơn đúng trong scope unit mình phụ trách
Primary CTADuyệt / Từ chối trên request row/detail
Secondary CTAFilter theo loại đơn, trạng thái, Đơn vị chấm công
Khối thông tin phải nổi bậtĐơn vị chấm công, loại đơn, trạng thái duyệt, người gửi

Rules

Rule IDRule
REQ-001Approver chỉ nhận và thấy đơn từ NV cùng timekeeping_unit
REQ-002Danh sách đơn filter theo timekeeping_unit_id của approver
REQ-003Diva approver không bị ảnh hưởng — query legacy

B2) SCR-08: Hồ sơ nhân viên (Employee Profile) — badge đơn vị chấm công

Delta

  • Thêm field readonly "Đơn vị chấm công" trên profile NV (hiện tên unit, VD: "Phương Nam")
  • Nếu NV không thuộc unit → không hiện field
  • HR mở profile NV PN → thấy badge "Phương Nam" + chi nhánh chính

Mục tiêu màn hình & CTA hierarchy

ConcernChốt trong spec
Người dùng vào màn để làm gì?Xác nhận nhanh NV đang thuộc đơn vị chấm công nào khi tra hồ sơ
Primary CTAKhông có CTA ghi dữ liệu trên badge này
Secondary CTAVới role phù hợp có thể deeplink sang SCR-00 > Nhân viên áp dụng để chỉnh assignment
Khối thông tin phải nổi bậtBadge Đơn vị chấm công + chi nhánh chính

B4) Hợp đồng thông báo (Notification contract) — reuse event trigger hiện có

Reuse: Event trigger request_working_schedule_insert đã có sẵn — resolve reviewers via GetReviewersNextstepWithConfig() → gửi push notification. Chỉ cần verify routing đúng per-unit approver (approver Daisy không nhận đơn PN).

EventNgười nhậnFilter unit
NV tạo đơn mớiApprover cùng timekeeping_unit + cùng department/branchCó — chỉ approver cùng unit
Approver duyệt/từ chốiNV tạo đơnKhông cần filter — gửi cho owner
NV chấm trễ >60 phútManager cùng unit + departmentCó — chỉ manager cùng unit
Cronjob phát hiện quên chấmNV đóKhông cần filter

Rule: Notification route theo timekeeping_unit. Approver Daisy KHÔNG nhận thông báo đơn PN. Diva notification giữ nguyên logic cũ.


B5) Ma trận quyền (Permission Matrix)

SurfaceStaffManagerHR/Admin timekeepingSystem Admin
SCR-00Xem theo scope
SCR-00C✅ theo scope
SCR-00D✅ theo scope
SCR-01Theo scope
SCR-02Theo scope
SCR-05✅ self
SCR-06Theo scope nếu được cấp✅ theo scope

B5-1) Cơ chế "theo scope" — Auto-filter & Default Unit

Cách xác định scope của user đang đăng nhập:

  1. Truy vấn timekeeping_unit_user WHERE user_id = current_user AND disabled = false AND effective_from <= today AND (effective_to IS NULL OR effective_to >= today)
  2. Nếu user thuộc đúng 1 unitdefault_timekeeping_unit_id = unit đó
  3. Nếu user không thuộc unit nào (Diva native) → filter Đơn vị chấm công không xuất hiện, mọi màn timekeeping hiển thị behavior legacy hiện tại
  4. System Admin (BOD, ITLeader, ITStaff) → filter Đơn vị chấm công hiện dropdown tất cả unit active, không auto-select

Behavior khi mở trang admin (SCR-01, SCR-02, SCR-06):

Loại userFilter Đơn vị chấm côngMặc định
HR Daisy (thuộc unit Daisy)Hiện, auto-select Daisy, không đổi được sang PNChỉ thấy data Daisy
HR PN (thuộc unit PN)Hiện, auto-select PN, không đổi được sang DaisyChỉ thấy data PN
Manager DaisyHiện, auto-select Daisy, chỉ thấy department/branch mình quản lýScope Daisy + branch mình
System AdminHiện dropdown tất cả unit, bắt buộc chọn trước khi xem dataChưa chọn → empty state
Diva native (không có unit)Không hiện filter unitWorking Sheet/Schedule hiện data Diva như cũ

Rule:

Rule IDRule
PERM-001User chỉ thấy data của timekeeping_unit mình thuộc; không cross-unit
PERM-002System Admin phải chọn unit trước khi thao tác; không có view "all units merged"
PERM-003Diva native user không bị ảnh hưởng bởi feature mới — mọi màn timekeeping giữ nguyên
PERM-004HR Daisy không thể đổi filter sang PN hoặc Diva (và ngược lại)
PERM-005Manager chỉ thấy department/branch mình quản lý TRONG scope unit mình

B6) Từ điển Copy Text (Copy Text Dictionary)

KeyTiếng ViệtNgữ cảnh
timekeeping_unit.open_missing_stepMở bước còn thiếuCTA trên readiness checklist của SCR-00
timekeeping_unit.unsaved_changesBạn có thay đổi chưa lưu ở tab nàyDirty state khi rời tab trong SCR-00
timekeeping_config.apply_tomorrowÁp dụng từ ngày maiCTA publish ở Tab Quy định
timekeeping_shift.runtime_previewXem trước kết quả runtimeTiêu đề panel preview trong SCR-00C
working_schedule.empty_missing_prerequisiteChưa thể tạo lịch vì đơn vị này còn thiếu cấu hình đầu vàoEmpty state của SCR-01
working_sheet.no_data_in_scopeChưa có dữ liệu chấm công trong phạm vi đang chọnEmpty state của SCR-02
attendance.gps_out_of_scopeBạn đang ngoài phạm vi GPS hợp lệ của ca làm việc nàyError feedback của SCR-05
export.job_processingHệ thống đang tạo file, bạn có thể tiếp tục làm việc khácBanner/toast async export ở SCR-06

B7) Responsive + Accessibility Rules

B7.1) Responsive Rules

SurfaceDesktop / LaptopTabletMobile
SCR-00, SCR-00C, SCR-00D, SCR-01, SCR-02, SCR-06, SCR-07, SCR-08Layout đầy đủ; filter bar được phép wrap thành 2 dòng khi <1280px; bảng có horizontal scroll và sticky headerƯu tiên tra cứu + thao tác nhẹ; popup full-width hơn; bảng vẫn cho scroll ngangAdmin web không phải target Day-1; nếu mở trên <768px thì ưu tiên helper Vui lòng dùng laptop/desktop thay vì cố nhồi full table
SCR-03 popupPopup giữa màn, max-width desktopPopup rộng gần full-screenNếu mở từ mobile web admin thì dùng full-screen modal
SCR-05 mobile appKhông áp dụngKhông áp dụngNative mobile là target chính; CTA chấm công phải luôn nằm trong vùng dễ chạm bằng ngón tay cái

B7.2) Mobile Interaction Contract

ConcernRule
Sticky CTASCR-05 giữ CTA chấm công ở vùng cuối màn hoặc sticky footer, không để user phải cuộn mới thấy CTA chính
Modal / drawer trên mobileCác helper như xin quyền vị trí, lỗi GPS, confirm action dùng bottom sheet hoặc full-screen modal tùy độ dài nội dung
Collapse patternỞ admin web trên tablet, filter bar wrap xuống 2 dòng; bảng dài dùng horizontal scroll thay vì collapse mất cột quan trọng
Touch targetCTA chính và action phá huỷ trên mobile phải có vùng chạm tối thiểu 44x44px

B7.3) Accessibility / Keyboard Contract

ConcernRule
Focus orderPopup/admin form focus theo thứ tự trái sang phải, trên xuống dưới; field lỗi đầu tiên nhận focus sau submit fail
Focus returnĐóng SCR-03 hoặc popup cấu hình phải trả focus về đúng trigger trước đó
Focus trapModal / popup trong admin phải trap focus cho tới khi đóng
KeyboardEnter submit form khi hợp lệ; Esc đóng popup nếu không có thao tác phá huỷ đang chờ confirm
Screen reader / labelIcon-only buttons như refresh, close, export phải có label text hoặc aria-label tương ứng
Không chỉ dựa vào màuBadge ✅ / ⚠️ / ❌ và trạng thái trễ/sớm/thiếu mốc phải luôn có text, không chỉ dùng màu

B8) Sự kiện phân tích (Analytics)

EventÝ nghĩa
timekeeping_unit_changedUser đổi filter đơn vị chấm công
timekeeping_setup_badge_clickedUser click badge readiness để mở bước còn thiếu trong SCR-00
timekeeping_config_publishedAdmin publish draft cấu hình unit
timekeeping_shift_preview_changedAdmin đổi field trong SCR-00C làm preview runtime thay đổi
timekeeping_mobile_cta_clickedUser bấm CTA chấm công
timekeeping_mobile_punch_failedUser chấm công thất bại do GPS/quyền vị trí/network
timekeeping_export_generatedUser xuất báo cáo chấm công
timekeeping_export_async_completedJob export async hoàn tất

B9) Từ điển Tooltip (Tooltip Dictionary)

Màn hìnhField / IconTooltip textĐiều kiện hiện
SCR-00Badge Mở bước còn thiếuMở đúng màn hoặc tab còn thiếu để hoàn tất điều kiện bật áp dụngKhi badge là ⚠️ hoặc
SCR-00Trạng thái Chờ hiệu lựcChỉ dùng cho gán nhân viên có effective_from trong tương laiHover badge ở Tab Nhân viên áp dụng
SCR-00Action Đồng bộ theo cơ cấu hiện tạiSo sánh nhân sự trong scope hiện tại với assignment đang active để tạo danh sách thêm / gỡ / chuyển đơn vịHover action bulk ở Tab Nhân viên áp dụng
SCR-00CCông tối đa của caGiá trị công tối đa mà ca này đóng góp trong ngày. Đây là nhãn hiển thị mới của field workdayHover icon cạnh field
SCR-00CGiờ chuẩn của caSố giờ chuẩn dùng để quy đổi công khi Cách tính công = Theo giờChỉ hiện khi mode = Theo giờ
SCR-00CYêu cầu chấm 4 mốcBật để ca này có thêm Ra nghỉVào lại trên mobile, bảng công và export chi tiết ngàyHover icon cạnh toggle
SCR-05Quota đơn X/3 lầnSố đơn đã dùng trong tháng hiện tại. Vượt quota vẫn gửi được nhưng có thể bị xử lý theo quy địnhKhi mở form trễ/sớm hoặc quên chấm
SCR-06Báo cáo phép nămDay-1 chỉ mở cho Phương Nam; Daisy chưa có entry này trong rollout hiện tạiHover vào option file type

Kết luận

UI spec v3.1.5 không chỉ khóa flow SaaS-ready mà còn khóa thêm lớp product execution: mục tiêu từng màn, CTA hierarchy, interaction behavior, feedback sau action, copy text, responsive và accessibility. Với phạm vi Day-1, file này đã tiến gần hơn mức team FE/UI/QA có thể follow để build đúng intent mà không phải tự suy diễn các điểm hành vi quan trọng.