Appearance
Thu thập & Chuẩn hoá thông tin cá nhân khách hàng
Version: 1.1 Date: 25/03/2026 Author: PO/BA Type: New Feature Complexity: M Module: Customer App (Flutter) + Admin Web (Settings) + Backend (Hasura)
Changelog
| Version | Date | Author | Thay đổi |
|---|---|---|---|
| 1.0 | 25/03/2026 | PO/BA | Initial |
| 1.1 | 27/03/2026 | PO/BA | DEC-009: Occupation dropdown lấy từ default_master_data thay vì hardcode. Bỏ CRUD occupation inline trong tab Consent → reference trang Master Data |
Hướng dẫn đọc (RACI)
| Audience | Đọc sections | Trách nhiệm |
|---|---|---|
| PO/BA | PRD: A0 (overview) → Z → A5 (FR) | Approve requirements, UX flows |
| Tech Lead | PRD: A0 → Z. Dev Spec: C1-C4 | Approve architecture |
| FE Dev (Mobile) | UI Spec: B2-B6. Dev Spec: C5-C6 | Implement Flutter screens |
| FE Dev (Admin) | UI Spec: B2 (SCR-03). Dev Spec: C6 | Implement admin tab |
| BE Dev | Dev Spec: C1-C12 | Migration + Hasura metadata |
| QA | PRD: A5. QA Plan: D1-D5 | Write + execute test cases |
Executive Summary (TL;DR)
Thu thập thông tin cá nhân (ngày sinh, nghề nghiệp, tỉnh/thành) của khách hàng qua popup đề xuất cập nhật trên app Flutter, kèm consent screen đơn giản để ghi nhận đồng ý xử lý dữ liệu. Admin quản lý nội dung từ web, thống kê % cập nhật để BOD theo dõi KPI 40-50%.
Milestones
| Milestone | Target | Owner | Điều kiện |
|---|---|---|---|
| BE: Migration + Hasura metadata + seed data | T+1 ngày | BE Dev | — |
| Mobile: Consent screen + Profile update popup | T+4 ngày | Mobile Dev | Sau BE deploy |
| FE Web: Admin tab Consent + thống kê | T+3 ngày | FE Dev | Sau BE deploy |
| QA: Test all platforms | T+7 ngày | QA | Sau Mobile + FE deploy |
| Go-Live | T+8 ngày | Tech Lead | QA pass |
Trạng thái Sign-off
| Domain | Người | Status |
|---|---|---|
| Business | PO | Pending |
| Tech | Tech Lead | Pending |
| QA | QA Lead | Pending |
Pending Decisions
Không có Pending Decision — tất cả đã locked trong Decision Log.
Backlog Phase 2 (Out-of-scope)
| # | Tính năng | Lý do defer |
|---|---|---|
| 1 | Enforce consent (filter notification, ẩn ảnh điều trị) | Sếp quyết định chỉ thu thập, không enforce |
| 2 | Incentive (tặng điểm/voucher khi cập nhật) | Sếp quyết định không có |
| 3 | Settings > Quyền riêng tư trên app khách | Chưa có nhu cầu |
| 4 | Multi-page consent (như 30Shine) | Quá phức tạp cho scope hiện tại |
| 5 | Admin/POS consent | Chỉ áp dụng cho customer app |
| 6 | Consent log/history chi tiết | Dùng audit fields cơ bản |
| 7 | GPS auto-detect tỉnh/thành | Không cần thêm plugin, phức tạp permission |
Z) Decision Log
| ID | Category | Quyết định | Lý do | Ngày | Status |
|---|---|---|---|---|---|
| DEC-001 | Business | Đề xuất cập nhật thông tin là trọng tâm, consent là phụ | Mục đích chính là thu thập data thống kê (birthday + occupation + province), không phải compliance. 2 hướng: (A) consent-first → phức tạp, low conversion; (B) data-collection-first → đơn giản, đạt KPI. Chọn B | 25/03/2026 | Locked |
| DEC-002 | UX | Consent đơn giản single-page, không multi-page | 2 hướng: (A) multi-page từng bước (30Shine style) → UX chuyên nghiệp hơn nhưng dài, drop-off cao; (B) single-page checkbox → nhanh, ít friction. Sếp chọn B vì consent chỉ là phụ | 25/03/2026 | Locked |
| DEC-003 | Business | Thu thập 3 fields: ngày sinh + nghề nghiệp + tỉnh/thành | 2 hướng: (A) chỉ birthday → thiếu data; (B) birthday + occupation + province → đủ phân khúc tuổi + lifestyle + vùng miền. Chọn B vì 3 fields vừa đủ, không quá nhiều gây bỏ qua | 25/03/2026 | Locked |
| DEC-004 | Technical | Bỏ GPS auto — khách tự chọn tỉnh/thành từ dropdown | 2 hướng: (A) GPS auto-detect → cần thêm plugin, xin GPS permission, battery drain; (B) dropdown từ danh sách có sẵn → không cần plugin, data tỉnh/thành đã có trong geo DB. Chọn B | 25/03/2026 | Locked |
| DEC-005 | UX | Hỏi lại nếu bỏ qua — sau mỗi 4 lần mở app, tối đa 3 lần | 2 hướng: (A) hỏi 1 lần duy nhất → KPI thấp (~20%); (B) hỏi lại có kiểm soát → KPI 40-50%. Chọn B với cap 3 lần để không spam | 25/03/2026 | Locked |
| DEC-006 | Business | Thống kê % cập nhật trên tab Consent admin | 2 hướng: (A) dashboard/report riêng → effort lớn; (B) tab trong Settings > Ứng dụng KH → tận dụng UI có sẵn, aggregate query đơn giản. Chọn B | 25/03/2026 | Locked |
| DEC-007 | Business | Không có incentive (không tặng điểm/voucher) | 2 hướng: (A) tặng điểm/voucher khi cập nhật → tăng conversion nhưng thêm cost + logic; (B) không incentive → đơn giản. Sếp quyết định B | 25/03/2026 | Locked |
| DEC-008 | Technical | Nội dung consent + popup quản lý từ server (app_setting) | 2 hướng: (A) hardcode trong app → đơn giản nhưng cần deploy mỗi lần sửa; (B) server-driven JSON từ app_setting → admin sửa không cần deploy app. Chọn B | 25/03/2026 | Locked |
| DEC-009 | Technical | Danh sách nghề nghiệp lấy từ default_master_data (type='occupation'), không hardcode trong app_setting | 2 hướng: (A) hardcode 4 options trong profile_update_info.fields[].options → đơn giản nhưng mỗi lần thêm/sửa phải update seed; (B) lấy từ default_master_data đã có sẵn 11 items + FE Settings CRUD → tái sử dụng data có sẵn, admin tự quản lý tại Cài đặt > Dữ liệu tham chiếu > Nghề nghiệp. Chọn B | 27/03/2026 | Locked |
A) PRD
A0) Feature Overview
Section này là "bức tranh toàn cảnh" — đọc 2 phút nắm hết feature. Chi tiết xem A1-A9.
Ý tưởng cốt lõi
App khách hàng Diva hiện thiếu data nghề nghiệp và tỉnh/thành, nhiều khách bỏ trống ngày sinh khi đăng ký. Feature này thêm popup đề xuất cập nhật thông tin (birthday + occupation + province) sau login, kèm consent screen đơn giản ghi nhận đồng ý xử lý dữ liệu. Mục tiêu: 40-50% khách cập nhật để BOD có data phân khúc thống kê.
Kiến trúc tổng thể
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Flutter App │────→│ Hasura GQL │────→│ PostgreSQL │
│ (Customer) │ │ (Controller) │ │ (default DB) │
├─────────────┤ ├──────────────┤ ├──────────────────┤
│ ConsentScreen│ │ customer_ │ │ customer_consent │
│ ProfilePopup │ │ consent CRUD │ │ account │
│ Auth Flow │ │ app_setting │ │ account_address │
└─────────────┘ │ account CRUD │ │ app_setting │
└──────────────┘ └──────────────────┘
┌─────────────┐ │
│ Admin Web │───────────┘
│ (Settings) │
├─────────────┤
│ Consent Tab │
│ Config Form │
│ Stats Panel │
└─────────────┘Mapping sang tài liệu chi tiết:
| Khối | PRD (FR) | UI Spec (SCR) | Dev Spec (Section) |
|---|---|---|---|
| Consent Screen (Flutter) | FR-001 | SCR-01 | C4, C5 |
| Profile Update Popup (Flutter) | FR-002, FR-003 | SCR-02 | C4, C5 |
| Auth Flow Integration | FR-004 | — | C6 |
| App Open Tracking | FR-005 | — | C5 |
| Admin Consent Tab | FR-006, FR-007 | SCR-03 | C5, C6 |
Quyết định chính (tóm tắt theo nhóm)
| Nhóm | Quyết định | Ref |
|---|---|---|
| Chiến lược | Data collection là trọng tâm, consent là phụ; không incentive | DEC-001, DEC-007 |
| UX | Single-page consent; retry popup tối đa 3 lần, mỗi 4 lần mở app | DEC-002, DEC-005 |
| Data | 3 fields: birthday + occupation + province (dropdown, không GPS) | DEC-003, DEC-004 |
| Technical | Server-driven content từ app_setting; thống kê trên tab admin | DEC-006, DEC-008 |
Bảng FR tóm tắt
| FR | Mô tả | Priority | Phase |
|---|---|---|---|
| FR-001 | Consent Screen — fullscreen, dynamic content, checkbox items | Must | 1 |
| FR-002 | Popup đề xuất cập nhật TT — birthday + occupation + province | Must | 1 |
| FR-003 | Logic retry popup — hỏi lại có kiểm soát | Must | 1 |
| FR-004 | Tích hợp auth flow — check consent + update_info sau login | Must | 1 |
| FR-005 | Track app_open_count — đếm số lần mở app | Must | 1 |
| FR-006 | Admin: Config consent + profile update | Must | 1 |
| FR-007 | Admin: Thống kê % cập nhật | Must | 1 |
Data Model tóm tắt
| Table | Loại | Mục đích |
|---|---|---|
customer_consent | MỚI | Lưu consent data + tracking skip/complete/app_open |
account | CÓ SẴN | Cột birthday, occupation đã có |
account_address | CÓ SẴN | Cột province_code, province_name đã có |
app_setting | SEED | Thêm consent_config + profile_update_info |
Impact lên code hiện tại
| File/Area | Thay đổi | Risk |
|---|---|---|
splash_bloc.dart (Flutter) | Thêm load consent_config từ app_setting | Med — core flow |
| Auth flow navigation (Flutter) | Thêm consent + update_info check sau login/signup | Med — core flow |
ServerToAppSetting (Dart model) | Extend thêm consent_config, profile_update_info | Low |
| Admin settings page | Thêm tab "Consent" | Low |
Timeline
| Phase | Nội dung | Target |
|---|---|---|
| Phase 1 | BE: Migration + Hasura + seed | T+1 ngày |
| Phase 2 | Mobile: Consent + Popup + Auth integration | T+4 ngày |
| Phase 3 | FE Web: Admin tab + thống kê | T+3 ngày |
| Phase 4 | QA + Go-live | T+8 ngày |
A1) Blueprint
| Field | Value |
|---|---|
| Feature | Thu thập & Chuẩn hoá thông tin cá nhân khách hàng |
| Type | New Feature |
| Platform | Customer App (Flutter mobile) + Admin Web (diva-admin) |
| Module ảnh hưởng | Flutter: authentication, splash, dashboard. Admin: settings. BE: Hasura controller (default DB) |
A2) Context
As-Is
- App khách hàng Flutter đăng ký bằng SĐT → OTP → Signup form (tên, giới tính, ngày sinh, email, chi nhánh)
- Nhiều khách bỏ trống ngày sinh, email khi đăng ký
- Không có dữ liệu nghề nghiệp (
occupationcolumn tồn tại nhưng chưa collect) - Không có tỉnh/thành khách hàng (chỉ có cho branch/staff)
- Không có cơ chế consent xử lý dữ liệu, không có audit trail
- Terms/Privacy hiện chỉ là link webview ở trang Tài khoản
- Flow sau login: Dashboard → Event popup (nếu có) → Push notification prompt (OS)
To-Be
- Sau login/signup → Consent Screen (1 lần, ghi nhận đồng ý) → Popup đề xuất cập nhật (birthday + occupation + province) → Dashboard
- Khách bỏ qua popup → hỏi lại sau 4 lần mở app, tối đa 3 lần
- Admin quản lý nội dung consent + popup từ web, không cần deploy app
- Thống kê % khách đã cập nhật hiển thị trên admin
A3) Goals & Success Metrics
| Goal | Metric | Target |
|---|---|---|
| Thu thập thông tin cá nhân | % khách cập nhật ít nhất 1 field | 40-50% trong 3 tháng |
| Ghi nhận consent | % khách đã consent | 70%+ (vì consent screen bắt buộc hiện) |
| Phân khúc tuổi | % khách có ngày sinh | 40-50% |
| Phân khúc nghề nghiệp | % khách có occupation | 40-50% |
| Phân khúc vùng miền | % khách có tỉnh/thành | 40-50% |
A4) Personas
| Persona | Vai trò | JTBD | Frequency |
|---|---|---|---|
| Khách hàng (app) | Customer (role user) | Đồng ý consent + cập nhật thông tin cá nhân để nhận trải nghiệm tốt hơn | 1-3 lần (consent 1 lần, popup tối đa 3 lần) |
| Admin/BOD | Admin (role admin) | Quản lý nội dung consent, theo dõi % cập nhật qua thống kê | Hàng tuần |
A5) Functional Requirements
FR-001: Consent Screen (Ref: DEC-001, DEC-002, DEC-008)
Priority: Must | SCR: SCR-01
Flow:
Auth success → Load consent_config (app_setting)
→ Query customer_consent
→ Không có record HOẶC version < current → Show Consent Screen
→ version = current → SkipAC:
- [ ] Hiển thị fullscreen, không có nút back/close — khách phải bấm "Đồng ý & Tiếp tục"
- [ ] Render nội dung động từ
consent_configtrong app_setting: title, body, checkbox items - [ ] Mỗi checkbox item hiển thị label + description, mặc định bật (checked)
- [ ] Khách có thể bỏ checkbox — vẫn cho vào app, lưu lựa chọn
- [ ] Link "Chính sách bảo mật" mở webview với URL từ
app_setting.privacy_policy - [ ] Link "Điều khoản sử dụng" mở webview với URL từ
app_setting.term_condition - [ ] Bấm "Đồng ý & Tiếp tục" → upsert
customer_consentvới consent_data (JSON chứa trạng thái từng checkbox) + consent_version + accepted_at - [ ] Nếu
consent_configchưa tồn tại trong app_setting → skip, vào Dashboard bình thường - [ ] Kill app giữa consent screen → login sau hiện lại (vì chưa có record)
FR-002: Popup đề xuất cập nhật thông tin (Ref: DEC-001, DEC-003, DEC-004, DEC-005)
Priority: Must | SCR: SCR-02
Flow:
Sau Consent (hoặc skip consent) → Check account fields:
→ birthday != null AND occupation != null AND province_code != null → Skip
→ update_info_completed = true → Skip
→ update_info_skip_count >= max_skip (3) → Skip
→ Chưa đủ lần mở app → Skip (xem FR-003)
→ Else → Show PopupAC:
- [ ] Hiển thị dạng popup (dialog), có nút X (close) và nút "Bỏ qua" — không bắt buộc
- [ ] Render nội dung động từ
profile_update_infotrong app_setting: title, body, fields config - [ ] Chỉ hiển thị fields mà khách chưa có data (birthday null → hiện, đã có → ẩn)
- [ ] Field ngày sinh: date picker, format dd-mm-yyyy
- [ ] Field nghề nghiệp: dropdown lấy danh sách từ cấu hình hệ thống (
default_master_datatype='occupation', chỉ items códisabled = false). Quản lý tại Cài đặt > Dữ liệu tham chiếu > Nghề nghiệp (Ref: DEC-009) - [ ] Field tỉnh/thành phố: dropdown dùng danh sách province có sẵn trong geo DB (đã load sẵn tại splash)
- [ ] Mỗi field có hint text giải thích benefit (VD: "Cập nhật để nhận voucher sinh nhật từ Diva")
- [ ] Bấm "Cập nhật" → mutation update account (birthday, occupation) + upsert account_address (province_code, province_name) + set update_info_completed = true
- [ ] Khách chỉ cập nhật 1-2 fields → lưu fields đó, lần sau chỉ hỏi fields còn thiếu
- [ ] Nếu tất cả 3 fields đã có data → không hiện popup
FR-003: Logic retry popup đề xuất cập nhật (Ref: DEC-005)
Priority: Must | SCR: —
AC:
- [ ] Khách bấm "Bỏ qua" hoặc X → tăng
update_info_skip_count(+1) trong customer_consent - [ ] Popup chỉ hiện lại khi
app_open_count >= update_info_skip_count * reshow_after_opens(mặc định reshow_after_opens = 4) - [ ] Tối đa hỏi
max_skiplần (mặc định 3). Khiupdate_info_skip_count >= max_skip→ không hỏi nữa - [ ] Giá trị
max_skipvàreshow_after_opensconfig từ app_setting, admin có thể sửa - [ ] VD: skip lần 1 → hỏi lại sau 4 lần mở app. Skip lần 2 → hỏi lại sau 8 lần. Skip lần 3 → không hỏi nữa
FR-004: Tích hợp auth flow (Ref: DEC-001, DEC-002)
Priority: Must | SCR: —
Flow (sau auth success):
Auth success
→ Load consent_config từ app_setting
→ Query customer_consent + account fields
Bước 1 — Consent:
→ Không có record → Show ConsentScreen
→ version < consent_config.version → Show ConsentScreen
→ version = current → Skip
Bước 2 — Đề xuất cập nhật:
→ birthday + occupation + province_code đều có → Skip
→ update_info_completed = true → Skip
→ update_info_skip_count >= max_skip → Skip
→ app_open_count < skip_count * reshow_after_opens → Skip
→ Else → Show ProfileUpdatePopup
→ Navigate to DashboardAC:
- [ ] Khách mới (signup) → Consent Screen → Popup → Dashboard
- [ ] Khách cũ chưa consent → Consent Screen (1 lần) → Popup → Dashboard
- [ ] Khách đã consent + chưa cập nhật đủ → Popup (nếu đủ điều kiện retry) → Dashboard
- [ ] Khách đã consent + đã cập nhật đủ → Dashboard trực tiếp
- [ ] Flow consent/popup không block các flow hiện tại (event popup, push prompt vẫn chạy sau Dashboard)
FR-005: Track app_open_count (Ref: DEC-005)
Priority: Must | SCR: —
AC:
- [ ] Mỗi lần mở app (auth success) → increment
app_open_count(+1) trongcustomer_consentqua Hasura_incmutation - [ ] Chỉ increment khi đã có record
customer_consent(đã consent ít nhất 1 lần) - [ ] Giá trị dùng để tính điều kiện retry popup (FR-003)
FR-006: Admin — Config consent + profile update (Ref: DEC-006, DEC-008)
Priority: Must | SCR: SCR-03
AC:
- [ ] Thêm tab "Consent" trong Cài đặt > Ứng dụng khách hàng
- [ ] Section Consent: form sửa consent_config trong app_setting — input title, textarea body, CRUD danh sách checkbox items (key, label, description, default)
- [ ] Nút "Lưu" → update JSON giữ nguyên version (khách không bị hỏi lại)
- [ ] Nút "Lưu & Tăng version" → update JSON + version++ + hiện dialog xác nhận cảnh báo "Tất cả khách hàng sẽ phải đồng ý lại consent khi mở app"
- [ ] Section Đề xuất cập nhật: toggle bật/tắt popup, input max_skip, input reshow_after_opens. Danh sách nghề nghiệp quản lý tại Cài đặt > Dữ liệu tham chiếu > Nghề nghiệp (
default_master_datatype='occupation') — không CRUD inline trong tab Consent (Ref: DEC-009) - [ ] Bấm "Lưu cài đặt" → update
profile_update_infotrong app_setting
FR-007: Admin — Thống kê % cập nhật (Ref: DEC-006)
Priority: Must | SCR: SCR-03
AC:
- [ ] Hiển thị trên đầu tab Consent:
- Tổng khách dùng app (count account)
- Đã consent (count customer_consent) + %
- Đã cập nhật ngày sinh (count account WHERE birthday IS NOT NULL) + %
- Đã cập nhật nghề nghiệp (count account WHERE occupation IS NOT NULL) + %
- Có tỉnh/thành phố (count account_address WHERE province_code IS NOT NULL AND primary_contact = true) + %
- [ ] % tính = count / tổng khách dùng app * 100, hiển thị 1 decimal
- [ ] Data realtime (query mỗi lần mở tab, không cache)
A6) Assumptions
| ID | Assumption | Owner xác nhận |
|---|---|---|
| ASM-001 | Cột occupation trong bảng account đã tồn tại và Hasura user role có quyền update | BE (verified qua codebase discovery) |
| ASM-002 | Bảng account_address đã có province_code, province_name và Hasura customer role có quyền insert/update | BE (verified) |
| ASM-003 | Danh sách tỉnh/thành phố đã có sẵn trong geo DB và Flutter app đã load tại splash (getProvincesData()) | Mobile (verified) |
| ASM-004 | app_setting table dùng JSON field app_settings để lưu config, admin đã có UI sửa app_setting | FE Web (verified) |
| ASM-005 | Consent screen là fullscreen bắt buộc nhưng không enforce — khách bỏ checkbox vẫn vào app | PO |
A7) Risks
| ID | Risk | Impact | Probability | Mitigation |
|---|---|---|---|---|
| RSK-001 | Khách bỏ qua popup, KPI < 40% | Trung bình | Trung bình | Retry logic (3 lần), hint text giải thích benefit |
| RSK-002 | Consent screen gây friction, tăng churn | Thấp | Thấp | Single-page đơn giản, checkbox mặc định bật |
| RSK-003 | Admin thay đổi consent version vô ý → tất cả khách phải consent lại | Cao | Thấp | Dialog xác nhận cảnh báo khi "Lưu & Tăng version" |
| RSK-004 | Splash load thêm consent_config tăng startup time | Thấp | Thấp | consent_config nhỏ (< 1KB JSON), load song song với config hiện tại |
A8) Metrics (Post-launch)
| Metric | Cách đo | Target | Khi nào đo |
|---|---|---|---|
| % khách đã consent | count(customer_consent) / count(account) | 70%+ | Sau 1 tuần |
| % khách cập nhật birthday | count(birthday NOT NULL) / count(account) | 40-50% | Sau 1 tháng |
| % khách cập nhật occupation | count(occupation NOT NULL) / count(account) | 40-50% | Sau 1 tháng |
| % khách có province | count(province_code NOT NULL) / count(account) | 40-50% | Sau 1 tháng |
| Tỉ lệ bỏ qua popup | count(skip >= max_skip) / count(customer_consent) | < 30% | Sau 1 tháng |
| Consent screen drop-off | Số khách kill app tại consent / tổng hiện consent | < 5% | Sau 1 tuần |
A9) Glossary
| Thuật ngữ (VI) | Thuật ngữ (EN) | Định nghĩa | Phân biệt với |
|---|---|---|---|
| Consent | Consent | Khách đồng ý cho Diva xử lý dữ liệu cá nhân. Ghi nhận 1 lần, hỏi lại khi version tăng | ≠ Đề xuất cập nhật (update_info) |
| Đề xuất cập nhật | Profile Update Prompt | Popup mời khách bổ sung thông tin (birthday, occupation, province). Có thể bỏ qua, hỏi lại tối đa 3 lần | ≠ Consent (consent là đồng ý xử lý data) |
| consent_version | Consent Version | Số version của nội dung consent. Tăng khi admin bấm "Lưu & Tăng version" → khách phải consent lại | — |
| update_info_skip_count | Skip Count | Số lần khách bỏ qua popup đề xuất cập nhật. Cap tại max_skip (3) | — |
| app_open_count | App Open Count | Số lần khách mở app (auth success). Dùng tính điều kiện retry popup | — |
| reshow_after_opens | Reshow Interval | Số lần mở app giữa 2 lần hiện popup. Mặc định 4 | — |
RACI
| Deliverable | PO | TL | UI/UX | FE Dev (Mobile) | FE Dev (Admin) | BE Dev | QA |
|---|---|---|---|---|---|---|---|
| PRD (file này) | A | C | I | I | I | I | I |
| UI Spec | C | I | R | C | C | I | I |
| Dev Spec | I | A | I | C | C | R | I |
| QA Test Plan | C | I | I | I | I | I | R |