🧮 Mô Hình Toán Học Bài Toán Lập Lịch V4
Để tối ưu hóa kế hoạch sản xuất và làm nổi bật các điểm nghẽn, solver mô hình hóa các ràng buộc như sau:
1. Định nghĩa các tập hợp & biến số:
- $WO = \{wo_1, wo_2, ..., wo_k\}$: Tập hợp các Lệnh sản xuất.
- $WOI = \{woi_1, woi_2, ..., woi_n\}$: Các dòng sản phẩm chi tiết của từng Lệnh.
- Với mỗi $woi_i$, ta có: $Q_i$ (số lượng), $S_i$ (ngày bắt đầu kế hoạch), $D_i$ (ngày đến hạn - Due Date).
- $Routing(BOM_i) = \{op_1, op_2, ..., op_m\}$: Cây quy trình công nghệ của chi tiết.
- $WS_j$ tại ngày $d$ có giới hạn công suất thực tế: $Cap_{j, d}$ (availableHours + otHours).
2. Ràng buộc cứng tiến trình công nghệ (Sequence Constraints):
Công đoạn đứng trước (child) phải hoàn thành trước khi công đoạn đứng sau (parent) khởi chạy một khoảng thời gian trung chuyển (leadTime): $$Date_{parent} \ge Date_{child} + leadTime$$ *Hỗ trợ:* $leadTime = 0$ cho các công đoạn sản xuất song song gối đầu trên cùng một ngày.
3. Ràng buộc giới hạn ngày bắt đầu cứng (Lower Boundary Constraint):
Không có công đoạn nào được lập lịch trước ngày bắt đầu $S_i$ của Lệnh sản xuất. Nếu lượng công việc phân bổ lùi vượt quá $S_i$, toàn bộ phần sản lượng chưa được phân bổ còn lại sẽ được gán trực tiếp vào ngày $S_i$: $$Date_{assignment} \ge S_i$$
📊 Sơ Đồ Tiến Trình Xử Lý Của Solver V4
Luồng xử lý chất tải lùi cô lập theo từng Lệnh sản xuất để đảm bảo hiệu năng và tính đúng đắn:
⚡ Cơ Chế Lập Lịch Cô Lập Cho Từng Lệnh Sản Xuất
Cách giải thuật V4 giải quyết xung đột chiếm dụng công suất giữa các Lệnh sản xuất đồng thời:
Để tránh hiện tượng các Lệnh sản xuất lớn chiếm dụng toàn bộ công suất của các Lệnh sản xuất nhỏ khác một cách ngẫu nhiên gây sai lệch tải dự báo, V4 thực hiện cô lập hoàn toàn môi trường chạy máy:
- Mỗi Lệnh sản xuất được cấp một bản sao sạch của công suất trạm máy (`CapacityContext`).
- Hệ thống rải ngược độc lập toàn bộ các mặt hàng trong Lệnh đó để tính toán điểm nghẽn chính xác của riêng Lệnh sản xuất đó.
- Sau khi hoàn thành, kết quả được gộp lại để hiển thị tổng tải xếp chồng trên sơ đồ Heatmap chung của nhà máy, giúp người làm kế hoạch biết chính xác Lệnh nào gây ra quá tải tại ngày nào.
⚖️ Cơ Chế Gom Cụm Bán Thành Phẩm Đảm Bảo Tròn Số
Cơ chế gom nhóm chi tiết phôi cùng loại để tránh số lượng kế hoạch bị thập phân lẻ:
Hệ thống tự động gom các chi tiết phôi cùng công đoạn (`bomWorkStepId`) và cùng hạn hoàn thành (`deadline`) lại thành một lô sản xuất chung:
const consolidationKey = `${j.bomWorkStepId}-${j.deadline.format(EDateFormat.DATE)}`;
...
// Cộng trực tiếp số lượng nguyên bản của các chi tiết con
totalConsolidatedQty += j.remainingQty;
Việc cộng trực tiếp số lượng nguyên gốc thay vì quy đổi qua phút rồi chia ngược cho định mức đảm bảo số lượng kế hoạch của các tác vụ chi tiết luôn là số nguyên tuyệt đối, giải quyết triệt để lỗi số lượng lẻ.
💾 Cấu Trúc Database (Prisma Schema DDL)
Cấu trúc dữ liệu lưu trữ kết quả và năng lực trạm máy:
// Bảng đo lường công suất khả dụng của trạm máy theo từng ngày
model TaskCapacity {
id String @id @default(uuid())
workstationId String // Tham chiếu sang Workstation
date DateTime // Ngày áp dụng
availableHours Float // Giờ khả dụng tiêu chuẩn (ví dụ: 8.0 giờ)
allocatedHours Float @default(0.0) // Tổng giờ đã được chất tải
otHours Float @default(0.0) // Giờ tăng ca (Overtime) được duyệt thêm
loadRatio Float @default(0.0) // Hệ số chất tải
@@unique([workstationId, date])
}
// Bảng lưu kết quả phân bổ tác vụ chi tiết xuống MES
model WorkTaskSchedule {
id String @id @default(uuid())
workOrderItemId String // Link dòng lệnh sản xuất chi tiết
operationId String // Link công đoạn trong cây BOM
workstationId String // Trạm máy thực hiện
scheduledDate DateTime // Ngày chạy máy
durationHours Float // Thời lượng chạy máy thực tế (giờ)
status String @default("SCHEDULED") // SCHEDULED, STARTED, COMPLETED, DELAYED
}
📉 Phân Tích Độ Phức Tạp Thuật Toán V4 (Complexity Analysis)
Đánh giá hiệu năng vận hành thực tế của giải thuật V4:
-
Độ phức tạp thời gian (Time Complexity):
$$O(W \cdot (I \log I + I \cdot M \cdot D))$$
Trong đó:
- $W$: Số lượng Lệnh sản xuất độc lập.
- $I$: Số lượng Items trong mỗi Lệnh.
- $M$: Số lượng nút công đoạn trong cây BOM.
- $D$: Số ngày tối đa từ ngày bắt đầu đến hạn chót (thông thường dưới 30 ngày).
- Độ phức tạp không gian (Space Complexity): $$O(I \cdot M)$$ Do cô lập trạng thái gán theo từng Lệnh sản xuất, bộ nhớ chỉ lưu trữ cây lùi của một Lệnh tại một thời điểm trước khi tổng hợp kết quả.
🚨 Thiết Kế Xử Lý Quá Tải Đầu Kỳ (Start Date Overload)
Cơ chế thay thế hoàn toàn cho giải pháp Cascade Shifting cũ:
Giải thuật V4 loại bỏ hoàn toàn phương thức dịch chuyển tiến đệ quy `cascadeShiftParent`. Lý do là việc tự động đẩy các công đoạn cha về tương lai sẽ vô tình dồn ứ toàn bộ tải của các công đoạn cuối về sát ngày hạn bàn giao (`deadline`), tạo ra nút thắt cổ chai ảo rất lớn ở cuối kỳ sản xuất.
Quy tắc dồn ứ (Overload Rule):
Khi chất tải lùi một công việc chạm cận ngày bắt đầu (`startDate`): 1. Hệ thống dừng di chuyển lùi về quá khứ. 2. Toàn bộ lượng sản lượng còn lại chưa phân bổ được gán trực tiếp vào ngày `startDate`. 3. Công suất trạm máy ngày `startDate` sẽ bị vượt ngưỡng (overload) tương ứng. 4. Điều này giúp hiển thị chính xác cột màu đỏ/đen cảnh báo quá tải ở đầu kỳ lập lịch trên biểu đồ Heatmap của nhà máy, báo hiệu cho người lập lịch biết cần bổ sung năng lực sản xuất (tăng ca, thêm nhân công) ngay từ ngày đầu tiên.
💻 Cài Đặt Thực Tế Của Solver V4 (NestJS Solver Integration)
Cấu trúc logic lõi của solver `HeuristicBackwardSolverV4` tích hợp trong codebase:
@Injectable()
export class HeuristicBackwardSolverV4 implements ISchedulingSolverGeneral {
async solve(input: ISchedulingSolverInput): Promise {
const bomTreeContext = new BomTreeContext(input.bomsWithRelated);
const allFinalAssignments: any[] = [];
const allWarnings: any[] = [];
// 1. Nhóm items theo Lệnh sản xuất để cô lập công suất
const groupedByWorkOrder = groupBy(input.workOrderItems, 'workOrderId');
for (const itemsInWorkOrder of Object.values(groupedByWorkOrder)) {
const workOrderCapacity = new CapacityContext(input.stepCapacities);
workOrderCapacity.enableExtensionMode(); // Cho phép nới lỏng tạm thời khi dồn tải
const workOrderAssignment = new AssignmentContext();
const scheduleContext = new ScheduleContext(
input.planningStartDate,
input.planningEndDate,
workOrderCapacity,
workOrderAssignment,
bomTreeContext,
);
const assigner = new BackwardAssignerV4(scheduleContext);
// Sắp xếp items theo ngày bàn giao tăng dần (EDD)
const sortedItems = itemsInWorkOrder.sort((a, b) => a.endDate.compareTo(b.endDate));
// 2. Chạy lập lịch ngược thuần túy cho từng Item
for (const workOrderItem of sortedItems) {
const bomWorkStepTrees = bomTreeContext.getBackwardTree(workOrderItem.bomId);
if (!bomWorkStepTrees || bomWorkStepTrees.length === 0) {
throw new BOMNotFoundException();
}
bomWorkStepTrees.forEach((tree) => {
const request: IBackwardAssignmentRequest = {
workOrderItem,
quantity: workOrderItem.quantity,
deadline: workOrderItem.endDate,
startDate: workOrderItem.startDate,
parentUuid: undefined,
};
assigner.assign(tree, request);
});
}
scheduleContext.assignmentContext.combine();
allFinalAssignments.push(...scheduleContext.assignmentContext.getAllAssignments());
allWarnings.push(...scheduleContext.assignmentContext.getWarnings());
}
return {
assignments: allFinalAssignments,
warnings: allWarnings,
};
}
}