From 21969c3ebb86d45c082d316aebb33b32ebafbc97 Mon Sep 17 00:00:00 2001
From: Kritkhanin Anantakul <65160144@go.buu.ac.th>
Date: Sun, 9 Mar 2025 16:41:19 +0700
Subject: [PATCH] make queue bnt and algorithm cal

---
 src/components/GanttChart/GanttCalendar.vue   |   2 +-
 src/components/GanttChart/GanttChart.vue      |  83 ++---
 src/components/GanttChart/MakequeueBtn.vue    |  28 ++
 src/components/GanttChart/ganttChart.css      |   6 +
 .../GanttChart/scheduleCalculator.js          | 306 ++++++++++++++++++
 src/views/ProductQueueView.vue                |   1 -
 6 files changed, 371 insertions(+), 55 deletions(-)
 create mode 100644 src/components/GanttChart/MakequeueBtn.vue
 create mode 100644 src/components/GanttChart/scheduleCalculator.js

diff --git a/src/components/GanttChart/GanttCalendar.vue b/src/components/GanttChart/GanttCalendar.vue
index 541850f..b3ad73a 100644
--- a/src/components/GanttChart/GanttCalendar.vue
+++ b/src/components/GanttChart/GanttCalendar.vue
@@ -6,7 +6,7 @@ import CalendarPicker from "@/components/GanttChart/CalendarPicker.vue";
 const props = defineProps({
   modelValue: {
     type: String,
-    default: '1/1/2024', // เปลี่ยนเป็น format `d/M/yyyy`
+    default: '1/1/2025', // เปลี่ยนเป็น format `d/M/yyyy`
   },
 });
 
diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue
index 41ae98d..a32b2ac 100644
--- a/src/components/GanttChart/GanttChart.vue
+++ b/src/components/GanttChart/GanttChart.vue
@@ -1,8 +1,12 @@
 <template>
   <div class="gantt-container">
-    <!-- Calendar: เชื่อมกับ selectedDate -->
-    <GanttCalendar v-model:modelValue="selectedDate" />
-
+    <div class="gantt-header">
+      <!-- Calendar: เชื่อมกับ selectedDate -->
+      <GanttCalendar v-model:modelValue="selectedDate" />
+      
+      <!-- ปุ่ม Make a queue พร้อม event listener -->
+      <MakequeueBtn @updateQueue="handleUpdateQueue" />
+    </div>
     <!-- Gantt Chart UI -->
     <div class="gantt-chart">
       <!-- Header: Time Scale -->
@@ -121,11 +125,13 @@
 import GanttCalendar from './GanttCalendar.vue';
 import OrderDialog from './OrderDialog.vue';
 import AddQueueDialog from './AddQueueDialog.vue';
+import MakequeueBtn from './MakequeueBtn.vue';
 import './ganttChart.css';
 
 export default {
   name: 'GanttChart',
   components: {
+    MakequeueBtn,
     GanttCalendar,
     OrderDialog,
     AddQueueDialog,
@@ -134,7 +140,7 @@ export default {
     return {
       showAddDialog: false,
       // ค่าจาก Calendar
-      selectedDate: '1/1/2024',
+      selectedDate: '1/1/2025',
 
       // กำหนดช่วงเวลาใน Gantt
       startTime: '08:00',
@@ -142,22 +148,21 @@ export default {
 
       // รายการเครื่องจักร
       machines: [
-        { machineID: 'MC1' ,name: 'เครื่องเป่าขวด'},
-        { machineID: 'MC2' ,name: 'เครื่องเป่าขวด2'},
-        { machineID: 'MC3' ,name: 'เครื่องสวมฉลาก'},
-        { machineID: 'MC4' ,name: 'เครื่องสวมฉลาก2'},
-        { machineID: 'MC5' ,name: 'เครื่องบรรจุ+แพ็ค'},
+        { machineID: 'MC1' , name: 'เครื่องเป่าขวด' },
+        { machineID: 'MC2' , name: 'เครื่องเป่าขวด2' },
+        { machineID: 'MC3' , name: 'เครื่องสวมฉลาก' },
+        { machineID: 'MC4' , name: 'เครื่องบรรจุ+แพ็ค' },
       ],
 
-      // เปลี่ยนจาก orders เป็น Queue
+      // รายการ Queue เริ่มต้น (สามารถเปลี่ยนแปลงได้)
       Queue: [
         {
           queueID: 1,
           machineID: 'MC1',
           orderID: 5,
           pageNumber: 1,
-          startTime: '1/1/2024 09:00',
-          finishTime: '1/1/2024 11:00',
+          startTime: '1/1/2025 09:00',
+          finishTime: '1/1/2025 11:00',
           status: 'Process',
           bottleSize: '600ml',
           producedQuantity: 400,
@@ -167,45 +172,12 @@ export default {
           machineID: 'MC2',
           orderID: 1,
           pageNumber: 1,
-          startTime: '1/1/2024 13:00',
-          finishTime: '1/1/2024 15:00',
+          startTime: '1/1/2025 13:00',
+          finishTime: '1/1/2025 15:00',
           status: 'Waiting',
           bottleSize: '500ml',
           producedQuantity: 200,
         },
-        {
-          queueID: 3,
-          machineID: 'MC1',
-          orderID: 2,
-          pageNumber: 2,
-          startTime: '1/1/2024 10:00',
-          finishTime: '1/1/2024 12:00',
-          status: 'Process',
-          bottleSize: '700ml',
-          producedQuantity: 350,
-        },
-        {
-          queueID: 4,
-          machineID: 'MC5',
-          orderID: 2,
-          pageNumber: 2,
-          startTime: '1/1/2024 14:00',
-          finishTime: '1/1/2024 16:00',
-          status: 'Process',
-          bottleSize: '600ml',
-          producedQuantity: 500,
-        },
-        {
-          queueID: 5,
-          machineID: 'MC5',
-          orderID: 4,
-          pageNumber: 1,
-          startTime: '2/1/2024 8:00',
-          finishTime: '2/1/2024 12:00',
-          status: 'Process',
-          bottleSize: '600ml',
-          producedQuantity: 500,
-        },
       ],
 
       // Drag/Drop และ Resize
@@ -250,6 +222,14 @@ export default {
     },
   },
   methods: {
+    // รับข้อมูล Queue ที่ส่งมาจาก MakequeueBtn
+    handleUpdateQueue(newQueue) {
+      console.log("Received new queue:", newQueue);
+      // ในที่นี้เราสามารถแทนที่ Queue เดิมหรือ merge กับข้อมูลที่มีอยู่
+      // ตัวอย่างนี้ใช้แทนที่ Queue ทั้งหมด
+      this.Queue = newQueue;
+    },
+
     // -------------------- Dialog & Edit --------------------
     openQueueDialog(item) {
       this.selectedQueueItem = { ...item };
@@ -262,11 +242,10 @@ export default {
       this.showAddDialog = true;
     },
     deleteQueueItem(item) {
-        this.Queue = this.Queue.filter(q => q.queueID !== item.queueID);
-        this.closeQueueDialog();
+      this.Queue = this.Queue.filter(q => q.queueID !== item.queueID);
+      this.closeQueueDialog();
     },
     openAddQueueDialogForEdit(queueItem) {
-      // เมื่อกด Edit ใน OrderDialog ให้เปิด AddQueueDialog ในโหมดแก้ไข
       this.selectedQueueItem = { ...queueItem };
       this.showAddDialog = true;
     },
@@ -275,15 +254,13 @@ export default {
       this.selectedQueueItem = null;
     },
     handleAddQueue(newQueueItem) {
-      // เพิ่ม newQueueItem ลงใน Queue
       this.Queue.push({
-        queueID: this.Queue.length + 1, // กำหนด queueID ใหม่
+        queueID: this.Queue.length + 1,
         ...newQueueItem,
       });
       this.showAddDialog = false;
     },
     handleEditQueue(updatedQueueItem) {
-      // หา index ของ queue ที่ต้องแก้ไข
       const index = this.Queue.findIndex(q => q.queueID === updatedQueueItem.queueID);
       if (index !== -1) {
         this.Queue.splice(index, 1, { ...updatedQueueItem });
diff --git a/src/components/GanttChart/MakequeueBtn.vue b/src/components/GanttChart/MakequeueBtn.vue
new file mode 100644
index 0000000..bff4e23
--- /dev/null
+++ b/src/components/GanttChart/MakequeueBtn.vue
@@ -0,0 +1,28 @@
+<template>
+    <div>
+      <button @click="makeQueue">Make Queue</button>
+    </div>
+  </template>
+  
+  <script>
+  import { scheduleAllOrders } from './scheduleCalculator.js';
+  
+  export default {
+    name: 'MakequeueBtn',
+    methods: {
+      makeQueue() {
+        const queueItems = scheduleAllOrders();
+        // ส่งข้อมูล queue ไปยังคอมโพเนนต์พาเรนต์
+        this.$emit('updateQueue', queueItems);
+      }
+    }
+  }
+  </script>
+  
+  <style scoped>
+  button {
+    padding: 8px 16px;
+    font-size: 16px;
+  }
+  </style>
+  
\ No newline at end of file
diff --git a/src/components/GanttChart/ganttChart.css b/src/components/GanttChart/ganttChart.css
index 2903ba1..ba67c4b 100644
--- a/src/components/GanttChart/ganttChart.css
+++ b/src/components/GanttChart/ganttChart.css
@@ -9,6 +9,12 @@
     display: flex;
     background: #fff;
   }
+  .gantt-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between; /* จัดให้ Calendar อยู่ซ้าย, ปุ่มอยู่ขวา */
+    margin-bottom: 16px;
+  }
   
   .machine-label {
     width: 150px;
diff --git a/src/components/GanttChart/scheduleCalculator.js b/src/components/GanttChart/scheduleCalculator.js
new file mode 100644
index 0000000..4adf107
--- /dev/null
+++ b/src/components/GanttChart/scheduleCalculator.js
@@ -0,0 +1,306 @@
+// src/utils/schedule.js
+
+// ฟังก์ชันช่วยจัดการวันที่และเวลา
+function roundDownToHalfHour(dt) {
+    const newDate = new Date(dt);
+    const minutes = newDate.getMinutes();
+    if (minutes < 30) {
+      newDate.setMinutes(0, 0, 0);
+    } else {
+      newDate.setMinutes(30, 0, 0);
+    }
+    return newDate;
+  }
+  
+  // แบ่งช่วงเวลางานตามเวลาทำงาน (08:00–12:00, 13:00–17:00)
+  function scheduleTaskSegments(startTime, durationHours) {
+    const segments = [];
+    let current = new Date(startTime);
+    let remaining = durationHours;
+    while (remaining > 0) {
+      const day = new Date(current.getFullYear(), current.getMonth(), current.getDate());
+      const morningStart = new Date(day);
+      morningStart.setHours(8, 0, 0, 0);
+      const morningEnd = new Date(day);
+      morningEnd.setHours(12, 0, 0, 0);
+      const afternoonStart = new Date(day);
+      afternoonStart.setHours(13, 0, 0, 0);
+      const afternoonEnd = new Date(day);
+      afternoonEnd.setHours(17, 0, 0, 0);
+  
+      if (current < morningStart) {
+        current = new Date(morningStart);
+      } else if (current >= morningEnd && current < afternoonStart) {
+        current = new Date(afternoonStart);
+      }
+  
+      const segLimit = (current < morningEnd) ? morningEnd : afternoonEnd;
+      const available = (segLimit - current) / (3600 * 1000);
+      if (available >= remaining) {
+        let segEnd = new Date(current.getTime() + remaining * 3600 * 1000);
+        segEnd = roundDownToHalfHour(segEnd);
+        segments.push({ start: new Date(current), end: segEnd });
+        current = segEnd;
+        remaining = 0;
+      } else {
+        let segEnd = roundDownToHalfHour(segLimit);
+        segments.push({ start: new Date(current), end: segEnd });
+        remaining -= available;
+        const nextDay = new Date(current);
+        nextDay.setDate(current.getDate() + 1);
+        nextDay.setHours(8, 0, 0, 0);
+        current = nextDay;
+      }
+    }
+    return { segments, endTime: current };
+  }
+  
+  function formatDate(dt) {
+    const month = dt.getMonth() + 1;
+    const day = dt.getDate();
+    const year = dt.getFullYear();
+    const hours = String(dt.getHours()).padStart(2, '0');
+    const minutes = String(dt.getMinutes()).padStart(2, '0');
+    return `${month}/${day}/${year} ${hours}:${minutes}`;
+  }
+  
+  // ==============================
+  // ข้อมูลจำลอง (Data)
+  // ==============================
+  
+  // ข้อมูลเครื่องจักร
+  const machines_data = {
+    "MC1": { name: "เครื่องเป่าขวด", speed: { 600: 2000 } },
+    "MC3": { name: "เครื่องสวมฉลาก", speed: { 600: 4000 } },
+    "MC4": { name: "เครื่องบรรจุ + แพ็ค", speed: { 600: 200 } }
+  };
+  
+  // รายการออเดอร์ (เพิ่ม orderID)q
+  const orders = [
+    { orderID: 1, customer: "จอห์น", brand: "น้ำดื่มยี่ห้อ A", size: 600, packs: 300 },
+    { orderID: 2, customer: "เจน",  brand: "น้ำดื่มยี่ห้อ A", size: 600, packs: 300 }
+  ];
+  
+  // จำนวนขวดต่อแพ็ค (สำหรับ 600 ml = 12 ขวด/แพ็ค)
+  const bottles_per_pack_map = { 600: 12 };
+  
+  // สต๊อกสินค้าสำเร็จรูป
+  const product_stock = {
+    "น้ำดื่มยี่ห้อ A": { 600: 0 }
+  };
+  
+  // วัตถุดิบ
+  const raw_materials = {
+    "MT001": { name: "ฉลาก ยี่ห้อ A", stock: 200000, usage_per_bottle: 1 },
+    "MT005": { name: "Preform (ขวด)", stock: 200000, usage_per_bottle: 1 },
+    "MT007": { name: "ขวดเปล่า", stock: 0, usage_per_bottle: 1 },
+    "MT008": { name: "ขวดเปล่าพร้อมฉลาก ยี่ห้อ A", stock: 0, usage_per_bottle: 1 }
+  };
+  
+  // ตารางเวลาของเครื่องจักร (เก็บ task ในแต่ละเครื่อง)
+  const schedule = {
+    "MC1": [],
+    "MC3": [],
+    "MC4": []
+  };
+  
+  // เวลาที่เครื่องจักรว่าง (เริ่มต้นที่ 1 มีนาคม 2025 เวลา 08:00)
+  // หมายเหตุ: เดือนใน JS เริ่มที่ 0 => 2 หมายถึงมีนาคม
+  const machine_available = {
+    "MC1": new Date(2025, 0, 1, 8, 0, 0),
+    "MC3": new Date(2025, 0, 1, 8, 0, 0),
+    "MC4": new Date(2025, 0, 1, 8, 0, 0)
+  };
+  
+  // ฟังก์ชันช่วยปัดจำนวนผลิต
+  function roundBottlesUp(bottles_needed, step = 1000) {
+    return Math.ceil(bottles_needed / step) * step;
+  }
+  
+  function roundPacksUp(packs_needed, step = 100) {
+    return Math.ceil(packs_needed / step) * step;
+  }
+  
+  // ==============================
+  // ฟังก์ชันจัดตารางงานสำหรับแต่ละออเดอร์
+  // ==============================
+  function schedule_order(order) {
+    const { orderID, size, packs, customer, brand } = order;
+    const required_bottles = packs * bottles_per_pack_map[size];
+  
+    // --- เช็คสินค้าสำเร็จรูป ---
+    if (product_stock[brand][size] >= packs) {
+      product_stock[brand][size] -= packs;
+      console.log(`Order ${customer}: ใช้สินค้าสำเร็จรูป (ไม่ต้องผลิต)`);
+      return;
+    }
+  
+    // --- เช็ควัตถุดิบ: ถ้ามี ขวดเปล่าพร้อมฉลาก (MT008) พอ ---
+    if (raw_materials["MT008"].stock >= required_bottles) {
+      raw_materials["MT008"].stock -= required_bottles;
+      const packs_MC4 = roundPacksUp(packs, 100);
+      const time_MC4 = packs_MC4 / machines_data["MC4"].speed[size];
+      let start_MC4 = new Date(machine_available["MC4"]);
+      const day = new Date(start_MC4.getFullYear(), start_MC4.getMonth(), start_MC4.getDate());
+      const afternoonStart = new Date(day);
+      afternoonStart.setHours(13, 0, 0, 0);
+      if (start_MC4 < afternoonStart) {
+        start_MC4 = new Date(afternoonStart);
+      }
+      const result_MC4 = scheduleTaskSegments(start_MC4, time_MC4);
+      result_MC4.segments.forEach(seg => {
+        schedule["MC4"].push({
+          start: seg.start,
+          end: seg.end,
+          task: `บรรจุ+แพ็ค ~ ${packs_MC4} แพ็ค (Order ${orderID})`,
+          orderID,
+          producedQuantity: required_bottles
+        });
+      });
+      machine_available["MC4"] = new Date(result_MC4.endTime);
+      return;
+    }
+  
+    // --- เช็ควัตถุดิบ: ถ้ามี ขวดเปล่า (MT007) พอ ---
+    if (raw_materials["MT007"].stock >= required_bottles) {
+      raw_materials["MT007"].stock -= required_bottles;
+      const bottles_MC3 = roundBottlesUp(required_bottles, 1000);
+      const time_MC3 = bottles_MC3 / machines_data["MC3"].speed[size];
+      let start_MC3 = new Date(machine_available["MC1"]); // ใช้เวลาที่ MC1 ว่าง
+      const result_MC3 = scheduleTaskSegments(start_MC3, time_MC3);
+      result_MC3.segments.forEach(seg => {
+        schedule["MC3"].push({
+          start: seg.start,
+          end: seg.end,
+          task: `สวมฉลาก ~ ${bottles_MC3} ขวด (Order ${orderID})`,
+          orderID,
+          producedQuantity: required_bottles
+        });
+      });
+      machine_available["MC3"] = new Date(result_MC3.endTime);
+      const dependency_time = new Date(result_MC3.endTime);
+  
+      const packs_MC4 = roundPacksUp(packs, 100);
+      const time_MC4 = packs_MC4 / machines_data["MC4"].speed[size];
+      let start_MC4 = new Date(Math.max(machine_available["MC4"], dependency_time));
+      const day2 = new Date(start_MC4.getFullYear(), start_MC4.getMonth(), start_MC4.getDate());
+      const afternoonStart2 = new Date(day2);
+      afternoonStart2.setHours(13, 0, 0, 0);
+      if (start_MC4 < afternoonStart2) {
+        start_MC4 = new Date(afternoonStart2);
+      }
+      const result_MC4 = scheduleTaskSegments(start_MC4, time_MC4);
+      result_MC4.segments.forEach(seg => {
+        schedule["MC4"].push({
+          start: seg.start,
+          end: seg.end,
+          task: `บรรจุ+แพ็ค ~ ${packs_MC4} แพ็ค (Order ${orderID})`,
+          orderID,
+          producedQuantity: required_bottles
+        });
+      });
+      machine_available["MC4"] = new Date(result_MC4.endTime);
+      return;
+    }
+  
+    // --- กรณีไม่ครบวัตถุดิบ: ผลิตครบทุกขั้นตอน ---
+    // 1. MC1: เป่าขวด
+    const bottles_MC1 = roundBottlesUp(required_bottles, 1000);
+    const time_MC1 = bottles_MC1 / machines_data["MC1"].speed[size];
+    let start_MC1 = new Date(machine_available["MC1"]);
+    const result_MC1 = scheduleTaskSegments(start_MC1, time_MC1);
+    result_MC1.segments.forEach(seg => {
+      schedule["MC1"].push({
+        start: seg.start,
+        end: seg.end,
+        task: `เป่าขวด ${bottles_MC1} ขวด (Order ${orderID})`,
+        orderID,
+        producedQuantity: required_bottles
+      });
+    });
+    machine_available["MC1"] = new Date(result_MC1.endTime);
+    let dependency_time = new Date(result_MC1.endTime);
+  
+    // 2. MC3: สวมฉลาก (ใช้จำนวนเดียวกับ MC1)
+    const bottles_MC3_full = bottles_MC1;
+    const time_MC3_full = bottles_MC3_full / machines_data["MC3"].speed[size];
+    let start_MC3_full = new Date(Math.max(machine_available["MC3"], dependency_time));
+    const result_MC3_full = scheduleTaskSegments(start_MC3_full, time_MC3_full);
+    result_MC3_full.segments.forEach(seg => {
+      schedule["MC3"].push({
+        start: seg.start,
+        end: seg.end,
+        task: `สวมฉลาก ~ ${bottles_MC3_full} ขวด (Order ${orderID})`,
+        orderID,
+        producedQuantity: required_bottles
+      });
+    });
+    machine_available["MC3"] = new Date(result_MC3_full.endTime);
+    dependency_time = new Date(result_MC3_full.endTime);
+  
+    // 3. MC4: บรรจุ+แพ็ค
+    const packs_MC4_full = roundPacksUp(packs, 100);
+    const time_MC4_full = packs_MC4_full / machines_data["MC4"].speed[size];
+    let start_MC4_full = new Date(Math.max(machine_available["MC4"], dependency_time));
+    const day3 = new Date(start_MC4_full.getFullYear(), start_MC4_full.getMonth(), start_MC4_full.getDate());
+    const afternoonStart3 = new Date(day3);
+    afternoonStart3.setHours(13, 0, 0, 0);
+    if (start_MC4_full < afternoonStart3) {
+      start_MC4_full = new Date(afternoonStart3);
+    }
+    const result_MC4_full = scheduleTaskSegments(start_MC4_full, time_MC4_full);
+    result_MC4_full.segments.forEach(seg => {
+      schedule["MC4"].push({
+        start: seg.start,
+        end: seg.end,
+        task: `บรรจุ+แพ็ค ~ ${packs_MC4_full} แพ็ค (Order ${orderID})`,
+        orderID,
+        producedQuantity: required_bottles
+      });
+    });
+    machine_available["MC4"] = new Date(result_MC4_full.endTime);
+  }
+  // ==============================
+  // ฟังก์ชันจัดตารางงานสำหรับออเดอร์ทั้งหมด (FIFO)
+  // ==============================
+  function scheduleAllOrders() {
+    schedule["MC001"] = [];
+    schedule["MC003"] = [];
+    schedule["MC004"] = [];
+    machine_available["MC001"] = new Date(2025, 0, 1, 8, 0, 0);
+    machine_available["MC003"] = new Date(2025, 0, 1, 8, 0, 0);
+    machine_available["MC004"] = new Date(2025, 0, 1, 8, 0, 0);
+  
+    for (const order of orders) {
+      if (product_stock[order.brand][order.size] >= order.packs) {
+        product_stock[order.brand][order.size] -= order.packs;
+        console.log(`Order ${order.customer}: ใช้สินค้าสำเร็จรูป`);
+      } else {
+        schedule_order(order);
+      }
+    }
+  
+    // รวม queue จาก schedule
+    const queueItems = [];
+    let idCounter = 1;
+    for (const machineKey of Object.keys(schedule)) {
+      for (const task of schedule[machineKey]) {
+        queueItems.push({
+          queueID: idCounter++,
+          machineID: machineKey,
+          orderID: task.orderID,
+          pageNumber: 1,
+          startTime: formatDate(task.start),  // ✅ เปลี่ยนรูปแบบเป็น "1/1/2025 HH:mm"
+          finishTime: formatDate(task.end),
+          status: "Process",
+          bottleSize: "600ml",
+          producedQuantity: task.producedQuantity,
+          taskDescription: task.task
+        });
+      }
+    }
+    return queueItems;
+  }
+  
+  export { scheduleAllOrders };
+  
\ No newline at end of file
diff --git a/src/views/ProductQueueView.vue b/src/views/ProductQueueView.vue
index 7c6ec08..e24eed3 100644
--- a/src/views/ProductQueueView.vue
+++ b/src/views/ProductQueueView.vue
@@ -3,7 +3,6 @@ import { ref } from 'vue'
 import GanttChart from '@/components/GanttChart/GanttChart.vue'
 import EmployeeSector from '@/components/EmployeeSector.vue'
 
-const selectedDate = ref('1/1/2024')
 const employees = Array.from({ length: 10 }, (_, i) => `EM${i + 1}`)
 const orders = ref([
   { id: 1, name: 'ORDER1' },
-- 
GitLab