Theo dõi Chi phí
1. Tổng quan
Hệ thống theo dõi chi phí quản lý chi phí biến thể sản phẩm theo thời gian. Mỗi bản ghi chi phí có phạm vi ngày hiệu lực, cho phép theo dõi lịch sử chi phí và lên lịch thay đổi chi phí. Chỉ có một chi phí có thể đang hoạt động (không giới hạn ngày kết thúc) tại một thời điểm cho mỗi biến thể sản phẩm.
Source: src/services/management/cost.service.tsController: src/controllers/cost.controller.tsRoute: /costs
2. Mô hình Dữ liệu
2.1. Trạng thái Phạm vi Ngày
| Trạng thái | effectiveFrom | effectiveTo | Mô tả |
|---|---|---|---|
| Hiện tại | set | null | Chi phí đang hoạt động — không giới hạn ngày kết thúc |
| Lịch sử | set | set | Chi phí quá khứ — phạm vi đóng |
| Đã lên lịch | ngày tương lai | set hoặc null | Thay đổi chi phí trong tương lai |
3. Thao tác Chi phí
3.1. Tạo Chi phí
Kiểm tra không có chi phí hiện có nào trùng lặp với phạm vi ngày mới. Trả về 409 Conflict nếu phát hiện trùng lặp.
POST /costs
Content-Type: application/json
Authorization: Bearer <token>
{
"productVariantId": "variant-123",
"amount": "50000",
"effectiveFrom": "2026-01-01T00:00:00Z",
"note": "New supplier pricing"
}3.2. Cập nhật Chi phí Hiện tại
Thay thế chi phí đang hoạt động hiện tại bằng cách đóng chi phí cũ và tạo mục mới:
PUT /costs/current
Content-Type: application/json
Authorization: Bearer <token>
{
"productVariantId": "variant-123",
"amount": "55000",
"effectiveFrom": "2026-03-01T00:00:00Z",
"note": "Supplier price increase"
}3.3. Lấy Chi phí Hiện tại
GET /costs/product-variant/variant-123/current
Authorization: Bearer <token>Trả về chi phí có effectiveTo = null, hoặc null nếu không có chi phí đang hoạt động.
4. Kiểm tra Trùng lặp Ngày
Trước khi tạo hoặc cập nhật chi phí, hệ thống kiểm tra trùng lặp phạm vi ngày:
- Phạm vi có giới hạn: Kiểm tra nếu bất kỳ phạm vi ngày chi phí hiện có nào giao với phạm vi mới
- Phạm vi mở: Kiểm tra nếu đã tồn tại chi phí mở khác
- Loại trừ khi cập nhật: Loại trừ bản ghi hiện tại khi kiểm tra cho thao tác cập nhật
Phát hiện trùng lặp -> lỗi 409 Conflict.
5. Thao tác Repository
| Phương thức | Mô tả |
|---|---|
findCurrentCost() | Tìm chi phí có effectiveTo = null |
findEffectiveCost() | Tìm chi phí đang hoạt động tại một ngày cho trước |
findCostHistory() | Truy xuất bản ghi chi phí trong phạm vi ngày |
hasOverlappingDates() | Kiểm tra trùng lặp phạm vi ngày (với tùy chọn loại trừ) |
6. API Controller
| Phương thức | Đường dẫn | Mô tả |
|---|---|---|
POST | /costs | Tạo chi phí |
GET | /costs | Danh sách chi phí |
GET | /costs/:id | Lấy chi phí theo ID |
PUT | /costs/:id | Cập nhật chi phí |
DELETE | /costs/:id | Xóa chi phí |
PUT | /costs/current | Thay thế chi phí đang hoạt động hiện tại |
GET | /costs/product-variant/:variantId/current | Lấy chi phí đang hoạt động hiện tại |
7. Ví dụ Thực tế
7.1. Kịch bản 1: Tạo Chi phí Ban đầu
Quy tắc: "Đặt chi phí ban đầu cho biến thể sản phẩm mới là 50,000đ bắt đầu từ 1/1/2026"
Yêu cầu:
POST /costs
Content-Type: application/json
{
"productVariantId": "variant-laptop-001",
"amount": "50000",
"effectiveFrom": "2026-01-01T00:00:00Z",
"effectiveTo": null,
"note": "Giá nhà cung cấp ban đầu từ TechVendor Ltd"
}Phản hồi:
{
"id": "cost-001",
"productVariantId": "variant-laptop-001",
"amount": "50000",
"effectiveFrom": "2026-01-01T00:00:00.000Z",
"effectiveTo": null,
"note": "Giá nhà cung cấp ban đầu từ TechVendor Ltd",
"createdAt": "2026-01-01T08:00:00Z"
}Kết quả: Chi phí này hiện là chi phí hiện tại đang hoạt động (effectiveTo = null).
7.2. Kịch bản 2: Thay thế Chi phí Hiện tại
Quy tắc: "Nhà cung cấp tăng giá lên 55,000đ có hiệu lực từ 1/3/2026"
Bước 1: Lấy Chi phí Hiện tại
GET /costs/product-variant/variant-laptop-001/currentPhản hồi: { "id": "cost-001", "amount": "50000", "effectiveTo": null }
Bước 2: Cập nhật Chi phí Hiện tại
PUT /costs/current
Content-Type: application/json
{
"productVariantId": "variant-laptop-001",
"amount": "55000",
"effectiveFrom": "2026-03-01T00:00:00Z",
"note": "Tăng giá nhà cung cấp - Điều chỉnh Q1"
}Thao tác Backend:
- Đóng chi phí cũ:
effectiveTo = "2026-02-28T23:59:59.999Z"(1ms trước effectiveFrom mới) - Tạo chi phí mới:
amount = "55000",effectiveTo = null
Kết quả: Lịch sử chi phí được giữ lại, chi phí mới có hiệu lực từ 1/3/2026.
7.3. Kịch bản 3: Dòng Thời gian Lịch sử Chi phí
Quy tắc: "Theo dõi thay đổi chi phí qua 6 tháng: Tháng 1 (50k) → Tháng 3 (55k) → Tháng 5 (52k)"
Dòng thời gian:
2026-01-01 2026-03-01 2026-05-01 (tiếp tục)
| | | |
|---- 50,000 -----|---- 55,000 -----|---- 52,000 -----|→
cost-001 cost-002 cost-003Truy vấn Lịch sử Chi phí:
GET /costs?filter={"where":{"productVariantId":"variant-001"}}&order=effectiveFrom ASCPhản hồi:
[
{
"id": "cost-001",
"amount": "50000",
"effectiveFrom": "2026-01-01T00:00:00Z",
"effectiveTo": "2026-02-28T23:59:59.999Z",
"note": "Giá Q1"
},
{
"id": "cost-002",
"amount": "55000",
"effectiveFrom": "2026-03-01T00:00:00Z",
"effectiveTo": "2026-04-30T23:59:59.999Z",
"note": "Tăng giá nhà cung cấp"
},
{
"id": "cost-003",
"amount": "52000",
"effectiveFrom": "2026-05-01T00:00:00Z",
"effectiveTo": null,
"note": "Giảm giá thương lượng"
}
]7.4. Kịch bản 4: Lỗi Trùng lặp Ngày
Quy tắc: "Ngăn chặn tạo bản ghi chi phí trùng lặp"
Trạng thái Hiện tại: Chi phí tồn tại từ 1/1/2026 đến 31/3/2026
Yêu cầu Không hợp lệ (trùng lặp):
POST /costs
{
"productVariantId": "variant-001",
"amount": "48000",
"effectiveFrom": "2026-02-01T00:00:00Z",
"effectiveTo": "2026-04-30T23:59:59Z"
}Phản hồi Lỗi:
{
"statusCode": 409,
"message": "[createCost] Cost dates overlap with existing records"
}Phát hiện Trùng lặp:
| Chi phí Hiện tại | Chi phí Mới | Trùng lặp? |
|---|---|---|
| 1/1 → 31/3 | 1/2 → 30/4 | ✅ CÓ |
| 1/1 → 31/3 | 1/4 → 31/5 | ❌ KHÔNG |
7.5. Kịch bản 5: Lấy Chi phí Hiệu lực tại Ngày Cụ thể
Quy tắc: "Truy xuất chi phí đang hoạt động vào một ngày lịch sử cụ thể"
Truy vấn: Chi phí vào ngày 15/2/2026
const cost = await costService.getEffectiveCost({
productVariantId: 'variant-001',
effectiveDate: new Date('2026-02-15T10:00:00Z')
});Kết quả: Trả về cost-001 với amount 50000 (hoạt động từ 1/1 đến 28/2).
7.6. Kịch bản 6: Chuyển đổi Chi phí với Ranh giới Ngày Chính xác
Quy tắc: "Đảm bảo chuyển đổi chi phí liền mạch không có khoảng trống"
Cập nhật Chi phí Hiện tại:
PUT /costs/current
{
"productVariantId": "variant-001",
"amount": "55000",
"effectiveFrom": "2026-03-01T00:00:00.000Z"
}Tính toán Backend:
const oldEffectiveTo = dayjs('2026-03-01T00:00:00.000Z')
.subtract(1, 'millisecond')
.toISOString();
// Kết quả: "2026-02-28T23:59:59.999Z"Trạng thái Cuối cùng:
[
{
"id": "cost-001",
"effectiveTo": "2026-02-28T23:59:59.999Z"
},
{
"id": "cost-002",
"effectiveFrom": "2026-03-01T00:00:00.000Z",
"effectiveTo": null
}
]Kết quả: Không có khoảng trống, không trùng lặp — liên tục hoàn hảo.
8. Tài liệu Liên quan
- Dịch vụ Định giá — Tổng quan package
- Hệ thống Giá vé — Tính toán giá vé
- Hệ thống Thuế — Tính toán thuế
- Khuyến mãi — Hệ thống khuyến mãi