Skip to content

Hệ thống Khuyến mãi

1. Tổng quan

Thuộc tínhGiá trị
StatusBeta - chỉ CRUD; tính toán giảm giá chưa wire vào pipeline nào
Ownerpricing-team
ServicesPromotionService (CRUD); promotion-compute.service.ts-disable (tính toán, đã tắt)
Controllers / RoutesPromotionController /promotions (POST /aggregate, PATCH /{id}/aggregate), PromotionMethodController /promotion-methods
Depends onschema Promotion, PromotionMethod, Rule (@nx/core)

Quản lý các chiến dịch khuyến mãi thông qua Promotions (chiến dịch + rule điều kiện), PromotionMethods (phương pháp giảm giá với rule nguồn/đích), và Rules (điều kiện ngữ cảnh). Hỗ trợ mã thủ công, khuyến mãi tự động, giảm giá phần trăm/cố định, chiến dịch BuyGet, và các chiến lược đa phân bổ.

⚠️ Tính toán giảm giá chưa được triển khai. Compute service đã tắt; cả v1 và v2 đều không áp dụng khuyến mãi, nên endpoint calculate trả về discount: '0'. CRUD hoạt động đầy đủ. Giảm giá phải được áp dụng bên ngoài cho đến khi calculator hoàn thành.

Endpoint aggregate tạo/cập nhật một promotion cùng method và rules của nó một cách liền mạch trong một transaction (một round-trip). Danh mục service và identity card: xem Pricing Service index. Tham chiếu REST: trực tiếp tại /v1/api/pricing/doc/openapi.json.

2. Mô hình Dữ liệu

2.1. Loại Khuyến mãi

LoạiMô tảTrường hợp dùng
STANDARDGiảm giá phần trăm hoặc cố định đơn giản"Giảm 10% toàn order", "Giảm $5 phí ship"
BUY_GETMua X tặng Y có điều kiện"Mua 2 tặng 1", "Mua $100 giảm 20% phụ kiện"

2.2. Vòng đời Trạng thái Khuyến mãi

Trạng tháiMô tả
DRAFTĐã tạo nhưng chưa active - chỉnh tự do
ACTIVATEDKhuyến mãi đang chạy - áp được vào order
DEACTIVATEDTạm dừng - có thể kích hoạt lại
EXPIREDQuá ngày effectiveTo - chỉ đọc
ARCHIVEDVô hiệu vĩnh viễn - chỉ là bản ghi lịch sử

2.3. Loại Target

LoạiÁp dụng choVí dụ
ITEMSTừng item order khớp rule target"Giảm 10% danh mục giày"
ORDERTổng phụ toàn order"Giảm 15% order trên $100"
SHIPPINGChi phí ship"Miễn phí ship cho order trên $50"

2.4. Chiến lược Phân bổ

Chiến lượcHành viVí dụ
EACHGiảm giá áp lên từng item khớp riêng lẻ"Giảm 10% mỗi item Electronics" → 3 item = 3× giảm
ACROSSGiảm giá phân bổ trên mọi item khớp"Giảm $10 Electronics" → $10 chia cho mọi item khớp
ONCEGiảm giá áp một lần bất kể số lượng"Mua 2 tặng 1" → Áp một lần mỗi tập đủ điều kiện

2.5. Quy ước Ngữ cảnh Rule

Rule được phân biệt bởi metadata.context:

ContextGắn vàoMục đíchVí dụ
eligibilityPromotionAi dùng được khuyến mãi này?Nhóm khách = "VIP"
sourcePromotionMethodPhải mua gì? (chỉ BuyGet)Danh mục sản phẩm = "Laptops" AND quantity >= 2
targetPromotionMethodCái gì được giảm?Danh mục sản phẩm = "Accessories"

3. Luồng Tạo Khuyến mãi

3.1. Khuyến mãi Đơn giản (CRUD)

http
POST /promotions
Content-Type: application/json
Authorization: Bearer <token>

{
  "code": "SUMMER2026",
  "name": { "en": "Summer Sale", "vi": "Khuyến mãi mùa hè" },
  "type": "STANDARD",
  "status": "DRAFT",
  "isAutomatic": false,
  "effectiveFrom": "2026-06-01T00:00:00Z",
  "effectiveTo": "2026-08-31T23:59:59Z",
  "usageLimit": 1000
}

3.2. Promotion Aggregate (Promotion + Method + Rules)

Tạo một promotion hoàn chỉnh cùng method và mọi rule trong một transaction liền mạch duy nhất:

http
POST /promotions/aggregate
Content-Type: application/json
Authorization: Bearer <token>

{
  "promotion": {
    "code": "NEWUSER20",
    "name": { "en": "New User 20% Off" },
    "type": "STANDARD",
    "isAutomatic": false,
    "effectiveFrom": "2026-03-01T00:00:00Z",
    "usageLimit": 500
  },
  "promotionMethod": {
    "targetType": "ORDER",
    "allocation": "ONCE",
    "type": "PERCENTAGE",
    "value": "20",
    "currency": "VND"
  },
  "promotionRules": [
    {
      "attribute": "customer.registeredAt",
      "operator": "GTE",
      "dataType": "TEXT",
      "tValue": "2026-01-01T00:00:00Z",
      "priority": 1,
      "metadata": { "context": "eligibility" }
    }
  ],
  "sourceRules": [],
  "targetRules": [
    {
      "attribute": "order.subtotal",
      "operator": "GTE",
      "dataType": "NUMBER",
      "nValue": "500000",
      "priority": 1,
      "metadata": { "context": "target" }
    }
  ]
}

Đảm bảo transaction: Mọi entity được tạo hoặc không cái nào (rollback khi có lỗi).

4. Áp dụng Khuyến mãi

⚠️ Chưa hoạt động. PromotionComputeService (promotion-compute.service.ts) hiện đang tắt (đổi tên thành .ts-disable). Các ví dụ dưới đây ghi lại hợp đồng API dự định; việc tính giảm giá thực tế chưa active.

4.1. Áp dụng Thủ công

http
POST /promotions/apply
Content-Type: application/json
Authorization: Bearer <token>

{
  "promotionCode": "SUMMER2026",
  "context": {
    "customer": {
      "id": "customer-123",
      "groupId": "vip"
    },
    "order": {
      "subtotal": "1000000"
    },
    "items": [
      {
        "id": "item-1",
        "productId": "prod-1",
        "variantId": "var-1",
        "quantity": 2,
        "price": "500000"
      }
    ]
  }
}

Phản hồi:

json
{
  "isApplicable": true,
  "discountAmount": "100000",
  "itemDiscounts": [
    {
      "itemId": "item-1",
      "discountAmount": "100000",
      "reason": "SUMMER2026 - 10% off"
    }
  ]
}

4.2. Khuyến mãi Tự động

Khuyến mãi tự động (isAutomatic: true) được đánh giá trong checkout mà không cần mã khuyến mãi:

typescript
// During checkout flow
const promotions = await promotionRepository.findAutomaticPromotions({
  effectiveDate: new Date()
});

for (const promotion of promotions) {
  const result = await promotionComputeService.applyPromotion({
    promotion,
    context: orderContext
  });
  
  if (result.isApplicable) {
    // Apply discount to order
  }
}

5. Ví dụ Khuyến mãi BuyGet

Kịch bản: "Mua 2 laptop, tặng 1 chuột miễn phí"

json
{
  "promotion": {
    "code": "LAPTOP2GET1",
    "type": "BUY_GET",
    "name": { "en": "Buy 2 Laptops Get 1 Mouse Free" }
  },
  "promotionMethod": {
    "targetType": "ITEMS",
    "allocation": "ONCE",
    "type": "PERCENTAGE",
    "value": "100",
    "buyGetSourceMinQuantity": 2,
    "buyGetTargetQuantity": 1
  },
  "sourceRules": [
    {
      "attribute": "product.categoryId",
      "operator": "EQ",
      "dataType": "TEXT",
      "tValue": "laptops",
      "metadata": { "context": "source" }
    }
  ],
  "targetRules": [
    {
      "attribute": "product.categoryId",
      "operator": "EQ",
      "dataType": "TEXT",
      "tValue": "accessories",
      "metadata": { "context": "target" }
    },
    {
      "attribute": "product.subcategoryId",
      "operator": "EQ",
      "dataType": "TEXT",
      "tValue": "mouse",
      "metadata": { "context": "target" }
    }
  ]
}

Logic:

  1. Đánh giá source rule: Tìm item có categoryId = "laptops" AND sum(quantity) >= 2
  2. Nếu source pass, đánh giá target rule: Tìm item có categoryId = "accessories" AND subcategoryId = "mouse"
  3. Áp giảm 100% cho chuột giá thấp nhất (tối đa buyGetTargetQuantity = 1)

6. Thao tác Quản lý Khuyến mãi

6.1. Kích hoạt Khuyến mãi

http
PUT /promotions/:id/activate
Authorization: Bearer <token>

Đổi trạng thái từ DRAFTACTIVATED. Kiểm tra promotion có method hợp lệ gắn kèm.

6.2. Vô hiệu hoá Khuyến mãi

http
PUT /promotions/:id/deactivate
Authorization: Bearer <token>

Đổi trạng thái từ ACTIVATEDDEACTIVATED. Promotion có thể kích hoạt lại sau.

6.3. Xoá Khuyến mãi (Cascade)

http
DELETE /promotions/:id
Authorization: Bearer <token>

Soft-delete:

  • Entity Promotion
  • PromotionMethod liên kết
  • Mọi Rule gắn kèm (eligibility, source, target)

Mọi thao tác xoá đều transactional (rollback khi có lỗi).

7. Thao tác Repository

7.1. Promotion Repository

Phương thứcMô tả
findByCode()Tìm promotion active theo mã (loại trừ đã xoá, lọc theo thời gian)
findAutomaticPromotions()Lấy mọi promotion auto-apply active tại ngày cho trước
findActivePromotions()Lấy mọi promotion active trong phạm vi ngày
isUsageLimitReached()Kiểm tra promotion đã đạt giới hạn dùng chưa
incrementUsageCount()Tăng usageCount một cách liền mạch (raw SQL)
updateRulesCount()Cập nhật rulesCount denormalize
findByStatus()Lọc promotion theo trạng thái

7.2. PromotionMethod Repository

Phương thứcMô tả
findByPromotionId()Lấy method cho promotion (quan hệ 1:1)
updateSourceRulesCount()Cập nhật sourceRulesCount denormalize
updateTargetRulesCount()Cập nhật targetRulesCount denormalize

7.3. Rule Repository (Riêng Promotion)

Phương thứcMô tả
findEligibilityRulesByPromotionId()Lấy rule eligibility (metadata.context = 'eligibility')
findSourceRulesByMethodId()Lấy rule source BuyGet (metadata.context = 'source')
findTargetRulesByMethodId()Lấy rule target (metadata.context = 'target')
deleteByPromotionId()Cascade xoá mọi rule eligibility của promotion
deleteByMethodId()Cascade xoá mọi rule method (source + target)

8. Tích hợp với Luồng Định giá

8.1. Trạng thái Hiện tại (Đã tắt)

Trong PricingService.calculate(), discount được hardcode vì compute service đã tắt:

typescript
// Hardcoded - PromotionComputeService is disabled
discount: '0', // TODO: Apply promotions here

8.2. Tích hợp Dự kiến

typescript
// Future implementation
const applicablePromotions = await this.promotionComputeService.applyAutomaticPromotions({
  context: pricingContext
});

const discount = applicablePromotions.reduce((sum, promo) => 
  sum + parseFloat(promo.discountAmount), 0
);

9. Quyết định Thiết kế Chính

Quyết địnhLý do
Hệ thống Rule đa hìnhMột bảng Rule phục vụ Fare, Tax, và Promotion - giảm độ phức tạp schema
Metadata context cho RuleDùng quy ước metadata.context thay vì trường riêng - linh hoạt và mở rộng được
1:1 Promotion:MethodĐơn giản hoá model so với mảng ApplicationMethod của Medusa.js - đủ cho nhu cầu hiện tại
Đếm denormalizerulesCount, sourceRulesCount, targetRulesCount - tránh join cho truy vấn thường gặp
Hỗ trợ i18nMọi text hướng người dùng (name, description) lưu dạng JSONB - sẵn sàng đa ngôn ngữ
Vòng đời trạng thái5 trạng thái (DRAFT → ACTIVATED → DEACTIVATED → EXPIRED → ARCHIVED) - máy trạng thái rõ ràng
Tạo aggregate liền mạchMột transaction cho Promotion + Method + Rules - nhất quán dữ liệu
Compute service tách riêngLogic tính toán cách ly khỏi CRUD - tách bạch mối quan tâm

10. Trạng thái Triển khai

Thành phầnTrạng tháiGhi chú
Schemas✅ Hoàn thànhPromotion, PromotionMethod, Rule (đa hình)
Repositories✅ Hoàn thànhMở rộng với phương thức riêng promotion
Controllers✅ CRUD cơ bảnDùng pattern ControllerFactory
Management Service✅ Hoàn thànhPromotionService với 6 phương thức (CRUD, activate, deactivate, delete)
Compute Service❌ Đã tắtpromotion-compute.service.ts đổi tên thành .ts-disable - cố ý loại khỏi build
Pricing Integration❌ TODOChưa tích hợp vào luồng định giá động
Request Models✅ Hoàn thànhCreate, update, aggregate, apply request
Response Models✅ Hoàn thànhAggregate và application response

11. Tài liệu Liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.