Bỏ qua nội dung

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ỉ role admin, 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 plans và các field giá: gửi plans thì dùng trực tiếp; chỉ gửi field giá (không gửi plans) 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 đang ACTIVE vẫn dùng tới expired_at.
  • Trả về SubscriptionPackageResponse sau 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 (code khô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

  • 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.
  • 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.
  • admin, tôi muốn cập nhật bộ plans (đổi giá theo chu kỳ, bonus months) trong một lần PATCH.
  • 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
    end

Acceptance Criteria

  • AC-1: PATCH /admin/subscriptions/packages/{id} yêu cầu role admin.
  • AC-2: Body theo UpdateSubscriptionPackageRequest (partial); phải có ít nhất 1 field thay đổi, nếu không → 409 REQUIRED_FIELD_MISSING.
  • AC-3: Gói không tồn tại theo id → 404 ResourceNotFoundException.
  • AC-4: Chỉ các field được gửi bị ghi đè; field không gửi giữ nguyên giá trị cũ.
  • AC-5: name nếu gửi không được blank; monthlyPriceVnd / monthlyPriceCredits / annualPriceCredits / annualBonusMonths nếu gửi phải >= 0.
  • AC-6: affiliateRate nếu gửi thuộc [0, 1].
  • AC-7: Nếu gửi plans → normalize và dùng trực tiếp (validate cycleMonths > 0, giá/bonus >= 0, không entry null).
  • AC-8: Nếu không gửi plans như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: status nếu gửi nhận ACTIVE / 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ầu package.status = ACTIVE).
  • AC-13: Subscriber đang ACTIVE trên gói INACTIVE vẫn dùng đầy đủ quyền lợi tới expired_at — entitlement không kiểm package.status.
  • AC-14: Gói INACTIVE không auto-renew dù isRenewal = true; tới hạn chuyển EXPIRED và phát notification PACKAGE_EXPIRED.
  • AC-10: Sửa thành công trả SubscriptionPackageResponse phản ánh giá trị mới.
  • AC-11: Thay đổi catalog không hồi tố lên UserPackage đã mua (đã snapshot purchasePriceCredits).

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.
  • code là identifier immutable — không nằm trong payload update.
  • Quan hệ plans vs 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ầu package.status = ACTIVE).
  • Subscriber đang ACTIVE trên gói INACTIVE vẫn dùng đầy đủ quyền lợi tới expired_at (entitlement không kiểm package.status).
  • Gói INACTIVE không auto-renew kể cả khi UserPackage.isRenewal = true; tới hạn chuyển EXPIRED + notification PACKAGE_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 updatePackage thự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 set INACTIVE tự 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 trong plans[] 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 đang ACTIVE vẫn hưởng đầy đủ quyền lợi tới expired_at, sau đó chuyển EXPIRED + notification PACKAGE_EXPIRED.

Liên quan