LLM Wiki

FEA_005 — Sản phẩm, Giỏ hàng & Đơn hàng

Tổng quan

Module products (NestJS) gộp 3 domain: Sản phẩm (CRUD), Giỏ hàng (thêm/xóa/cập nhật), và Đơn hàng (đặt hàng, lịch sử, cập nhật thanh toán). Toàn bộ controller dùng JwtAuthGuard + PermissionGuard; riêng GET /productsGET /products/:id@Public().

  • Controller: src/products/products.controller.ts
  • Service: src/products/products.service.ts
  • Phụ thuộc (từ constructor ProductsService:18): PrismaService, Bull queue email (@InjectQueue('email')), MailService.

Luồng gọi (Call Graph — CodeGraph)

Trích từ CodeGraph index của laptop-shop (codegraph_callees), là quan hệ gọi thực tế trong code.

  • handlePlaceOrder (đặt hàng) gọi:
    • prisma.product.create/update (qua transaction) — tạo order, trừ kho.
    • MailService.sendOrderConfirmationEmail (src/mail/services/mail.service.ts:109) — đường fallback gửi email đồng bộ khi Bull queue lỗi.
  • updatePaymentMethod (chọn thanh toán) gọi:
    • prisma.order.update — cập nhật trạng thái + thông tin người nhận.
    • UpdatePaymentMethodDto — validate paymentMethod ∈ {COD, BANKING}, receiverMail là email.

Việc gửi email còn đi qua Bull queue email (không phải lời gọi hàm trực tiếp nên CodeGraph không vẽ cạnh) — consumer xử lý ở module mail/cronjob.

Danh sách APIs

Prefix toàn cục: /api/v1. Quyền yêu cầu trong ngoặc.

Sản phẩm

MethodEndpointQuyềnMô tả
POST/api/v1/productsPRODUCTS_CREATETạo sản phẩm
GET/api/v1/products?page=PublicDanh sách (phân trang)
GET/api/v1/products/:idPublicChi tiết sản phẩm
PATCH/api/v1/products/:idPRODUCTS_UPDATECập nhật sản phẩm
DELETE/api/v1/products/:idPRODUCTS_DELETEXóa mềm sản phẩm

Giỏ hàng

MethodEndpointQuyềnMô tả
GET/api/v1/products/cartPRODUCTS_READLấy giỏ hàng + tổng tiền của user
POST/api/v1/products/:id/add-to-cartPRODUCTS_READThêm sản phẩm vào giỏ
POST/api/v1/products/delete-product-in-cart/:idPRODUCTS_READXóa 1 dòng giỏ (theo cartDetailId)
POST/api/v1/products/update-cart-before-checkoutPRODUCTS_READCập nhật số lượng trước khi checkout

Đơn hàng

MethodEndpointQuyềnMô tả
POST/api/v1/products/place-orderPRODUCTS_READĐặt hàng từ giỏ (transaction)
GET/api/v1/products/orders-historyPRODUCTS_READLịch sử đơn của user
GET/api/v1/products/order/:idPRODUCTS_READChi tiết 1 đơn (của chính user)
PATCH/api/v1/products/order/:id/payment-methodPRODUCTS_READChọn phương thức TT + thông tin nhận

Payload mẫu

POST /api/v1/products (CreateProductDto)

{
  "name": "Laptop Dell XPS 13",
  "price": 25000000,
  "image": "https://example.com/xps13.jpg",
  "detailDesc": "Mô tả chi tiết...",
  "shortDesc": "Mô tả ngắn",
  "quantity": 10,
  "sold": 0,
  "factory": "Dell",
  "target": "Doanh nhân"
}
  • Validation: name/detailDesc/shortDesc/factory/target bắt buộc; price ≥ 1, quantity ≥ 0; image phải là URL hợp lệ (optional). UpdateProductDto = PartialType (mọi field optional).

POST /api/v1/products/:id/add-to-cart

{ "quantity": 2 }

POST /api/v1/products/update-cart-before-checkout

{ "currentCartDetail": [{ "id": "12", "quantity": "3" }] }

POST /api/v1/products/place-order

{ "totalPrice": 50000000 }

PATCH /api/v1/products/order/:id/payment-method (UpdatePaymentMethodDto)

{
  "paymentMethod": "COD",
  "receiverName": "Nguyễn Văn A",
  "receiverAddress": "123 Đường ABC",
  "receiverPhone": "0900000000",
  "receiverMail": "a@example.com"
}
  • paymentMethod chỉ nhận "COD" hoặc "BANKING"; receiverMail phải là email hợp lệ.

Business Logic (Quy tắc Nghiệp vụ)

Tạo sản phẩm (create)

  • Chặn trùng name409 ConflictException nếu đã tồn tại.

Danh sách / Chi tiết (findAll, findOne)

  • findAll: phân trang PAGE_SIZE (src/config/constant), chỉ lấy deletedAt = null; trả { data, currentPage, totalPages, totalProducts }. page ≤ 0 → ép về 1.
  • findOne: chỉ lấy deletedAt = null; không thấy → 404.

Xóa (remove)

  • Soft delete qua prisma.softDelete (set deletedAt), không xóa cứng. Không thấy → 404.

Thêm vào giỏ (addProductToCart)

  • Nếu user chưa có giỏ → tạo Cart mới kèm CartDetail.
  • Nếu đã có giỏincrement cart.sum, rồi upsert CartDetail (cộng dồn quantity nếu sản phẩm đã có trong giỏ).
  • quantity mặc định 1 nếu body rỗng. Giá lưu theo product.price tại thời điểm thêm.
  • ⚠️ Không kiểm tra tồn kho ở bước thêm giỏ (chỉ kiểm tra khi đặt hàng).

Xóa dòng giỏ (deleteProductInCart)

  • Nhận cartDetailId; decrement cart.sum theo quantity của dòng rồi xóa CartDetail.

Đặt hàng (handlePlaceOrder) — luồng phức tạp nhất

Chạy trong prisma.$transaction với isolationLevel: 'Serializable', timeout 10s:

  1. Lấy giỏ + chi tiết; giỏ rỗng → 400 BadRequest.
  2. Lock các sản phẩm bằng SELECT ... FOR UPDATE (raw query) chống race condition.
  3. Re-check tồn kho từng dòng; thiếu hàng → 400 (kèm số lượng available/required).
  4. Tạo Order (status PENDING, paymentMethod = NOT_DEFINED, paymentStatus = PAYMENT_UNPAID) + OrderDetail từ giỏ.
  5. decrement product.quantity, increment product.sold.
  6. Xóa CartDetail + Cart của user.
  7. Sau transaction: đẩy email order-confirmation vào Bull queue (3 attempts, exponential backoff); nếu queue lỗi → fallback gửi đồng bộ qua MailService; lỗi email không làm fail đơn.

Cập nhật phương thức thanh toán (updatePaymentMethod)

  • Chỉ cho đơn status = PENDING của chính user; không thấy → 404.
  • Chỉ cho khi paymentMethod = NOT_DEFINED (chưa set); đã set → 400.
  • Cập nhật → paymentStatus = PAYMENT_SUCCESS, status = CONFIRM, lưu thông tin người nhận. Gửi email xác nhận qua queue (lỗi email không rollback).
  • ⚠️ Map paymentMethod: COD → "Thanh toán khi nhận hàng", còn lại → "Chuyển khoản ngân hàng".

Xem đơn (getOrderById, getOrderHistoryForUser)

  • Phân quyền dữ liệu: luôn lọc userId của người gọi → user chỉ xem được đơn của chính mình.

Database Tương tác

  • Schema: laptop-shop-db
  • Bảng: Product, Cart, CartDetail, Order, OrderDetail
  • Phụ: User (lấy email gửi xác nhận); EmailQueue (qua Bull queue email).

Cần human review

  • ⚠️ Giá trị trạng thái không nhất quán: đặt hàng dùng paymentStatus = PAYMENT_UNPAID còn schema/cập nhật dùng PAYMENT_SUCCESS; status đi qua PENDING → CONFIRM. Nên chuẩn hóa enum.
  • ⚠️ Tồn kho không kiểm ở bước add-to-cart — chỉ kiểm khi place-order. Có thể gây trải nghiệm xấu (thêm được nhưng không đặt được).
  • ⚠️ place-order nhận totalPrice từ client thay vì tính lại từ giỏ ở server → rủi ro sai/giả mạo giá. Cần review.
  • ⚠️ Order.userId không có FK @relation trong Prisma (xem ghi chú ở schema doc).

Source of truth

  • Controller: laptop-shop/src/products/products.controller.ts
  • Service: laptop-shop/src/products/products.service.ts
  • DTO: laptop-shop/src/products/dto/
  • Catalog: 01_Raw/features/Features.json

Liên kết