From 8890cba87e3f661cf0f9781df72d1913cdfbce19 Mon Sep 17 00:00:00 2001 From: Naz Date: Wed, 11 Mar 2026 14:48:25 +0800 Subject: [PATCH] Update Inv Movement - ALL ITEM & QR --- .../Views/InventoryMaster/ItemMovement.cshtml | 1069 ++++++++++------- .../Views/InventoryMaster/QrMaster.cshtml | 17 +- .../ItemMovement/ItemMovementUser.cshtml | 215 +++- Controllers/API/Inventory/InvMainAPI.cs | 3 +- 4 files changed, 848 insertions(+), 456 deletions(-) diff --git a/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml b/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml index bada624..b91dc86 100644 --- a/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml @@ -184,6 +184,33 @@ style="width:100%;border-style: solid; border-width: 1px"> + + @* All Item Table *@ +
+
+

All Item

+
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
@@ -702,16 +729,34 @@ Next
+ + @* QR Model *@ + @section Scripts { -@{ - await Html.RenderPartialAsync("_ValidationScriptsPartial"); -} - -} \ No newline at end of file + }); + +} diff --git a/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml b/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml index 3c89e3a..81d1b37 100644 --- a/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml @@ -924,7 +924,6 @@ isStationPIC() { if (!this.thisItem || !this.stationlist || !this.currentUser) return false; - // Now this.thisItem.toStation will have the ID from the backend const targetStationId = this.thisItem.toStation; return this.stationlist.some(station => @@ -1244,7 +1243,6 @@ let receiveToStation = null; if (this.thisItem.toStation) { - // Only keep Station. Clear User and Store as requested. receiveToUser = null; receiveToStore = null; receiveToStation = this.thisItem.toStation; @@ -1258,19 +1256,16 @@ receiveToStore = null; } - // 3. BINA PAKEJ DATA (Lengkap dengan Last & To) const formData = { Id: this.thisItem.movementId, ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Remark: this.thisItem.remark, LatestStatus: statusToSave, - // Data Penerima (Update kepada siapa yang klik receive sekarang) ToUser: receiveToUser, ToStore: receiveToStore, ToStation: receiveToStation, - // Data Penghantar (Kekalkan maklumat asal sebagai rujukan sejarah) LastUser: this.thisItem.lastUser, LastStore: this.thisItem.lastStore, LastStation: this.thisItem.lastStation @@ -1570,13 +1565,10 @@ const formData = { ItemId: this.thisItem.itemID, - // ORIGIN: Where is it right now? LastUser: this.thisItem.toUser, LastStore: this.thisItem.toStore, LastStation: this.thisItem.toStation, - // DESTINATION: Set to null here. The C# backend will query the DB - // and fill these in automatically based on history! ToUser: null, ToStore: null, ToStation: null, @@ -1615,7 +1607,7 @@ alert('Error: ' + error.message); } }, - // Show the Station Modal + StationMessage() { this.selectedStation = ""; $("#stationMessageModal").modal("show"); @@ -1630,7 +1622,6 @@ try { const now = new Date(); - // 1. LOGIC: Dynamically Identify the Origin (Where is the item right now?) let originUser = null; let originStore = null; let originStation = null; @@ -1642,18 +1633,16 @@ } else if (this.thisItem.toUser) { originUser = this.thisItem.toUser; } else { - originUser = this.currentUserId; // Fallback + originUser = this.currentUserId; } const formData = { ItemId: this.thisItem.itemID, - // ORIGINS: Shift the current location to the "Last" fields LastStation: originStation, LastStore: originStore, LastUser: originUser, - // DESTINATIONS: To the newly selected station (User and Store must be null) ToStation: this.selectedStation, ToUser: null, ToStore: null, @@ -1662,7 +1651,7 @@ Action: "Assign", Quantity: this.thisItem.movementQuantity || 1, Remark: this.remark ? (this.remark + ' / Deployed to station') : 'Deployed to station', - ConsignmentNote: this.document, // Ensure this matches how your file is stored in Vue data + ConsignmentNote: this.document, SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), diff --git a/Areas/Inventory/Views/ItemMovement/ItemMovementUser.cshtml b/Areas/Inventory/Views/ItemMovement/ItemMovementUser.cshtml index 33199ed..3ecad63 100644 --- a/Areas/Inventory/Views/ItemMovement/ItemMovementUser.cshtml +++ b/Areas/Inventory/Views/ItemMovement/ItemMovementUser.cshtml @@ -152,6 +152,33 @@ style="width:100%;border-style: solid; border-width: 1px"> + + @* All Item User *@ +
+
+

All Item

+
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
@@ -629,6 +656,24 @@ Next + + @* QR Modal *@ + @section Scripts { @@ -663,6 +708,8 @@ dropdownOpen: false, currentUser: null, stations:[], + selectedLocation: "all", + allItemsListDatatable: null, }; }, computed: { @@ -823,11 +870,14 @@ }, }, async mounted() { - this.fetchItemMovement(); - await this.fetchUser(); - await Promise.all([ - this.fetchStation(), - ]); + try { + await this.fetchUser(); + await this.fetchStation(); + await this.fetchItemMovement(); + + } catch (error) { + console.error("Initialization failed:", error); + } }, methods: { formatDate(dateString) { @@ -876,7 +926,159 @@ let modal = new bootstrap.Modal(document.getElementById("remarkModal")); modal.show(); }, + updateAllItemsTable() { + if (this.allItemsListDatatable) { + this.allItemsListDatatable.destroy(); + } + let filteredData = []; + let latestMovementsMap = {}; + + this.itemMovements.forEach(movement => { + let id = movement.itemId; + if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) { + latestMovementsMap[id] = movement; + } + }); + + let latestMovements = Object.values(latestMovementsMap); + + latestMovements.forEach(movement => { + + let isReturned = movement.toOther === 'Return' && movement.movementComplete == 1; + let isCanceled = movement.latestStatus === 'Ready To Deploy' && movement.movementComplete == 1; + + if (!isReturned && !isCanceled && movement.movementComplete == 1) { + + // Check if the item is currently with the user OR deployed to a station + let isWithUser = movement.toUser === this.userId; + + // Extract all station IDs where the current user is the PIC + let userStationIds = this.stations.map(s => s.stationId); + let isDeployedToUserStation = userStationIds.includes(movement.toStation); + + if (isWithUser || isDeployedToUserStation) { + + if (this.selectedLocation === "all" || this.selectedLocation === "" || !this.selectedLocation) { + filteredData.push(movement); + } + + else if (this.selectedLocation === "user") { + if (isWithUser) { + filteredData.push(movement); + } + } + + else if (this.selectedLocation.startsWith("station_")) { + let stationId = parseInt(this.selectedLocation.split("_")[1]); + if (movement.toStation === stationId) { + filteredData.push(movement); + } + } + } + } + }); + + function renderFile(data, type, full, meta) { + if (!data) { + return "No Document"; + } + var isImage = /\.(jpeg|jpg|png|gif)$/i.test(data); + var isPdf = /\.pdf$/i.test(data); + if (isImage) { + return ` + Image + `; + } else if (isPdf) { + return ` + PDF Document +
View PDF +
`; + } else { + return `Download File`; + } + } + + this.allItemsListDatatable = $("#allItemsListDatatable").DataTable({ + data: filteredData, + columns: [ + { + title: "Unique ID", + data: "uniqueID", + render: function (data, type, row) { + return ` + ${data} +
+ `; + } + }, + { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data}
${renderFile(full.productImage)}`; } }, + { title: "Current Location", data: null, render: (data, type, row) => { + return row.toStationName ? row.toStationName : (row.toUserName ? row.toUserName : "Unknown"); + }}, + { title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) }, + { title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) }, + { title: "Action", data: "action" }, + { title: "From User", data: "lastUserName" }, + { title: "From Station", data: "lastStationName" }, + { title: "From Store", data: "lastStoreName" }, + { title: "Last User", data: "toUserName" }, + { title: "Last Station", data: "toStationName" }, + { title: "Last Store", data: "toStoreName" }, + { title: "Quantity", data: "quantity" }, + ], + responsive: true, + drawCallback: function (settings) { + setTimeout(() => { + const api = this.api(); + api.rows().every(function () { + const data = this.data(); + + const containerId = `qr_movement_${data.itemId}`; + const container = document.getElementById(containerId); + + if (!container) return; + + container.innerHTML = ""; + const qrText = data.qrString ? data.qrString : data.uniqueID; + if (!qrText) return; + + // small QR + new QRCode(container, { + text: qrText, + width: 100, + height: 100, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.M + }); + + container.style.cursor = "pointer"; + container.title = "Click to enlarge"; + + container.onclick = function () { + const zoomContainer = document.getElementById("zoomedQrContainer"); + zoomContainer.innerHTML = ""; + + // big QR + new QRCode(zoomContainer, { + text: qrText, + width: 250, + height: 250, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.M + }); + + document.getElementById("zoomedQrText").innerText = data.uniqueID; + $('#zoomQrModal').modal('show'); + }; + }); + }, 100); + }, + }); + }, consignmentNote(consignmentNote) { if (!consignmentNote) { this.consignmentNoteUrl = "No consignment note available."; @@ -884,10 +1086,8 @@ return; } - // Pastikan URL betul this.consignmentNoteUrl = consignmentNote; - // Tunggu Vue update sebelum buka modal this.$nextTick(() => { new bootstrap.Modal(document.getElementById('consignmentModal')).show(); }); @@ -960,6 +1160,7 @@ renderTables() { if (this.sortBy === "all") { this.initAllTables(); + this.updateAllItemsTable(); // Initialize the new list table on load } }, diff --git a/Controllers/API/Inventory/InvMainAPI.cs b/Controllers/API/Inventory/InvMainAPI.cs index d11b6d8..692c72b 100644 --- a/Controllers/API/Inventory/InvMainAPI.cs +++ b/Controllers/API/Inventory/InvMainAPI.cs @@ -1382,7 +1382,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory i.ToStation, i.LatestStatus, i.receiveDate, - i.MovementComplete + i.MovementComplete, + QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{i.Item?.UniqueID}" })); } catch (Exception ex)