Skip to content

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ề list

Dữ liệu create quan trọng

Field familyVai trò
kpi.name, description, type_id, from, toHeader KPI
total_*Tổng ngưỡng mức kết quả của KPI
participants.dataParticipant references + target totals riêng từng participant
metric_relationsWeight + thresholds của từng metric
templates.captureSnapshot template khi isSaved = true

Rules

IDRule
KLS-001KPI create không gọi action backend riêng; create path đi thẳng qua Hasura insert.
KLS-002FE chuẩn hóa from về đầu ngày và to về cuối ngày.
KLS-003KPI branch và KPI personal dùng cùng create shell nhưng map participant khác reference_id.
KLS-004Sau 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ầnCách xử lý
Headerupdate_kpi_by_pk
Participants bị xóadelete participants + delete related metric relations
Participants mớiinsert participants
Metric relations bị xóadelete metric relations
Metric relations mới/changedinsert lại objects với mapping mới
Child additional rangessync 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 độngCách làm
UI action cancelChỉ hiện khi status là inprogress
Mutationupdate_kpi_by_pk(_set: { canceled_at: now })
Hệ quảFE xem KPI là canceled

Delete KPI

Hành độngCách làm
UI action deleteChỉ hiện khi status là new
Mutationupdate_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.

StatusPredicate
canceledcanceled_at
newfrom ở tương lai
closedto đã qua
inprogressfrom <= 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,
  • done không phải canonical lifecycle state của module.

5. Detail routing là role-aware

KPIDetail.tsx build tabs theo role và participation:

User shapeTabs
Manager rolesmetric, target
Branch manager với KPI branch có managed participantbranch-kpi
Assignee của KPImy-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ả:

  1. người dùng vào module trước tiên thấy dashboard revenue,
  2. KPI core CRUD surface tồn tại nhưng không phải first impression của module.

7. Findings

IDFinding
KLS-F01KPI core lifecycle đang sống bằng computed status; không có audit transition log cho cancel/delete/update dates.
KLS-F02done là status phụ của UI progress, dễ bị nhầm là status thật của module.
KLS-F03Entry route của module và mental model "KPIs list" hiện lệch nhau rõ ràng.