FE: module coupon — mã giảm giá (ADR-0073 đợt 3) #
Phần chung (khởi tạo client, lỗi, danh sách, ngôn ngữ) ở tài liệu FE của Core:
docs/fe/common.md (bản đọc: xforge-documentation, ADR-0102 của xforge-core).
Sửa file này CÙNG PR khi đổi hành vi FE thấy được của module.
CMS / admin (XforgeClient) #
Menu: key coupon.coupons (quyền coupon.read). Bộ route CRUD chuẩn:
listCoupons, getCoupon, createCoupon, updateCoupon (sửa một phần),
replaceCoupon, deleteCoupon (thùng rác; ?force=true xoá hẳn, cần
coupon.hard_delete), restoreCoupon, listCouponTrash, listCouponOptions.
| Field | Ghi chú |
|---|---|
code | chữ, số, _ . -; server lưu CHỮ HOA; duy nhất trong store |
discountType | percent (discountValue là phần trăm, tối đa 100) hoặc fixed (số tiền) |
maxDiscount | trần số tiền giảm, chỉ cho percent |
minSubtotal | tiền hàng tối thiểu (sau CTKM, trước thuế) |
startsAt | endsAt — ISO UTC; null = không giới hạn |
usageLimit | tổng lượt; perCustomerLimit lượt mỗi khách (khách phải đăng nhập); null = không |
customerId | mã riêng của một khách (phải là khách của store) |
productIds | mã chỉ giảm các sản phẩm này (giảm theo dòng); rỗng = cả đơn |
Tiền tệ của mã là tiền tệ của store lúc tạo. Response có redeemedCount (lượt
trên đơn chưa huỷ), customerInfo, products: [{ productId, productInfo }].
Luật chéo field sai (phần trăm trên 100, maxDiscount cho fixed, endsAt
trước startsAt) → 400 validation_failed (rule: check).
Gỡ khách khỏi store (unlinkCustomer) hoặc xoá khách thì mã riêng của khách đó
ở store tự tắt (active: false); customerInfo của mã riêng là null khi khách
không còn là khách của store (customerId vẫn giữ). Bật lại mã riêng cần khách
vẫn là khách của store (khách đăng nhập lại ở store là được), không thì 400
validation_failed ở customerId.
Storefront #
Không có route riêng: khách nhập mã vào giỏ (setMyCartCodes, trang module
cart). Mỗi đơn MỘT mã giảm giá. Giỏ và chốt đơn có khoản giảm trong
adjustments (source: "coupon", code, scope order hoặc line).
- Nhập mã không dùng được → 409
coupon_not_applicable, mã không được lưu (giỏ chưa có hàng thì chưa nhập mã được — xem trangcart).details.reason:inactive,not_started,expired,usage_limit_reached,per_customer_limit_reached,not_assigned,sign_in_required,min_subtotal_not_met,no_eligible_items,currency_mismatch— hiện câu theo lý do (error.messageđã dịch sẵn). - Hai mã giảm giá → 409
coupon_only_one(details.code= mã cần bỏ). - Mã đã lưu mà về sau không dùng được (hết hạn, hết lượt, bị tắt) nằm trong
rejectedCodescủa giỏ và không tính vào giá; chốt đơn khi đó trả 409coupon_not_applicable— bỏ mã (setMyCartCodes) rồi chốt lại. - Đơn huỷ thì lượt dùng được trả lại.
Màn hình dựng sẵn #
listMenu() → mục coupon.coupons có screen (type: "crud"): CMS chung vẽ
được từ getMenuScreen({ params: { key: "coupon.coupons" } }) mà không cần code
riêng (tài liệu FE của Core: cms.md §3.1). Có: danh sách (mã, tên, kiểu, mức
giảm, khách, hiệu lực, đã dùng, đang dùng; tìm; lọc trạng thái / khách), form
tạo / sửa, xoá, thùng rác + khôi phục.
- Ô "Chỉ cho sản phẩm" (
productIds, chọn nhiều) CHỈ ở form tạo: bản ghi trảproducts(không cóproductIds) nên form sửa không có giá trị ban đầu, chọn ở đó sẽ THAY cả danh sách. Sửa danh sách sản phẩm của mã đã tạo → màn tay (updateCoupon({ body: { productIds } })với danh sách đầy đủ). - "Mức giảm" là số (
%khipercent, tiền khifixed) — màn hình chung không đổi đơn vị theo kiểu; tiền tệ của "Giảm tối đa" / "Đơn tối thiểu" làcurrencycủa mã (lúc tạo: tiền tệ của store). - "Giảm tối đa" chỉ hợp lệ với kiểu
percent: đổi sangfixedmà còn giá trị → 400 ở ô đó (xoá ô trước khi đổi kiểu). - Ô "Chỉ cho sản phẩm" cần
product.read. - Ô "Chỉ cho khách" cần quyền
core.view_customers; thiếu thì ô nhập id thường. Danh sách chọn làlistCustomerstrong phạm vi admin — admin cấp account thấy cả khách của store khác cùng account; chọn khách chưa ghé store hiện tại thì lưu bị từ chối ở ô đó. Màn tay nên gửistore=<mã store hiện tại>(giá trị cố định của mô tả chưa nói được "store hiện tại").