diff --git a/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml b/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml index 3b73f6b..a53292f 100644 --- a/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml @@ -1162,11 +1162,13 @@ { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data}
${renderFile(full.productImage)}`; } }, { title: "Product Code", data: "uniqueID" }, { title: "Action", data: "action" }, - { title: "Send Date", data: "sendDate" , render: this.formatDate.bind(this)}, + { title: "Send Date", data: "sendDate" , render: this.formatDate.bind(this)}, { title: "From User", data: "lastUserName" }, { title: "Last User", data: "toUserName" }, { title: "From Station", data: "lastStationName" }, + { title: "Last Station", data: "toStationName" }, { title: "From Store", data: "lastStoreName" }, + { title: "Last Store", data: "toStoreName" }, { title: "Start Status", data: "toOther" }, { title: "Product Category", data: "productCategory" }, { title: "Qty", data: "quantity" }, diff --git a/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml b/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml index f88a720..e9c2196 100644 --- a/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/QrMaster.cshtml @@ -97,6 +97,37 @@
+
+ Current Information +
+
    +
  • +
    + + User: + + + {{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }} + +
    +
  • + +
  • + + Store: + + {{ thisItem.currentStore || 'N/A' }} +
  • + +
  • + + Station: + + {{ thisItem.currentStation || 'N/A' }} +
  • +
+
+ @*
Current Information
@@ -126,7 +157,7 @@ {{ thisItem.lastStationName || 'N/A' }} -
+
*@
@@ -165,7 +196,7 @@ -
+

Cancel Item Movement

@@ -194,7 +225,23 @@
+
+
+ +
+
+ +
+

Receive Item

+
+
+
+
+
+
@@ -227,7 +274,7 @@
@* Inv Master Return Item & Deploy to Station*@ -
+

Item Actions

@@ -165,7 +166,7 @@ User: - {{ thisItem.currentUser }} + {{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }}
@@ -448,21 +449,42 @@ try { const now = new Date(); + + // 1. LOGIC: Identify the Origin (Last location) + let originUser = null; + let originStore = null; + let originStation = null; + + if (this.thisItem.toStation || this.thisItem.currentStationId) { + originStation = this.thisItem.toStation || this.thisItem.currentStationId; + } else if (this.thisItem.toStore) { + originStore = this.thisItem.toStore; + } else if (this.thisItem.toUser) { + originUser = this.thisItem.toUser; + } else { + originUser = this.currentUserId; // Fallback to current user if not found + } + const formData = { ItemId: this.thisItem.itemID, - LastStation: this.thisItem.currentStationId, - LastStore: this.thisItem.lastStore, - LastUser: this.currentUserId, + + // ORIGINS: Based on the logic above + LastStation: originStation, + LastStore: originStore, + LastUser: originUser, + + // DESTINATIONS: Explicitly set to only the Station + ToStation: this.selectedStation, + ToUser: null, + ToStore: null, + ToOther: "Delivered", - SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Action: "Assign", - Quantity: this.thisItem.quantity, + Quantity: this.thisItem.quantity || 1, Remark: this.remark, ConsignmentNote: this.consignmentNote, + SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), - ToUser: this.currentUserId, - ToStore: this.thisItem.lastStore, - ToStation: this.selectedStation, LatestStatus: "Delivered", ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), MovementComplete: true, @@ -482,6 +504,7 @@ alert('Success! Item assign to the Station.'); $('#stationMessage').modal('hide'); this.displayStatus = "return"; + this.resetForm(); } else { throw new Error('Failed to submit form.'); } @@ -501,12 +524,42 @@ try { const now = new Date(); + + let receiveToUser = null; + let receiveToStore = null; + let receiveToStation = null; + + if (this.thisItem.toStation) { + // Only keep Station. Clear User and Store as requested. + receiveToUser = null; + receiveToStore = null; + receiveToStation = this.thisItem.toStation; + } + else if (this.thisItem.toStore) { + receiveToUser = null; + receiveToStore = this.currentUser.store; + } + else if (this.thisItem.toUser) { + receiveToUser = this.currentUser.id; + receiveToStore = null; + } + const formData = { Id: this.thisItem.id, - ToStore: this.thisItem.lastStore, + // ToStore: this.thisItem.lastStore, LatestStatus: "Delivered", ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), MovementComplete: true, + + // 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 }; const response = await fetch('/InvMainAPI/UpdateItemMovementUser', { @@ -537,29 +590,47 @@ }, async returnItemMovement() { - if (!confirm("Are you sure you want to return this item?")) { return false; } try { + // 1. LOGIC: Identify the Origin (Last location) + let returnLastUser = null; + let returnLastStore = null; + let returnLastStation = null; + + if (this.thisItem.toStation || this.thisItem.currentStationId) { + returnLastStation = this.thisItem.toStation || this.thisItem.currentStationId; + } else if (this.thisItem.toStore) { + returnLastStore = this.thisItem.toStore; + } else if (this.thisItem.toUser) { + returnLastUser = this.thisItem.toUser; + } else { + returnLastUser = this.currentUserId; // Fallback to current user if not found + } + const now = new Date(); const formData = { ItemId: this.thisItem.itemID, - LastStation: this.thisItem.currentStationId, - LastStore: this.thisItem.currentStoreId, - LastUser: this.currentUserId, + + // ORIGINS: Where the item is currently sitting + LastUser: returnLastUser, + LastStore: returnLastStore, + LastStation: returnLastStation, + + // DESTINATIONS: Set to null (the C# API logic overrides this) + ToUser: null, + ToStore: null, + ToStation: null, + ToOther: "Return", - SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Action: "StockIn", - // Quantity: this.thisItem.quantity, Quantity: this.thisItem.movementQuantity || 1, Remark: this.remark, ConsignmentNote: this.consignmentNote, + SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), - ToUser: this.InventoryMasterId, - ToStore: this.thisItem.lastStore, - ToStation: this.thisItem.lastStation, LatestStatus: null, ReceiveDate: null, MovementComplete: false, @@ -598,22 +669,27 @@ this.thisItem = await response.json(); // this.fetchStore(this.thisItem.lastStore); - console.log("Current Station ID:", this.thisItem.currentStationId); - console.log("To Station ID:", this.thisItem.toStationId); + // Check if the current user is the PIC of the destination station + const isPicOfTargetStation = this.stationList.some( + station => station.stationId == this.thisItem.toStationId + ); - // 1. ARRIVED/RECEIVE LOGIC: Check if YOU are the "ToUser" + console.log("Is PIC of Target Station:", isPicOfTargetStation); + + // 1. ARRIVED/RECEIVE LOGIC + // Allow access if the user is the 'ToUser' OR the 'Station PIC' if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus == null && - this.thisItem.toUser == this.currentUserId && // Check ToUser, not LastUser/CurrentUserId + (this.thisItem.toUser == this.currentUserId || isPicOfTargetStation) && this.thisItem.movementComplete == 0) { this.displayStatus = "arrived"; - // 2. RETURN/OWNED LOGIC: Check if YOU currently hold the item + // 2. RETURN/OWNED LOGIC } else if (this.thisItem.movementId != null && this.thisItem.latestStatus != null && - this.thisItem.toUser == this.currentUserId && + (this.thisItem.toUser == this.currentUserId || isPicOfTargetStation) && this.thisItem.latestStatus != "Ready To Deploy") { this.displayStatus = "return"; @@ -627,7 +703,6 @@ this.displayStatus = "requestAgain"; } else { - // FALLBACK: If none of the above matches, it means someone else is the ToUser this.displayStatus = "differentUser"; this.thisItem = null; } diff --git a/Controllers/API/Inventory/InvMainAPI.cs b/Controllers/API/Inventory/InvMainAPI.cs index 3e34dc7..dc08673 100644 --- a/Controllers/API/Inventory/InvMainAPI.cs +++ b/Controllers/API/Inventory/InvMainAPI.cs @@ -961,7 +961,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory CurrentStation = item.Movement?.FromStation?.StationName, CurrentStationId = item.Movement?.ToStation ?? item.Movement?.LastStation, - ToStationId = item.Movement?.ToStation, + ToStationId = item.Movement?.ToStation, + toStation = item.Movement?.ToStation, LastUser = item.Movement?.LastUser, ToUserName = item.Movement?.NextUser?.UserName, @@ -1060,13 +1061,37 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory try { - itemmovement.sendDate = DateTime.Now; // This ensures hours/minutes/seconds are captured - itemmovement.Date = DateTime.Now; // Set the general record date as well + //itemmovement.sendDate = DateTime.Now; // This ensures hours/minutes/seconds are captured + //itemmovement.Date = DateTime.Now; // Set the general record date as well - var inventoryMaster = await _centralDbContext.InventoryMasters.Include("User").FirstOrDefaultAsync(i => i.UserId == itemmovement.LastUser); - if (inventoryMaster != null) + //var inventoryMaster = await _centralDbContext.InventoryMasters.Include("User").FirstOrDefaultAsync(i => i.UserId == itemmovement.LastUser); + //if (inventoryMaster != null) + //{ + // itemmovement.LastStore = inventoryMaster.StoreId; + //} + + // 1. FIX DATE OVERRULE: + // Use the date from the frontend (assigndate) if it exists. + // Only set to DateTime.Now if the frontend sent null/empty. + if (itemmovement.sendDate == default || itemmovement.sendDate == null) { - itemmovement.LastStore = inventoryMaster.StoreId; + itemmovement.sendDate = DateTime.Now; + } + itemmovement.Date = DateTime.Now; // Log the entry creation time + + // 2. FIX STORE/USER OVERRULE: + // Only auto-fill LastStore if: + // - The frontend didn't send one (null) + // - We have a LastUser to look up + // - AND it is NOT a "user" assignment (because for 'user', we want it to stay NULL) + if (itemmovement.LastStore == null && itemmovement.LastUser != null && itemmovement.ToUser == null) + { + var inventoryMaster = await _centralDbContext.InventoryMasters + .FirstOrDefaultAsync(i => i.UserId == itemmovement.LastUser); + if (inventoryMaster != null) + { + itemmovement.LastStore = inventoryMaster.StoreId; + } } @@ -1184,8 +1209,11 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory return NotFound("Item movement record not found."); } - updatedList.ToUser = receiveMovement.ToUser; - updatedList.ToStore = receiveMovement.ToStore; + updatedList.ToUser = receiveMovement.ToUser ?? updatedList.ToUser; + updatedList.ToStore = receiveMovement.ToStore ?? updatedList.ToStore; + updatedList.ToStation = receiveMovement.ToStation ?? updatedList.ToStation; + //updatedList.ToUser = receiveMovement.ToUser; + //updatedList.ToStore = receiveMovement.ToStore; updatedList.LatestStatus = receiveMovement.LatestStatus; updatedList.receiveDate = receiveMovement.receiveDate; updatedList.Remark = receiveMovement.Remark; @@ -1934,10 +1962,45 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory try { + // --- CORE FIX: ISOLATE THE CURRENT LIFECYCLE --- + // 1. Find the last time the item cycle was "reset" (Returned or newly Registered) + var lastResetMovement = await _centralDbContext.ItemMovements + .Where(m => m.ItemId == returnMovement.ItemId && (m.Action == "StockIn" || m.Action == "Register")) + .OrderByDescending(m => m.Id) + .FirstOrDefaultAsync(); + + // Get the ID where the current cycle started (if 0, it means it has never been returned/registered) + int currentCycleStartId = lastResetMovement != null ? lastResetMovement.Id : 0; + + // 2. Find the VERY FIRST movement AFTER the cycle started. + // This bypasses the middleman (User/Store who gave it to the station) and + // accurately grabs the Inventory Master who originally assigned it at the beginning of the chain. + var originalMasterMovement = await _centralDbContext.ItemMovements + .Where(m => m.ItemId == returnMovement.ItemId + && m.Id > currentCycleStartId + && (m.LastUser != null || m.LastStore != null)) + .OrderBy(m => m.Id) // <-- Get the FIRST movement of the current chain + .FirstOrDefaultAsync(); + + if (originalMasterMovement != null) + { + // Set the destination back to the original Inventory Master + returnMovement.ToUser = originalMasterMovement.LastUser; + returnMovement.ToStore = originalMasterMovement.LastStore; + } + else + { + // Fallback just in case history isn't found + returnMovement.ToUser = null; + returnMovement.ToStore = null; + } + if (!string.IsNullOrEmpty(returnMovement.ConsignmentNote)) { var findUniqueCode = _centralDbContext.Items.Include(i => i.Product).FirstOrDefault(r => r.ItemID == returnMovement.ItemId); - var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == returnMovement.LastUser); + + // Fetch the user data for the file name based on the destination we just assigned + var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == returnMovement.ToUser); var bytes = Convert.FromBase64String(returnMovement.ConsignmentNote); @@ -2026,27 +2089,28 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == stationMovement.LastUser); var bytes = Convert.FromBase64String(stationMovement.ConsignmentNote); - string filePath = ""; - var uniqueAbjad = new string(Enumerable.Range(0, 8).Select(_ => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[new Random().Next(36)]).ToArray()); + // FIX: Safely handle null users and clean invalid file characters (just like your Return API) + string safeUserName = string.Join("_", (findUniqueUser?.FullName ?? "Station").Split(Path.GetInvalidFileNameChars())); + string safeModelNo = string.Join("_", (findUniqueCode?.Product?.ModelNo ?? "NA").Split(Path.GetInvalidFileNameChars())); - if (IsImage(bytes)) + string extension = IsPdf(bytes) ? ".pdf" : ".jpg"; + if (!IsImage(bytes) && !IsPdf(bytes)) return BadRequest("Unsupported file format."); + + string relativePath = $"media/inventory/itemmovement/{safeUserName}_{safeModelNo}_{uniqueAbjad}_Station{extension}"; + string folderPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "media", "inventory", "itemmovement"); + string filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", relativePath); + + if (!Directory.Exists(folderPath)) { - filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.jpg"); - stationMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.jpg"; - } - else if (IsPdf(bytes)) - { - filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "Return.pdf"); - stationMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.pdf"; - } - else - { - return BadRequest("Unsupported file format."); + Directory.CreateDirectory(folderPath); } await System.IO.File.WriteAllBytesAsync(filePath, bytes); + + // Save the safe relative path to the database + stationMovement.ConsignmentNote = "/" + relativePath; } _centralDbContext.ItemMovements.Add(stationMovement);