Appearance
Type — KPI Lifecycle And Status
1. Create KPI definition
KPICreate.tsx tạo KPI core theo direct Hasura insert.
text
Nhập general info
-> chọn type branch hoặc personal
-> chọn targets/participants
-> chọn metric relations + child ranges
-> optional: save template capture
-> insert kpi + participants
-> insert kpi_metric_relation
-> redirect về listDữ liệu create quan trọng
| Field family | Vai trò |
|---|---|
kpi.name, description, type_id, from, to | Header KPI |
total_* | Tổng ngưỡng mức kết quả của KPI |
participants.data | Participant references + target totals riêng từng participant |
metric_relations | Weight + thresholds của từng metric |
templates.capture | Snapshot template khi isSaved = true |
Rules
| ID | Rule |
|---|---|
| KLS-001 | KPI create không gọi action backend riêng; create path đi thẳng qua Hasura insert. |
| KLS-002 | FE chuẩn hóa from về đầu ngày và to về cuối ngày. |
| KLS-003 | KPI branch và KPI personal dùng cùng create shell nhưng map participant khác reference_id. |
| KLS-004 | Sau create KPI header thành công, FE còn phải insert metric relations mới hoàn tất object graph. |
2. Update KPI definition
KPIUpdate.tsx không chỉ sửa header mà còn sync delta của participants và metrics.
Update semantics
| Thành phần | Cách xử lý |
|---|---|
| Header | update_kpi_by_pk |
| Participants bị xóa | delete participants + delete related metric relations |
| Participants mới | insert participants |
| Metric relations bị xóa | delete metric relations |
| Metric relations mới/changed | insert lại objects với mapping mới |
| Child additional ranges | sync riêng theo nhánh child metrics |
Điều này cho thấy update KPI thực tế là "reconcile tree", không phải patch vài field.
3. Cancel và delete là soft actions
Cancel KPI
| Hành động | Cách làm |
|---|---|
UI action cancel | Chỉ hiện khi status là inprogress |
| Mutation | update_kpi_by_pk(_set: { canceled_at: now }) |
| Hệ quả | FE xem KPI là canceled |
Delete KPI
| Hành động | Cách làm |
|---|---|
UI action delete | Chỉ hiện khi status là new |
| Mutation | update_kpi_by_pk(_set: { disabled: true }) |
| Hệ quả | builder list thường lọc disabled = false, nên KPI biến mất khỏi list |
Không thấy hard delete trên kpi core path này.
4. Status là computed status
KPIStatus.tsx dùng các predicate thời gian thay vì đọc status field.
| Status | Predicate |
|---|---|
canceled | có canceled_at |
new | from ở tương lai |
closed | to đã qua |
inprogress | from <= now <= to |
Display state phụ
KPIProgress.mapStatus() sẽ trả done khi progress >= 100% trong lúc base status là inprogress hoặc closed.
Hệ quả:
- cùng một KPI có thể hiển thị badge trạng thái theo lifecycle và progress bar theo status phụ khác nhau,
donekhông phải canonical lifecycle state của module.
5. Detail routing là role-aware
KPIDetail.tsx build tabs theo role và participation:
| User shape | Tabs |
|---|---|
| Manager roles | metric, target |
| Branch manager với KPI branch có managed participant | branch-kpi |
| Assignee của KPI | my-kpi |
Route sâu branch-kpi/:assigneeId, personal-kpi/:assigneeId | ẩn tab header, vào thẳng participant detail |
Điều này làm detail KPI không phải trải nghiệm giống nhau cho mọi actor.
6. Entry route drift
/k/KPIs hiện render KPIRevenue, không phải KPITable.
Điều đó có 2 hệ quả:
- người dùng vào module trước tiên thấy dashboard revenue,
- KPI core CRUD surface tồn tại nhưng không phải first impression của module.
7. Findings
| ID | Finding |
|---|---|
| KLS-F01 | KPI core lifecycle đang sống bằng computed status; không có audit transition log cho cancel/delete/update dates. |
| KLS-F02 | done là status phụ của UI progress, dễ bị nhầm là status thật của module. |
| KLS-F03 | Entry route của module và mental model "KPIs list" hiện lệch nhau rõ ràng. |