Admin cập nhật gói package
Feature URL
Module
admin
Status
shipped
Priority
P1
Platforms
fe · be
AC progress
14 / 14
Last reviewed
2026-06-08
Mục tiêu
Cho phép admin sửa từng thuộc tính của một gói có sẵn (đổi tên, mô tả, giá theo cycle, plans, trust score, affiliate rate, hoặc ngừng bán) bằng partial update mà không cần tạo gói mới. Chỉ các field được gửi mới bị ghi đè; các field còn lại giữ nguyên, đảm bảo thao tác sửa an toàn và có thể từng phần.
Phạm vi
Trong phạm vi (In scope):
- API sửa gói
PATCH /admin/subscriptions/packages/{id}, chỉ roleadmin, semantics partial (PATCH). - Body
UpdateSubscriptionPackageRequest:name?,description?,monthlyPriceVnd?,monthlyPriceCredits?,annualPriceCredits?,annualBonusMonths?,plans?,status?,affiliateRate?. - Yêu cầu ít nhất 1 field thay đổi; chỉ áp dụng cho các field được gửi.
- Validate từng field gửi lên (range giá, affiliate rate, name không blank, plans theo cycle).
- Xử lý quan hệ giữa
plansvà các field giá: gửiplansthì dùng trực tiếp; chỉ gửi field giá (không gửiplans) thì rebuild default plans từ giá mới. - Đổi
statusđể ngừng bán (INACTIVE) hoặc bật lại (ACTIVE).INACTIVEẩn gói khỏi catalog công khai, chặn mua mới và chặn auto-renew; subscriber đangACTIVEvẫn dùng tớiexpired_at. - Trả về
SubscriptionPackageResponsesau khi sửa.
Ngoài phạm vi (Out of scope):
- Tạo gói mới — xem admin-package-create.
- Xem / list gói cho admin — xem admin-package-list.
- Xoá gói — không hỗ trợ; dùng
INACTIVE. - Sửa code gói (
codekhông nằm trong payload update — code là immutable identifier). - Sửa các flag không thuộc payload (smartPush, vipFlashDeal, displayPriorityRank, cleanData, avm, phoneMasked, listingLimit, trustScore) — payload update hiện không bao gồm các field này.
- Hồi tố thay đổi lên
UserPackageđã mua.
User Stories
- Là admin, tôi muốn sửa giá hoặc đổi tên một gói mà không phải tạo gói mới hay sửa các thuộc tính khác.
- Là admin, tôi muốn đánh dấu gói
INACTIVEđể ngừng bán (chặn mua mới + chặn auto-renew) mà subscriber đang dùng vẫn được hưởng đầy đủ quyền lợi tới khi hết hạn. - Là admin, tôi muốn cập nhật bộ
plans(đổi giá theo chu kỳ, bonus months) trong một lần PATCH. - Là hệ thống, tôi muốn từ chối request rỗng (không có field nào) để tránh ghi đè vô nghĩa.
Luồng chức năng
sequenceDiagram
actor Admin
participant App as FE Admin
participant BE
Admin->>App: Sửa field cần đổi
App->>BE: PATCH /admin/subscriptions/packages/{id} (UpdateSubscriptionPackageRequest)
BE->>BE: Kiểm tra role admin
BE->>BE: Validate — ít nhất 1 field, từng field hợp lệ
BE->>BE: Tìm Package theo id
alt Không tồn tại
BE-->>App: 404 ResourceNotFound
else Tồn tại
BE->>BE: Apply patch cho field được gửi
BE->>BE: Gửi plans thì dùng plans, chỉ gửi giá thì rebuild default plans
BE-->>App: SubscriptionPackageResponse mới
endAcceptance Criteria
- AC-1:
PATCH /admin/subscriptions/packages/{id}yêu cầu roleadmin. - AC-2: Body theo
UpdateSubscriptionPackageRequest(partial); phải có ít nhất 1 field thay đổi, nếu không → 409REQUIRED_FIELD_MISSING. - AC-3: Gói không tồn tại theo
id→ 404ResourceNotFoundException. - AC-4: Chỉ các field được gửi bị ghi đè; field không gửi giữ nguyên giá trị cũ.
- AC-5:
namenếu gửi không được blank;monthlyPriceVnd/monthlyPriceCredits/annualPriceCredits/annualBonusMonthsnếu gửi phải>= 0. - AC-6:
affiliateRatenếu gửi thuộc[0, 1]. - AC-7: Nếu gửi
plans→ normalize và dùng trực tiếp (validatecycleMonths > 0, giá/bonus>= 0, không entrynull). - AC-8: Nếu không gửi
plansnhưng có gửi field giá (monthlyPriceVnd/monthlyPriceCredits/annualPriceCredits/annualBonusMonths) → rebuild default plans từ giá (giữ giá cũ cho field giá không gửi). - AC-9:
statusnếu gửi nhậnACTIVE/INACTIVE(chỉ 2 giá trị này). - AC-12:
status = INACTIVE(“ngừng bán”): gói bị ẩn khỏi catalog công khai và chặn mua mới (flow purchase yêu cầupackage.status = ACTIVE). - AC-13: Subscriber đang
ACTIVEtrên góiINACTIVEvẫn dùng đầy đủ quyền lợi tớiexpired_at— entitlement không kiểmpackage.status. - AC-14: Gói
INACTIVEkhông auto-renew dùisRenewal = true; tới hạn chuyểnEXPIREDvà phát notificationPACKAGE_EXPIRED. - AC-10: Sửa thành công trả
SubscriptionPackageResponsephản ánh giá trị mới. - AC-11: Thay đổi catalog không hồi tố lên
UserPackageđã mua (đã snapshotpurchasePriceCredits).
Quy tắc nghiệp vụ
- PATCH là partial update: thiếu field = “không đổi”, không phải “set null”.
- Bắt buộc ít nhất một field để tránh request rỗng vô nghĩa.
codelà identifier immutable — không nằm trong payload update.- Quan hệ
plansvs field giá:plansưu tiên cao hơn; nếu chỉ đổi giá lẻ thì backend dựng lại bộ plans mặc định (1 tháng + 12 tháng) từ các giá hiện hành + giá mới. - Đổi
status = INACTIVE(“ngừng bán”): gói biến mất khỏi catalog public và chặn mua mới (purchase yêu cầupackage.status = ACTIVE). - Subscriber đang
ACTIVEtrên góiINACTIVEvẫn dùng đầy đủ quyền lợi tớiexpired_at(entitlement không kiểmpackage.status). - Gói
INACTIVEkhông auto-renew kể cả khiUserPackage.isRenewal = true; tới hạn chuyểnEXPIRED+ notificationPACKAGE_EXPIRED. - Note lệch backend (TODO): mô tả Swagger của endpoint update ghi “Không thể retire gói đang active”, nhưng code
updatePackagethực tế không có guard nào —statusđược set tự do. Cần backend xác nhận: có ý định thêm guard chặn retire-while-active hay không. Hiện tại spec mô tả theo hành vi code thực tế (cho phép setINACTIVEtự do). - Thay đổi giá/quyền lợi chỉ áp cho lần mua/đổi/gia hạn về sau; gói đã mua giữ snapshot cũ.
- Payload update hiện không bao gồm
listingLimit,trustScoreMaxExclusive,packageTypes,addressMasked,aiToolsEnabled,ownerLikelihoodEnabled— muốn đổi các field này cần mở rộng API (chưa trong phạm vi spec hiện tại).
Dữ liệu & Trạng thái
Entity nghiệp vụ:
Package: gói được cập nhật (định nghĩa đầy đủ tại package-view).PackagePlan: entry trongplans[]khi update plans.UpdateSubscriptionPackageRequest: payload partial —name,description,monthlyPriceVnd,monthlyPriceCredits,annualPriceCredits,annualBonusMonths,plans,status,affiliateRate.SubscriptionPackageResponse: view trả về sau khi sửa.
Trạng thái Package (sau update):
ACTIVE— đang bán, hiện trên catalog public, cho mua mới và auto-renew.INACTIVE— ngừng bán: ẩn khỏi catalog public, chặn mua mới, chặn auto-renew; subscriber đangACTIVEvẫn hưởng đầy đủ quyền lợi tớiexpired_at, sau đó chuyểnEXPIRED+ notificationPACKAGE_EXPIRED.
Liên quan
- Phụ thuộc: admin-package-create
- Ảnh hưởng: package-view, package-purchase, package-change