Mở rộng giới hạn số từ khóa (phiên bản release theo giai đoạn)
Tổng quan
| Mục | Nội dung |
|---|---|
| Trạng thái | 🔵 Đề xuất |
| Issue | - |
| Phụ trách | - |
| Phiên bản trước | #1-2 Bản sửa đổi |
Nâng giới hạn từ khóa cho mỗi location từ 8 → 15 từ. Một user sở hữu nhiều cửa hàng nhưng không phải cửa hàng nào cũng tăng đồng loạt, nên thiết kế lấy giới hạn động theo từng cửa hàng (per-location) làm trục chính. Để giảm rủi ro, triển khai theo 3 giai đoạn:
- Phase 1 (Release qua chỉnh sửa DB trực tiếp) — chỉ release phần "cơ chế cấu hình"; việc mở cho từng cửa hàng do ops thực hiện bằng SQL
- Phase 2 (Release màn quản trị) — sales / CS tự thao tác trên UI để cài đặt giới hạn cho từng cửa hàng
- Phase 3 (Workflow self-service: khách hàng gửi yêu cầu — admin duyệt) — khách hàng tự gửi "yêu cầu nâng giới hạn" từ tài khoản của mình, admin duyệt / từ chối
Nội dung đề xuất
Bối cảnh & vấn đề
- Giới hạn từ khóa hiện tại là 8 (hard-code rải rác ở 7 chỗ: xem v2)
- Nhu cầu khách hàng theo dõi từ 10 từ khóa trở lên ngày càng nhiều
- Một user có nhiều cửa hàng (
mappy_users 1:N mappy_gbp_locations) và việc nâng giới hạn tùy theo từng cửa hàng - Bản v2 đã đề xuất "mô hình DB hỗ trợ tính phí cả theo cửa hàng lẫn theo hợp đồng", nhưng nếu gộp cả UI quản trị thì thời gian release kéo dài
- Phía sales muốn "ưu tiên mở 15 từ cho một vài cửa hàng trước"
→ Áp dụng release theo giai đoạn: hạ tầng cấu hình (DB + backend + UI khách hàng) release trong Phase 1, Phase 2 giảm công thao tác bằng UI quản trị
Giải pháp đề xuất
| Phase | Phạm vi | Cách mở cho cửa hàng | Công ước tính |
|---|---|---|---|
| Phase 1 | DB / backend / UI khách hàng / Export / scraping | Ops chạy SQL để INSERT bản ghi giới hạn theo cửa hàng | ~7.5 người-ngày |
| Phase 2 | UI quản trị (chỉnh giới hạn theo cửa hàng) / ActivityLog / Admin API | Sales / CS thao tác trên màn quản trị | ~4.5 người-ngày |
| Phase 3 | UI khách hàng (gửi yêu cầu) / Queue admin / Email / Bảng request | Khách hàng request → admin duyệt → tự apply | ~6.0 người-ngày |
| Tổng cộng | 18.0 người-ngày |
Điểm khác với v2:
- Phase 1 chỉ vận hành
scope_type='location'(scope contract giữ để dùng cho tương lai) - Phạm vi release tách thành 2 giai đoạn (v2 dự định release một lần)
- Toàn bộ cải tạo UI khách hàng / Export / scraping gói trong Phase 1 (Phase 2 chỉ làm UI quản trị)
Mô hình dữ liệu
Dùng đúng schema của keyword-limit-v2, giữ tính hai giá trị của scope_type để sau này còn mở rộng được sang tính phí theo hợp đồng.
mappy_keyword_limit_settings
├─ id BIGINT PK
├─ scope_type ENUM('contract','location') -- Phase 1 chỉ dùng 'location'
├─ scope_id BIGINT -- gbp_location_id (hoặc mappy_user_id nếu contract)
├─ system_id BIGINT -- Định danh tenant đã có
├─ max_keywords TINYINT UNSIGNED -- 8〜15
├─ effective_from DATE -- Ngày bắt đầu hiệu lực
├─ effective_to DATE NULLABLE -- NULL = không có hạn cuối
├─ memo VARCHAR(255) NULLABLE -- Ghi chú của sales (cơ sở tính phí)
├─ created_at / updated_at
└─ UNIQUE KEY (scope_type, scope_id, effective_from)Logic tra cứu giới hạn
resolve_max_keywords(gbp_location_id, user_id, today)
= bản ghi hiệu lực scope=location.max_keywords
?? bản ghi hiệu lực scope=contract.max_keywords
?? plan-default (permissions.js: user-standard-plan=8, free=1, meo-booster=8)"Hiệu lực" = effective_from <= today AND (effective_to IS NULL OR today <= effective_to)
Phase 1 — Release qua chỉnh sửa DB trực tiếp
Phạm vi
- [x] Tạo bảng
mappy_keyword_limit_settings+ migration Laravel - [x] Thêm method
resolveMaxKeywords()cho modelKeyword/GbpLocation - [x] Thay validation FormRequest sang dùng logic resolve mới
- [x] Bỏ hard-code
MAX_KEYWORDS_COUNT = 8ở UI khách hàng (KeywordSettings.vue), lấy động từ API - [x]
GbpLocation.php:209.fillKeys(1, 8)chuyển sang dùng giới hạn động - [x]
ExportController.php:2291, 2431đổi header CSV/Excel sang dynamic - [x] PlacesAPI
export_data[:8]→[:resolved_max] - [x] Sửa 3 chỗ hard-code phía scraping (repo scraping)
- [x] Việc mở giới hạn cho từng cửa hàng do ops thực hiện bằng SQL vận hành (mục dưới)
Migration Laravel
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('mappy_keyword_limit_settings', function (Blueprint $table) {
$table->id();
$table->enum('scope_type', ['contract', 'location'])
->comment('Phạm vi áp dụng: location=theo cửa hàng / contract=theo hợp đồng user');
$table->unsignedBigInteger('scope_id')
->comment('scope_type=location → gbp_locations.id, contract → mappy_users.id');
$table->unsignedBigInteger('system_id')->comment('Định danh tenant');
$table->unsignedTinyInteger('max_keywords')->comment('8〜15');
$table->date('effective_from');
$table->date('effective_to')->nullable();
$table->string('memo', 255)->nullable()->comment('Ghi chú sales / cơ sở tính phí');
$table->timestamps();
$table->unique(
['scope_type', 'scope_id', 'effective_from'],
'uk_scope_effective'
);
$table->index(['scope_type', 'scope_id'], 'idx_scope');
$table->index('system_id', 'idx_system');
});
}
public function down(): void
{
Schema::dropIfExists('mappy_keyword_limit_settings');
}
};Template SQL cho ops
Dự kiến đặt tại docs/operations/keyword-limit-release.sql.
-- =========================================================
-- Phase 1 - SQL vận hành: mở 15 từ khóa cho cửa hàng chỉ định
-- =========================================================
-- Kiểm tra trước khi chạy:
-- 1. Đã nhận danh sách gbp_location_id từ marketing / CS (có văn bản)
-- 2. system_id tra từ mappy_gbp_locations.system_id
-- 3. Người thực thi · cửa hàng · lý do tính phí đã post lên Slack #releases
-- 4. BẮT BUỘC kiểm thử trước trên STAGING
-- =========================================================
START TRANSACTION;
-- Xác nhận cửa hàng đối tượng
SELECT id AS gbp_location_id, system_id, name
FROM mappy_gbp_locations
WHERE id IN (/* điền location_id ở đây */);
-- INSERT (thêm dòng nếu xử lý nhiều cửa hàng cùng lúc)
INSERT INTO mappy_keyword_limit_settings
(scope_type, scope_id, system_id, max_keywords,
effective_from, effective_to, memo,
created_at, updated_at)
VALUES
('location',
/* gbp_location_id */,
/* system_id */,
15,
CURDATE(),
NULL,
'Sales: dự án ◯◯ / Ticket: TICKET-XXXX',
NOW(), NOW());
-- Xác nhận kết quả
SELECT s.scope_type, s.scope_id, l.name, s.max_keywords,
s.effective_from, s.effective_to, s.memo
FROM mappy_keyword_limit_settings s
JOIN mappy_gbp_locations l ON l.id = s.scope_id
WHERE s.scope_type = 'location'
AND s.scope_id IN (/* điền location_id ở đây */);
-- Nếu OK thì:
COMMIT;
-- Nếu rollback:
-- ROLLBACK;Quy trình rollout
- Kiểm tra trên STAGING: mở 1 cửa hàng lên 15, chạy toàn bộ đường: UI khách hàng / Export / scraping / PlacesAPI
- Release production: migration → deploy code (xoá hard-code đồng thời)
- Mở cửa hàng: dùng danh sách
location_idCS gửi để chạy SQL vận hành → COMMIT - Xác nhận hoạt động: đăng nhập tài khoản khách hàng của cửa hàng đối tượng, mở màn KeywordSettings, kiểm tra slot 9〜15 đã có thể nhập
Công của Phase 1
| # | Hạng mục | Công (người-ngày) | Phụ trách |
|---|---|---|---|
| 1 | Xác nhận yêu cầu & tạo tài liệu | 1.0 | Thiết kế |
| 2 | Thiết kế DB / API / logic resolve | 1.0 | Thiết kế |
| 3 | migration + model + FormRequest | 1.0 | Sản xuất |
| 4 | Cải tạo UI khách hàng (lấy qua API + ô nhập động) | 1.0 | Sản xuất |
| 5 | Cải tạo PlacesAPI export_data + scraping | 1.0 | Sản xuất |
| 6 | CSV header + hiển thị ranking động | 1.0 | Sản xuất |
| 7 | Kiểm thử tích hợp (PlacesAPI / Export / UI khách hàng) | 1.0 | Sản xuất |
| 8 | Deploy + chuẩn bị template SQL + lần mở đầu tiên | 0.5 | Sản xuất |
| Phase 1 tổng cộng | 7.5 |
Phase 2 — Tự thao tác qua màn quản trị
Phạm vi
- Thêm màn cài đặt giới hạn theo cửa hàng dưới admin guard hiện có (
auth:admin_api) - Thêm tab thứ 3 "Giới hạn từ khóa" vào page
/admin/users/:userId/details(MappyUserDetail.vue) — không tạo route mới, không thêm sidebar - Mở rộng tối thiểu API
/api/mappy/admin/users/{user}/locations-and-groupsđã có sẵn để response thêmmax_keywords(resolved + override) - Bảng danh sách cửa hàng của user → thêm cột
max_keywords(input) + nút lưu - Khi lưu, ghi ActivityLog (ai · khi nào · cửa hàng nào · đổi sang bao nhiêu từ) làm cơ sở tính phí
- Validation:
8 <= max_keywords <= 15 - Phản ánh ngay sang UI khách hàng (invalidate cache)
Hành vi màn quản trị
Thiết kế API
| Method | Path | Mục đích |
|---|---|---|
| GET | /api/mappy/admin/users/{user}/locations-and-groups | API đã có. Mở rộng tối thiểu để include max_keywords (resolved + override) |
| PATCH | /api/mappy/admin/locations/{gbp_location}/keyword-limit | Upsert giới hạn theo cửa hàng (body: { max_keywords, memo }) — Mới |
| DELETE | /api/mappy/admin/locations/{gbp_location}/keyword-limit | Bỏ override cho cửa hàng (trở về plan default) — Mới |
Công của Phase 2
| # | Hạng mục | Công (người-ngày) | Phụ trách |
|---|---|---|---|
| 1 | Thiết kế UI (wireframe + mock admin) | 0.5 | Thiết kế |
| 2 | Cài đặt Admin API (GET / PATCH / DELETE) | 1.0 | Sản xuất |
| 3 | UI màn quản trị (thêm tab vào UserDetail + UserKeywordLimitTab.vue mới) | 1.5 | Sản xuất |
| 4 | Tích hợp ActivityLog + cache invalidate | 0.5 | Sản xuất |
| 5 | Kiểm thử tích hợp + deploy | 1.0 | Sản xuất |
| Phase 2 tổng cộng | 4.5 |
Phase 3 — Workflow self-service request
Sau khi Phase 2 release, khách hàng có thể tự gửi "yêu cầu nâng giới hạn" từ chính tài khoản của họ. Mô hình admin tiếp nhận thủ công của Phase 2 sẽ tiến hóa thành workflow xuất phát từ khách hàng.
Phạm vi
- Bảng request mới
mappy_keyword_limit_requests - UI khách hàng (
KeywordSettings.vue): thêm nút "Yêu cầu nâng giới hạn" + modal nhập yêu cầu - UI khách hàng: badge trạng thái (申請中 / 却下) ngay trong KeywordSettings
- Khách hàng được hủy request khi đang ở trạng thái pending
- Admin có màn queue
/admin/keyword-limit-requestsmới (thêm 1 mục sidebar "申請承認") - Khi approve: tự INSERT vào
mappy_keyword_limit_settings+ ghi ActivityLog + gửi email cho khách hàng - Khi reject: lưu review_note + gửi email cho khách hàng
- Email gửi tới danh sách trong
NotificationSettingscủa user
Mô hình dữ liệu (Phase 3 thêm)
mappy_keyword_limit_requests
├─ id BIGINT PK
├─ mappy_user_id BIGINT -- Người gửi
├─ gbp_location_id BIGINT -- Cửa hàng đối tượng
├─ current_max TINYINT -- Giá trị resolved tại thời điểm submit (lịch sử)
├─ requested_max TINYINT (9〜15)
├─ reason VARCHAR(500) -- Lý do user nhập
├─ status ENUM('pending','approved','rejected','cancelled')
├─ requested_at DATETIME
├─ reviewed_by BIGINT NULL -- ID admin đã duyệt
├─ reviewed_at DATETIME NULL
├─ review_note VARCHAR(500) NULL -- Memo của admin (lý do reject…)
├─ applied_setting_id BIGINT NULL -- FK tới settings được tạo khi approve
└─ created_at / updated_at
INDEX (mappy_user_id, status) -- User xem request của mình
INDEX (status, requested_at) -- Queue admin (pending cũ trước)State machine
Flow gửi — duyệt đầy đủ
Thiết kế API (Phase 3 thêm)
| Method | Path | Mục đích | Auth |
|---|---|---|---|
| POST | /api/mappy/keyword-limit-requests | Khách hàng gửi request | auth:api (khách hàng) |
| GET | /api/mappy/keyword-limit-requests | Khách hàng xem request của mình | auth:api (khách hàng) |
| DELETE | /api/mappy/keyword-limit-requests/{id} | Khách hàng hủy request pending | auth:api (khách hàng) |
| GET | /api/mappy/admin/keyword-limit-requests | Admin lấy queue (filter status) | auth:admin_api |
| PATCH | /api/mappy/admin/keyword-limit-requests/{id}/approve | Duyệt | auth:admin_api |
| PATCH | /api/mappy/admin/keyword-limit-requests/{id}/reject | Từ chối | auth:admin_api |
UI khách hàng cần sửa
| Vị trí | Sửa gì |
|---|---|
Header KeywordSettings.vue | Cạnh hiển thị resolved max → thêm badge trạng thái request |
| Dưới ô nhập | Nút "Yêu cầu nâng giới hạn" (chỉ hiển thị khi resolved max < 15) |
KeywordLimitRequestModal.vue ★ Mới | Form (slider requested_max / textarea lý do / nút gửi) |
| Khi đang pending | Slot 9 〜 requested_max hiển thị xám "申請中" |
Màn quản trị (Phase 3 mới)
Thêm mục sidebar
resources/js/mappy/_navigationAdmin.js chỉ thêm 1 mục:
{ text: '申請承認', value: 'KeywordLimitRequests', icon: '/images/...', badge: 'pending_count' }badge: 'pending_count' hiển thị số request chưa duyệt (đỏ) — dùng pattern có sẵn.
Màn queue /admin/keyword-limit-requests
| Thành phần | Nội dung |
|---|---|
| Filter | Status (pending / approved / rejected / all) / Khoảng thời gian / Tìm user |
| Bảng | Thời điểm submit / loginId / tên shop / giá trị hiện tại / giá trị yêu cầu / lý do / status |
| Click dòng | Sidepane mở chi tiết → nút "Duyệt" / "Từ chối (lý do bắt buộc)" |
| Bulk action | Checkbox chọn → duyệt / từ chối hàng loạt (cùng review_note) |
Email notification
Tạo 2 Laravel Mailable:
| Class | Trigger | Subject | Nội dung chính |
|---|---|---|---|
KeywordLimitRequestApprovedMail | approve API | 【MAPPY】Yêu cầu nâng giới hạn từ khóa đã được duyệt | Nội dung request + ngày duyệt + giới hạn mới |
KeywordLimitRequestRejectedMail | reject API | 【MAPPY】Về yêu cầu nâng giới hạn từ khóa | Nội dung request + lý do từ chối (review_note) |
Gửi đến: toàn bộ mailAddresses đã set trong NotificationSettings (ít nhất 1 email chính khi ký hợp đồng).
Công của Phase 3
| # | Hạng mục | Công (người-ngày) | Phụ trách |
|---|---|---|---|
| 1 | Xác nhận yêu cầu + tài liệu thiết kế (gồm state machine) | 1.0 | Thiết kế |
| 2 | migration (bảng requests) + Model + state machine | 0.5 | Sản xuất |
| 3 | 3 API khách hàng (POST / GET / DELETE) | 0.5 | Sản xuất |
| 4 | 3 API admin (GET / approve / reject) + auto INSERT | 0.5 | Sản xuất |
| 5 | 2 Email Mailable + template | 0.5 | Sản xuất |
| 6 | UI khách hàng (modal + badge + slot 申請中) | 1.0 | Sản xuất |
| 7 | Màn queue admin + thêm sidebar | 1.0 | Sản xuất |
| 8 | Kiểm thử tích hợp (email / state / auto apply) | 0.5 | Sản xuất |
| 9 | Deploy + xác nhận | 0.5 | Sản xuất |
| Phase 3 tổng cộng | 6.0 |
Mock màn hình
Phía khách hàng (Phase 1 sẽ cải tạo)
キーワード管理
変更内容
| 項目 | 現行 | 変更後 |
|---|---|---|
| キーワード上限 | 8件 | 無制限(推奨10件以上) |
| スクレイピング | 8件固定 | 登録数に応じて動的 |
| 表示 | 固定レイアウト | スクロール対応 |
Phía quản trị (Phase 2 sẽ làm mới)
Thiết kế UI chi tiết xem Thiết kế màn hình: Quản trị — Giới hạn từ khóa theo cửa hàng.
アカウント情報
| 店舗名 | プラン値 | 現在の上限 | 新しい上限 | メモ(課金根拠) | 有効期間 | 操作 |
|---|---|---|---|---|---|---|
渋谷本店 location_id: 8801 | 8 | 15 | 2026-06-01 〜 無期限 | |||
新宿西口店 location_id: 8802 | 8 | 8 | — 〜 無期限 | |||
池袋東口店 location_id: 8803 | 8 | 12 | 2026-05-15 〜 2026-12-31 | |||
横浜西口店 location_id: 8804 | 8 | 8 | — 〜 無期限 | |||
川崎駅前店 location_id: 8805 | 8 | 10 | 2026-06-01 〜 無期限 |
呼び出し API(参考)
| Method | Path | 役割 |
|---|---|---|
GET | /api/mappy/admin/users/{user}/locations-and-groups | 店舗一覧取得(既存 API を拡張:resolved + override を含める) |
PATCH | /api/mappy/admin/locations/{loc}/keyword-limit | 店舗別上限を upsert(新設) |
DELETE | /api/mappy/admin/locations/{loc}/keyword-limit | override 解除(新設) |
Flow request phía khách hàng (Phase 3 mới)
キーワード管理
Queue duyệt phía admin (Phase 3 mới)
キーワード上限引き上げ申請
| 申請日時 | 申請者 | 対象店舗 | 現在 | 希望 | 理由 | ステータス | ||
|---|---|---|---|---|---|---|---|---|
| 2026-05-29 10:32 | 田中 太郎 tanaka_taro_001 | 渋谷本店 | 8 | 12 | 新メニュー導入により追加で 4 キーワードを追跡したい | 未承認 | ||
| 2026-05-29 09:18 | 佐藤 花子 sato_hanako_023 | 横浜駅前店 | 8 | 15 | 競合分析強化のため上限まで申請 | 未承認 | ||
| 2026-05-28 16:45 | 鈴木 一郎 suzuki_ichi_007 | 池袋東口店 | 12 | 15 | 冬季キャンペーン用に+3 | 未承認 |
Các vấn đề thiết kế (kế thừa từ v2)
Ảnh hưởng phía PlacesAPI
- Mỗi location tăng 8 → 15 từ → lượng gọi API tăng +87%
- Cấu hình 3 GCP project key hiện tại (QPM 1800) → QPM cần là 940, còn dư (không cần thêm project)
- Phí Basic SKU = miễn phí
- Thời gian: 200 giây → ~375 giây (batch ban đêm vẫn dư)
- Trong Phase 1 số cửa hàng đối tượng còn ít, tải tăng rất hạn chế
Ảnh hưởng phía scraping
- Chỉ còn ~33 cửa hàng → toàn bộ chuyển sang 15 từ cũng chỉ tăng ~39 phút
- Hard-code phải sửa chỉ có 3 chỗ
Phương châm tích hợp hệ thống tính phí
mappy nhận thông báo thanh toán từ nền tảng bên ngoài (KUCHIKOMIONE / G-COR / pipit), bản thân không có engine tính phí. Case này giới hạn đến "có thể cài đặt thủ công trên màn quản trị", còn việc thêm SKU vào master sản phẩm và mở rộng payload thông báo phía nền tảng tính phí tách sang case sau.
Ước tính công (tiền đề có AI)
Cơ cấu nhân sự
| Vai trò | Số người | Nội dung phụ trách |
|---|---|---|
| Thiết kế | 1 người | Xác nhận yêu cầu → Chỉ thị AI tạo tài liệu → Review → Chỉ thị sản xuất |
| Sản xuất | 1 người | Chỉ thị AI tạo theo ISSUE → Code review → Kiểm thử → Deploy |
Tổng
| Phase | Công |
|---|---|
| Phase 1 (Release qua DB trực tiếp) | 7.5 người-ngày |
| Phase 2 (Release màn quản trị) | 4.5 người-ngày |
| Phase 3 (Workflow self-service) | 6.0 người-ngày |
| Tổng cộng | 18.0 người-ngày |
Điều kiện tiên quyết & ràng buộc
- Giới hạn cứng = 15, tối thiểu = 8 (giữ nguyên cho hợp đồng hiện hữu)
- Ngay sau release Phase 1, ops mở bằng SQL; Phase 2 sẽ chuyển sang UI
- Tích hợp nền tảng tính phí nằm ngoài phạm vi case này
- 3 key PlacesAPI hiện tại đủ hấp thụ lượng request tăng khi nâng lên 15 từ (không cần thêm project)
Lịch trình
| タスク | 担当 | 日数 | 6/1 | 6/2 | 6/3 | 6/4 | 6/5 | 6/6 | 6/7 | 6/8 | 6/9 | 6/10 | 6/11 | 6/12 | 6/13 | 6/14 | 6/15 | 6/16 | 6/17 | 6/18 | 6/19 | 6/20 | 6/21 | 6/22 | 6/23 | 6/24 | 6/25 | 6/26 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 月 | 火 | 水 | 木 | 金 | 土 | 日 | 月 | 火 | 水 | 木 | 金 | 土 | 日 | 月 | 火 | 水 | 木 | 金 | 土 | 日 | 月 | 火 | 水 | 木 | 金 | |||
| P1 Yêu cầu & tài liệu | Thiết kế | 1d | ||||||||||||||||||||||||||
| P1 Thiết kế DB · API | Thiết kế | 1d | ||||||||||||||||||||||||||
| P1 migration + model | Sản xuất | 1d | ||||||||||||||||||||||||||
| P1 Cải tạo UI khách hàng | Sản xuất | 1d | ||||||||||||||||||||||||||
| P1 PlacesAPI/scraping | Sản xuất | 1d | ||||||||||||||||||||||||||
| P1 CSV/ranking động | Sản xuất | 1d | ||||||||||||||||||||||||||
| P1 Kiểm thử tích hợp | Sản xuất | 1d | ||||||||||||||||||||||||||
| P1 Deploy + mở lần đầu | Sản xuất | 1d | ||||||||||||||||||||||||||
| P2 Thiết kế UI | Thiết kế | 1d | ||||||||||||||||||||||||||
| P2 Admin API | Sản xuất | 1d | ||||||||||||||||||||||||||
| P2 UI màn quản trị | Sản xuất | 2d | ||||||||||||||||||||||||||
| P2 ActivityLog + tích hợp + deploy | Sản xuất | 1d | ||||||||||||||||||||||||||
| P3 Xác nhận yêu cầu + thiết kế | Thiết kế | 1d | ||||||||||||||||||||||||||
| P3 Bảng request + Model | Sản xuất | 1d | ||||||||||||||||||||||||||
| P3 API (khách + admin) + Mailable | Sản xuất | 2d | ||||||||||||||||||||||||||
| P3 UI khách hàng (modal + badge) | Sản xuất | 1d | ||||||||||||||||||||||||||
| P3 Màn queue duyệt admin | Sản xuất | 1d | ||||||||||||||||||||||||||
| P3 Kiểm thử tích hợp + deploy | Sản xuất | 1d |