🛠️ Đặc tả Kiến Trúc APIs & Sơ đồ Tuần tự

Giải pháp quản lý hành trình Pallet được vận hành theo mô hình Clean Architecture & Domain-Driven Design (DDD) trong hệ thống Kingston MES, khởi đầu từ màn hình quản lý Nhu Cầu Sản Xuất (Demand Console):

sequenceDiagram
    actor Planner as Kế hoạch (Planner)
    actor Leader as Tổ trưởng sản xuất
    participant API as Web API Server
    participant DB as Database (Postgres)

    Planner->>API: 1. Truy vấn Nhu Cầu Gỗ (GET /api/production-demands)
    API->>DB: Query các Component có Lệnh sản xuất duyệt (WorkOrder)
    API-->>Planner: Trả về danh sách Cụm & Chi tiết chưa tạo phiếu

    Planner->>API: 2. Tạo phiếu hành trình tùy biến (POST /api/pallet-tickets/generate)
    API->>DB: Lưu các PalletTicket & PalletStep phân loại rõ chốt máy và kho đệm
    API-->>Planner: In phiếu thành công

    Leader->>API: 3. Xem danh sách phiếu (GET /api/pallet-tickets?status=ACTIVE)
    API->>DB: Lấy danh sách phiếu hành trình đang lưu thông (WIP)
    API-->>Leader: Hiển thị bảng danh sách các phiếu trong xưởng

    Leader->>API: 4. Xem chi tiết tiến trình phiếu (GET /api/pallet-tickets/{id}/steps)
    API->>DB: Truy vấn lịch sử check-in các chốt công đoạn & kho đệm của phiếu
    API-->>Leader: Trả về quy trình trong xưởng (vertical timeline node) kèm OK/NG, mốc thời gian
                

💾 Sơ Đồ Thực Thể Cơ Sở Dữ Liệu (Prisma ORM)

Trường dữ liệu `isWarehouseStep` (boolean) trong bảng `PalletStep` đóng vai trò phân loại hành vi kiểm soát chéo (Double-check) tại kho đệm:

model Product {
  id          String      @id @default(uuid()) @map("id")
  code        String      @unique @map("code")
  name        String      @map("name")
  createdAt   DateTime    @default(now()) @map("created_at")
  components  Component[]
  workOrders  WorkOrder[]

  @@map("products")
}

model Component {
  id            String         @id @default(uuid()) @map("id")
  productId     String         @map("product_id")
  parentId      String?        @map("parent_id") // NULL: Cụm | NOT NULL: Chi tiết con
  code          String         @map("code")      // Ví dụ: '1' cho Cụm, '1.4' cho Chi tiết
  name          String         @map("name")
  specification String?        @map("specification")
  unit          String         @default("Cái") @map("unit")

  product       Product        @relation(fields: [productId], references: [id], onDelete: Cascade)
  parent        Component?     @relation("BOMHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
  children      Component[]    @relation("BOMHierarchy")
  pallets       PalletTicket[]

  @@unique([productId, code])
  @@map("components")
}

model WorkOrder {
  id          String         @id @default(uuid()) @map("id")
  woNumber    String         @unique @map("wo_number")
  productId   String         @map("product_id")
  quantity    Int            @map("quantity")
  status      String         @default("RELEASED") @map("status")

  product     Product        @relation(fields: [productId], references: [id])
  pallets     PalletTicket[]

  @@map("work_orders")
}

model PalletTicket {
  id           String       @id @default(uuid()) @map("id")
  palletNumber String       @unique @map("pallet_number")
  workOrderId  String       @map("work_order_id")
  componentId  String       @map("component_id")
  quantity     Int          @map("quantity")
  status       String       @default("PLANNED") @map("status")
  qrCode       String       @unique @map("qr_code")

  workOrder    WorkOrder    @relation(fields: [workOrderId], references: [id], onDelete: Cascade)
  component    Component    @relation(fields: [componentId], references: [id])
  steps        PalletStep[]

  @@map("pallet_tickets")
}

model PalletStep {
  id              String            @id @default(uuid()) @map("id")
  palletId        String            @map("pallet_id")
  stepOrder       Int               @map("step_order")
  operationName   String            @map("operation_name")
  workshopCode    String            @map("workshop_code")
  isWarehouseStep Boolean           @default(false) @map("is_warehouse_step") // TRUE: Chốt Kho | FALSE: Trạm Máy
  status          String            @default("PENDING") @map("status")
  quantityOk      Int               @default(0) @map("quantity_ok")
  quantityError   Int               @default(0) @map("quantity_error")

  pallet          PalletTicket      @relation(fields: [palletId], references: [id], onDelete: Cascade)

  @@unique([palletId, stepOrder])
  @@map("pallet_steps")
}
            

Mã nguồn PostgreSQL DDL hoàn chỉnh có thể tìm thấy trong tệp tin: db_schema.sql