diff --git a/Areas/Inventory/Models/ItemModel.cs b/Areas/Inventory/Models/ItemModel.cs index 721709d..7ef56ae 100644 --- a/Areas/Inventory/Models/ItemModel.cs +++ b/Areas/Inventory/Models/ItemModel.cs @@ -45,6 +45,8 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models public virtual ProductModel? Product { get; set; } [ForeignKey("MovementId")] public virtual ItemMovementModel? Movement { get; set; } + public required DateTime? CreatedDate { get; set; } + public required DateTime? ModifiedDate { get; set; } } diff --git a/Areas/Inventory/Views/InventoryMaster/ItemRegistration.cshtml b/Areas/Inventory/Views/InventoryMaster/ItemRegistration.cshtml index 517d479..f8639fd 100644 --- a/Areas/Inventory/Views/InventoryMaster/ItemRegistration.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/ItemRegistration.cshtml @@ -23,7 +23,7 @@
- @*
+ @*
Loading...
@@ -99,7 +99,6 @@
-
@@ -112,7 +111,6 @@
-
@@ -123,7 +121,6 @@
-
@@ -157,7 +154,6 @@
- -
+ @* Submit and Reset Buttons *@
@@ -416,6 +412,8 @@ invoiceNo: null, invoiceDate: null, partNumber: null, + createdDate: null, // Add new data property + modifiedDate: null, // Add new data property products: [], depts: [], suppliers: [ @@ -440,7 +438,9 @@ thisQRInfo: { uniqueID: null, departmentName: null, + productShortName: null, // Added for print QR serialNumber: null, + partNumber: null, // Added for print QR endWDate: null, }, items: [], @@ -490,7 +490,7 @@ }, }, methods: { - async addItem() { + async addItem() { // List of required fields const requiredFields = ['DefaultPrice', 'DONo', 'DODate', 'warranty', 'invoiceNo', 'invoiceDate']; @@ -528,6 +528,9 @@ CreatedByUserId: this.currentUser.id, TeamType: this.selectedTeamType, PartNumber: this.partNumber, + // Add these lines for CreatedDate and ModifiedDate + CreatedDate: null, // Send as null, API will set it to DateTime.Now + ModifiedDate: null // Send as null, API will set it to DateTime.Now }; try { @@ -580,139 +583,147 @@ }, - initiateTable() { - self = this; - this.itemDatatable = $('#itemDatatable').DataTable({ - "data": this.items, - "columns": [ - { - "title": "Unique Id", - "data": "uniqueID", - "createdCell": function (td, cellData, rowData, row, col) { - // Assign a unique ID to the element - $(td).attr('id', `qr${cellData}`); - }, - }, - { - "title": "Print", - "data": "uniqueID", - "render": function (data, type, full, meta) { - var printButton = ``; - return printButton; - }, - "className": "align-middle", - }, - { - "title": "Item Short Name", - "data": "productShortName", - }, - { - "title": "Serial Number", - "data": "serialNumber", - }, - { - "title": "Part Number", - "data": "partNumber", - }, - { - "title": "Category", - "data": "category", - }, - { - "title": "Quantity", - "data": "quantity", - }, - { - "title": "Supplier", - "data": "supplier", - }, - { - "title": "Purchase Date", - "data": "purchaseDate", - }, - { - "title": "Price(RM)", - "data": (row) => parseFloat(row.convertPrice).toFixed(2), - }, - // --- REMOVE --- - // { - // "title": "Register Date", - // "data": "createDate", // This is the problematic line - // }, - // -------------- - { - "title": "Warranty Until", - "data": "warranty", - "render": function (data, type, full, meta) { - if (data > 0) { return full.endWDate } - else { return data } - } - }, - { - "title": "Location", - "data": "currentUser", - "render": function (data, type, full, meta) { - currentUser = data ?? null; - currentStore = full.currentStore ?? 'N/A'; - currentStation = full.currentStation ?? 'N/A'; - return `User: ${currentUser}
- Store: ${currentStore}
- Station: ${currentStation}` - } - }, - { - "title": "Edit", - "data": "itemID", - "render": function (data) { - var editButton = ``; - return editButton; - }, - "className": "align-middle", - }, - { - "title": "Delete", - "data": "itemID", - "render": function (data) { - var deleteButton = ``; - return deleteButton; - }, - "className": "align-middle", - } - ], - responsive: true, - drawCallback: function (settings) { - setTimeout(() => { - const api = this.api(); - api.rows().every(function () { - const data = this.data(); - const containerId = `qr${data.uniqueID}`; - const container = document.getElementById(containerId); - - if (!container) { - return; - } - - container.innerHTML = ""; - container.append(data.uniqueID); - - // Ensure qrString is valid before generating QR code - if (!data.qrString) { - return; - } - - // Generate QR Code - new QRCode(container, { - text: data.qrString, - width: 100, - height: 100, - colorDark: "#000000", - colorLight: "#ffffff", - correctLevel: QRCode.CorrectLevel.M - }); - }); - }, 100); // Small delay to ensure elements exist + initiateTable() { + self = this; + this.itemDatatable = $('#itemDatatable').DataTable({ + "data": this.items, + "columns": [ + { + "title": "Unique Id", + "data": "uniqueID", + "createdCell": function (td, cellData, rowData, row, col) { + // Assign a unique ID to the element + $(td).attr('id', `qr${cellData}`); + }, + }, + { + "title": "Print", + "data": "uniqueID", + "render": function (data, type, full, meta) { + var printButton = ``; + return printButton; + }, + "className": "align-middle", + }, + { + "title": "Item Short Name", + "data": "productShortName", + }, + { + "title": "Serial Number", + "data": "serialNumber", + }, + { + "title": "Part Number", + "data": "partNumber", + }, + { + "title": "Category", + "data": "category", + }, + { + "title": "Quantity", + "data": "quantity", + }, + { + "title": "Supplier", + "data": "supplier", + }, + { + "title": "Purchase Date", + "data": "purchaseDate", + }, + { + "title": "Price(RM)", + "data": (row) => parseFloat(row.convertPrice).toFixed(2), + }, + { + "title": "Warranty Until", + "data": "warranty", + "render": function (data, type, full, meta) { + if (data > 0) { return full.endWDate } + else { return data } } - }) + }, + { + "title": "Created Date", + "data": "createdDate", // Map to the new createdDate field + "render": function (data) { + return data ? new Date(data).toLocaleString() : ''; // Format date for display + } + }, + { + "title": "Modified Date", + "data": "modifiedDate", // Map to the new modifiedDate field + "render": function (data) { + return data ? new Date(data).toLocaleString() : ''; // Format date for display + } + }, + { + "title": "Location", + "data": "currentUser", + "render": function (data, type, full, meta) { + currentUser = data ?? null; + currentStore = full.currentStore ?? 'N/A'; + currentStation = full.currentStation ?? 'N/A'; + return `User: ${currentUser}
+ Store: ${currentStore}
+ Station: ${currentStation}` + } + }, + { + "title": "Edit", + "data": "itemID", + "render": function (data) { + var editButton = ``; + return editButton; + }, + "className": "align-middle", + }, + { + "title": "Delete", + "data": "itemID", + "render": function (data) { + var deleteButton = ``; + return deleteButton; + }, + "className": "align-middle", + } + ], + responsive: true, + drawCallback: function (settings) { + setTimeout(() => { + const api = this.api(); + api.rows().every(function () { + const data = this.data(); + const containerId = `qr${data.uniqueID}`; + const container = document.getElementById(containerId); + + if (!container) { + return; + } + + container.innerHTML = ""; + container.append(data.uniqueID); + + // Ensure qrString is valid before generating QR code + if (!data.qrString) { + return; + } + + // Generate QR Code + new QRCode(container, { + text: data.qrString, + width: 100, + height: 100, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.M + }); + }); + }, 100); // Small delay to ensure elements exist + } + }) // Attach click event listener to the edit buttons @@ -755,12 +766,10 @@ async fetchItem() { try { - // const token = localStorage.getItem('token'); // Get the token from localStorage const response = await fetch('/InvMainAPI/ItemList', { - method: 'POST', // Specify the HTTP method + method: 'POST', headers: { - 'Content-Type': 'application/json', // Set content type - // 'Authorization': `Bearer ${token}` // Include the token in the headers + 'Content-Type': 'application/json', } }); @@ -781,12 +790,10 @@ async fetchProducts() { try { - // const token = localStorage.getItem('token'); // Get the token from localStorage const response = await fetch('/InvMainAPI/ProductList', { - method: 'POST', // Specify the HTTP method + method: 'POST', headers: { - 'Content-Type': 'application/json', // Set content type - // 'Authorization': `Bearer ${token}` // Include the token in the headers + 'Content-Type': 'application/json', } }); @@ -804,7 +811,7 @@ async fetchCompanies() { try { const response = await fetch('/AdminAPI/GetDepartmentWithCompanyList', { - method: 'POST', // Specify the HTTP method + method: 'POST', headers: { 'Content-Type': 'application/json' } @@ -823,7 +830,7 @@ async fetchSuppliers() { try { const response = await fetch('/InvMainAPI/SupplierList', { - method: 'POST', // Specify the HTTP method + method: 'POST', headers: { 'Content-Type': 'application/json' } @@ -831,7 +838,7 @@ if (!response.ok) { throw new Error('Failed to fetch suppliers'); } - this.suppliers = await response.json(); // Get the full response object + this.suppliers = await response.json(); } catch (error) { console.error('Error fetching suppliers:', error); @@ -839,15 +846,13 @@ }, async fetchCurrencyData() { try { - // Fetch currency data from the API - const response = await fetch('https://openexchangerates.org/api/currencies.json'); // Example API + const response = await fetch('https://openexchangerates.org/api/currencies.json'); this.currencies = await response.json(); } catch (error) { console.error('Error fetching currency data:', error); } }, calculateWarrantyEndDate() { - // Check if DODate and warranty are valid if (!this.DODate || isNaN(Date.parse(this.DODate))) { this.EndWDate = null; return; @@ -856,7 +861,6 @@ const DODates = new Date(this.DODate); const warrantyMonth = parseInt(this.warranty); - // Ensure warranty is a valid number if (!isNaN(warrantyMonth)) { DODates.setMonth(DODates.getMonth() + warrantyMonth); this.EndWDate = DODates.toISOString().split('T')[0]; @@ -865,8 +869,10 @@ } }, convertCurrency() { - // Your currency conversion logic here console.log('Selected currency:', this.currency); + const total = this.DefaultPrice * this.currencyRate; + this.convertPrice = total.toFixed(2); + this.DefaultPrice = this.DefaultPrice }, resetForm() { this.company = ''; @@ -890,23 +896,19 @@ this.invoiceDate = null; this.selectedProduct = ''; this.selectedSupplier = ''; - // this.selectedCompany = this.currentUserCompanyDept.companyId; - // this.selectedDepartment = ''; this.selectedTeamType = ''; this.partNumber = null; - }, - - // FRONT END FUNCTIONS - //----------------------// - //Calculate Total Price - convertCurrency() { - const total = this.DefaultPrice * this.currencyRate; - this.convertPrice = total.toFixed(2); - this.DefaultPrice = this.DefaultPrice - // .replace(/[^0-9.]/g, '') // Remove non-numeric characters except decimal points - // .replace(/(\..*)\..*/g, '$1') // Allow only one decimal point - // .replace(/^(\d*\.\d{0,2})\d*$/, '$1'); // Limit to two decimal places + this.createdDate = null; // Clear createdDate on form reset + this.modifiedDate = null; // Clear modifiedDate on form reset + // Handle currentUserCompanyDept for company and department dropdowns + if (this.currentUserCompanyDept) { + this.selectedCompany = this.currentUserCompanyDept.companyId; + this.selectedDepartment = this.currentUserCompanyDept.departmentId; + } else { + this.selectedCompany = ''; + this.selectedDepartment = ''; + } }, async deleteItem(itemId) { @@ -924,7 +926,6 @@ if (result.success) { alert(result.message); - // Remove the row from DataTables this.itemDatatable .row($(`.delete-btn[data-id="${itemId}"]`).closest('tr')) .remove() @@ -941,14 +942,12 @@ }, async editItem(itemID) { - // Cari data item yang dipilih const item = this.items.find(s => s.itemID === itemID); if (!item) { alert('Error', 'Item not found!', 'warning'); return; } - // Populate form fields this.itemID = itemID; this.selectedCompany = item.companyId; this.selectedDepartment = item.departmentId; @@ -970,14 +969,15 @@ this.invoiceDate = item.invoiceDate.split('/').reverse().join('-'); this.selectedTeamType = item.teamType; this.partNumber = item.partNumber; - // Tunjukkan modal edit + this.createdDate = item.createdDate; // Populate createdDate + this.modifiedDate = item.modifiedDate; // Populate modifiedDate + $('#registerItemModal').modal('show'); this.addSection = false; this.editSection = true; }, - async submitEditItem() { - + async submitEditItem() { // List of required fields const requiredFields = ['DefaultPrice', 'DONo', 'DODate', 'warranty', 'invoiceNo', 'invoiceDate']; @@ -993,7 +993,7 @@ this.serialNumber = ""; } const formData = { - itemId : this.itemID, + itemId: this.itemID, CompanyId: this.selectedCompany, DepartmentId: this.selectedDepartment, ProductId: this.selectedProduct, @@ -1014,10 +1014,12 @@ InvoiceDate: this.invoiceDate, TeamType: this.selectedTeamType, PartNumber: this.partNumber, + // ADD THESE TWO LINES: + CreatedDate: this.createdDate, // Include the existing CreatedDate + ModifiedDate: this.modifiedDate // Include the existing ModifiedDate (API will update it) }; try { - // Additional specific checks if (this.showSerialNumber) { this.quantity = 0; @@ -1048,7 +1050,6 @@ return; } - // Refresh station list and reset form alert('Success!', 'Item form has been successfully submitted.', 'success'); this.fetchItem(); this.resetForm(); @@ -1070,11 +1071,9 @@ return; } - // Safely set image content - const sanitizedImgSrc = encodeURI(imgSrc); // Sanitize the URL - container.innerHTML = `QR Code`; + const sanitizedImgSrc = encodeURI(imgSrc); + container.innerHTML = `QR Code`; - // Fetch QR information const qrInfo = this.getPrintedQR(uniqueQR); if (!qrInfo) { console.error("QR Info not found."); @@ -1084,7 +1083,7 @@ this.thisQRInfo = qrInfo; this.thisQRInfo.imgSrc = sanitizedImgSrc this.thisQRInfo.imgContainer = container.innerHTML - $(`#QrItemModal`).modal('show'); // Show modal + $(`#QrItemModal`).modal('show'); } catch (error) { console.error("Error generating QR code:", error); @@ -1118,60 +1117,47 @@ return null; } - //FIX ERROR const foundItem = this.items.find(item => String(item.uniqueID).trim() === String(uniqueID).trim()); return foundItem ? JSON.parse(JSON.stringify(foundItem)) : null; }, printQRInfo() { - // Create a virtual DOM element const virtualElement = document.createElement('div'); - virtualElement.style.width = '360px '; // Match label size for 2 inches at 203 DPI + virtualElement.style.width = '360px '; virtualElement.style.height = '180px'; virtualElement.style.position = 'absolute'; - virtualElement.style.left = '-9999px'; // Position offscreen to avoid rendering on the main UI - // virtualElement.style.border = '1px solid #000'; // Optional: Add a border for debugging dimensions + virtualElement.style.left = '-9999px'; - // Populate the virtual DOM with content virtualElement.innerHTML = ` -
-
-
+
-
-
${this.thisQRInfo.imgContainer}
-
${this.thisQRInfo.uniqueID}
+
+
+
+
${this.thisQRInfo.imgContainer}
+
${this.thisQRInfo.uniqueID}
+
+
+
+
+
+
${this.thisQRInfo.departmentName}
+
${this.thisQRInfo.productShortName}
+
${this.thisQRInfo.serialNumber ?? "-"}
+
${this.thisQRInfo.partNumber}
+
-
-
-
${this.thisQRInfo.departmentName}
-
${this.thisQRInfo.productShortName}
-
${this.thisQRInfo.serialNumber ?? "-"}
-
${this.thisQRInfo.partNumber}
-
-
-
-
- `; + `; - // Append the virtual DOM to the body (temporarily) document.body.appendChild(virtualElement); - // Wait for the font to be loaded (important for custom fonts like OCR-A) document.fonts.load('1em "OCR A"').then(() => { - // Use html2canvas to convert the virtual DOM to an image html2canvas(virtualElement, { - scale: 1, // Increase scale for sharper images + scale: 1, }).then((canvas) => { - // Convert the canvas to an image const imgData = canvas.toDataURL('image/png'); - // Open the image in a new tab for preview (optional) - // const newWindow = window.open(); - // newWindow.location.href = imgData; - // console.log(imgData) - // Use printJS to print the image printJS({ printable: imgData, type: 'image', @@ -1186,20 +1172,27 @@ ` }); - // Remove the virtual DOM from the body after use document.body.removeChild(virtualElement); }).catch((error) => { console.error("Error generating image:", error); - // Remove the virtual DOM if an error occurs document.body.removeChild(virtualElement); }); }).catch((error) => { console.error("Error loading font:", error); - // Remove the virtual DOM if font loading fails document.body.removeChild(virtualElement); }); }, + // New helper method to format dates + formatDate(dateString) { + if (!dateString) return ''; + const date = new Date(dateString); + // Check if date is valid + if (isNaN(date.getTime())) { + return ''; + } + return date.toLocaleString(); // You can adjust the format as needed + } }, }); diff --git a/Areas/Inventory/Views/InventoryMaster/SupplierRegistration.cshtml b/Areas/Inventory/Views/InventoryMaster/SupplierRegistration.cshtml index 0434e0e..c40cd78 100644 --- a/Areas/Inventory/Views/InventoryMaster/SupplierRegistration.cshtml +++ b/Areas/Inventory/Views/InventoryMaster/SupplierRegistration.cshtml @@ -206,8 +206,16 @@ addSection: false, } }, - mounted(){ + mounted() { this.fetchSuppliers(); + + // Add this event delegation + document.addEventListener('click', (event) => { + if (event.target.classList.contains('delete-btn')) { + const supplierId = event.target.getAttribute('data-id'); + this.deleteSupplier(supplierId); + } + }); }, methods: { async fetchSuppliers() { @@ -239,6 +247,7 @@ console.error('Error fetching suppliers:', error); this.errorMessage = 'Error: ' + error.message; } + }, async addSupplier() { $('#loadingModal').modal('show'); @@ -339,14 +348,12 @@ "title": "Delete", "data": "supplierId", "render": function (data) { - var deleteButton = ``; - return deleteButton; + return ``; }, } ], responsive: true, - - }) + }); // Attach click event listener to the edit buttons $('#supplierDatatable tbody').off('click', '.edit-btn').on('click', '.edit-btn', function () { @@ -354,13 +361,7 @@ self.editSupplier(supplierId); }); - // Attach click event listener to the delete buttons - $('#supplierDatatable tbody').on('click', '.delete-btn', function () { - const supplierId = $(this).data('id'); - self.deleteSupplier(supplierId); - }); - this.loading = false; }, async editSupplier(supplierId) { @@ -431,32 +432,35 @@ this.errorMessage = 'Error: ' + error.message; } }, - async deleteSupplier(supplierId) { - if (!confirm("Are you sure you want to delete this supplier?")) { - return; - } - try { - const response = await fetch(`/InvMainAPI/DeleteSupplier/${supplierId}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ supplierId }) - }); + async deleteSupplier(supplierId) { + if (!confirm("Are you sure you want to delete this supplier?")) { + return; + } - if (!response.ok) { - const errorData = await response.json(); - console.error('Error response:', errorData); - this.errorMessage = 'Error: ' + (errorData.message || 'Unknown error'); - return; - } - - this.fetchSuppliers(); - } catch (error) { - console.error('Error deleting supplier:', error); - this.errorMessage = 'Error: ' + error.message; + try { + const response = await fetch(`/InvMainAPI/DeleteSupplier/${supplierId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' } - }, + // REMOVED the body completely + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('Error response:', errorData); + alert('Error', errorData.message || 'Failed to delete supplier', 'error'); + return; + } + + // Success case + alert('Success', 'Supplier deleted successfully', 'success'); + this.fetchSuppliers(); + } catch (error) { + console.error('Error deleting supplier:', error); + alert('Error', 'An error occurred while deleting the supplier', 'error'); + } + }, hideEditSection() { this.resetForm(); this.editSection = false; diff --git a/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs b/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs index 58f3ea7..ea4e784 100644 --- a/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs +++ b/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers { [Area("OTcalculate")] - [Authorize] + [Authorize(Policy = "RoleModulePolicy")] public class ApprovalDashboardController : Controller { public IActionResult Approval() diff --git a/Areas/OTcalculate/Controllers/HrDashboardController.cs b/Areas/OTcalculate/Controllers/HrDashboardController.cs index af97c1f..43d9d00 100644 --- a/Areas/OTcalculate/Controllers/HrDashboardController.cs +++ b/Areas/OTcalculate/Controllers/HrDashboardController.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers { [Area("OTcalculate")] - [Authorize] + [Authorize(Policy = "RoleModulePolicy")] public class HrDashboardController : Controller { diff --git a/Areas/OTcalculate/Controllers/OvertimeController.cs b/Areas/OTcalculate/Controllers/OvertimeController.cs index 1b3b5d4..f168e0d 100644 --- a/Areas/OTcalculate/Controllers/OvertimeController.cs +++ b/Areas/OTcalculate/Controllers/OvertimeController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc; namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers { [Area("OTcalculate")] - [Authorize] + [Authorize(Policy = "RoleModulePolicy")] public class OvertimeController : Controller { public IActionResult OtRegister() diff --git a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml index 61f8a87..141ba32 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml @@ -57,7 +57,7 @@
- +
@@ -128,8 +128,9 @@ return; } let rateValue = parseFloat(this.rate); - if (isNaN(rateValue)) { - alert("Please enter a valid salary."); + + if (isNaN(rateValue) || rateValue < 1) { + alert("Please enter a valid salary (minimum 1)."); // Update the alert message return; } const payload = this.selectedRates.map(userId => ({ UserId: userId, RateValue: rateValue.toFixed(2) })); diff --git a/Controllers/API/Inventory/InvMainAPI.cs b/Controllers/API/Inventory/InvMainAPI.cs index 27c7998..95eed8a 100644 --- a/Controllers/API/Inventory/InvMainAPI.cs +++ b/Controllers/API/Inventory/InvMainAPI.cs @@ -382,6 +382,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory } #endregion Supplier + #region Item [HttpPost("ItemList")] @@ -402,7 +403,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory var userRole = await _userManager.GetRolesAsync(user); var isAdmin = userRole.Contains("SystemAdmin") || userRole.Contains("SuperAdmin") || userRole.Contains("Finance"); List itemList = new List(); - + // Get the item list if (isAdmin) { @@ -437,14 +438,15 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory } - // Get the departments list (DepartmentId references Departments) var departments = await _centralDbContext.Departments.ToListAsync(); // Now join items with users and departments manually var itemListWithDetails = itemList.Select(item => new { - // createDate = createDateDict.ContainsKey(item.ItemID) ? createDateDict[item.ItemID].ToString("dd/MM/yyyy HH:mm:ss") : null, + // Add CreatedDate and ModifiedDate here for the response + item.CreatedDate, // Include CreatedDate + item.ModifiedDate, // Include ModifiedDate item.ItemID, item.UniqueID, item.CompanyId, @@ -468,19 +470,16 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory EndWDate = item.EndWDate.ToString("dd/MM/yyyy"), InvoiceDate = item.InvoiceDate?.ToString("dd/MM/yyyy"), item.Department?.DepartmentName, - //RegisterDate = createDate, - CreatedBy =item.CreatedBy!.UserName, + CreatedBy = item.CreatedBy!.UserName, item.Product!.ProductName, item.Product!.ProductShortName, item.Product!.Category, - //CurrentUser = item.Movement?.FromUser?.UserName, FromUser = item.Movement?.ToUser, FromStore = item.Movement?.ToStore, FromStation = item.Movement?.ToStation, CurrentUser = item.Movement?.FromUser?.UserName, CurrentStore = item.Movement?.FromStore?.StoreName, CurrentStation = item.Movement?.FromStation?.StationName, - QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.UniqueID}" // Generate QR String }).ToList(); @@ -519,23 +518,24 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory if (product.Category == "Disposable") { - // For disposable items, the quantity product is increased by the item's quantity product.QuantityProduct += item.Quantity; - item.SerialNumber = null; // Ensure serial number is null for disposables + item.SerialNumber = null; } else if (product.Category == "Asset" || product.Category == "Part") { - // For assets or parts, each added item counts as 1 to the product quantity - // and the item's quantity should be set to 1 if it's not already, or based on specific logic. - // Assuming for Assets/Parts, each 'AddItem' call registers one unit of that product. - product.QuantityProduct = (product.QuantityProduct ?? 0) + 1; // Increment by 1 for Asset/Part - item.Quantity = 1; // Force quantity to 1 for Assets/Parts if it's not already + product.QuantityProduct = (product.QuantityProduct ?? 0) + 1; + item.Quantity = 1; } - _centralDbContext.Items.Add(item); - _centralDbContext.Products.Update(product); // Update the product quantity + // Set CreatedDate when adding a new item + item.CreatedDate = DateTime.Now; + // Explicitly set ModifiedDate to null for a new item + item.ModifiedDate = null; // <--- CHANGE IS HERE - await _centralDbContext.SaveChangesAsync(); // This generates the auto-incremented ItemID + _centralDbContext.Items.Add(item); + _centralDbContext.Products.Update(product); + + await _centralDbContext.SaveChangesAsync(); ItemMovementModel itemMovement = new ItemMovementModel { @@ -545,7 +545,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory LastStore = inventoryMaster.StoreId, LastUser = inventoryMaster.UserId, LatestStatus = "Ready To Deploy", - Quantity = item.Quantity, // Use the item's quantity for movement record + Quantity = item.Quantity, Action = "Register", Date = DateTime.Now, MovementComplete = true, @@ -554,7 +554,6 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory _centralDbContext.ItemMovements.Add(itemMovement); await _centralDbContext.SaveChangesAsync(); - // Fetch the generated ItemID and MovementId for the response var savedItem = await _centralDbContext.Items.FirstOrDefaultAsync(i => i.ItemID == item.ItemID); var savedMovement = await _centralDbContext.ItemMovements.FirstOrDefaultAsync(im => im.Id == itemMovement.Id); @@ -598,6 +597,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory savedItem.EndWDate, savedItem.InvoiceDate, savedItem.PartNumber, + savedItem.CreatedDate, + savedItem.ModifiedDate // This will now be null for new items }; return Json(updatedItem); } @@ -619,73 +620,61 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory try { var savedItem = await _centralDbContext.Items - .Include(i => i.Product) // Include product to get the original category - .FirstOrDefaultAsync(i => i.ItemID == item.ItemID); + .Include(i => i.Product) + .FirstOrDefaultAsync(i => i.ItemID == item.ItemID); if (savedItem == null) { return NotFound(new { success = false, message = "Item not found" }); } - var product = await _centralDbContext.Products.FirstOrDefaultAsync(p => p.ProductId == item.ProductId) ?? throw new Exception("Product not found"); - var originalProduct = savedItem.Product; // Get the original product associated with the item + var oldProduct = savedItem.Product; + var newProduct = await _centralDbContext.Products.FirstOrDefaultAsync(p => p.ProductId == item.ProductId) ?? throw new Exception("New Product not found"); - // Calculate quantity changes for Disposable category - if (originalProduct?.Category == "Disposable" && product.Category == "Disposable") + // Quantity adjustment logic based on category changes + if (oldProduct?.Category == "Disposable" && newProduct.Category == "Disposable") { int quantityDifference = item.Quantity - savedItem.Quantity; - product.QuantityProduct += quantityDifference; + newProduct.QuantityProduct += quantityDifference; } - else if (originalProduct?.Category != "Disposable" && product.Category == "Disposable") + else if ((oldProduct?.Category == "Asset" || oldProduct?.Category == "Part") && newProduct.Category == "Disposable") { - // Category changed from Asset/Part to Disposable: add new quantity - product.QuantityProduct += item.Quantity; - // Optionally, if the old item was counted as 1 in QuantityProduct (for Asset/Part), decrement it. - if (originalProduct?.QuantityProduct > 0) // Ensure it doesn't go below zero + if (oldProduct != null && oldProduct.QuantityProduct > 0) { - var oldProduct = await _centralDbContext.Products.FirstOrDefaultAsync(p => p.ProductId == originalProduct.ProductId); - if (oldProduct != null) + oldProduct.QuantityProduct = (oldProduct.QuantityProduct ?? 0) - 1; + _centralDbContext.Products.Update(oldProduct); + } + newProduct.QuantityProduct += item.Quantity; + } + else if (oldProduct?.Category == "Disposable" && (newProduct.Category == "Asset" || newProduct.Category == "Part")) + { + if (oldProduct != null) + { + oldProduct.QuantityProduct = (oldProduct.QuantityProduct ?? 0) - savedItem.Quantity; + if (oldProduct.QuantityProduct < 0) oldProduct.QuantityProduct = 0; + _centralDbContext.Products.Update(oldProduct); + } + newProduct.QuantityProduct = (newProduct.QuantityProduct ?? 0) + 1; + item.Quantity = 1; + } + else if ((oldProduct?.Category == "Asset" || oldProduct?.Category == "Part") && (newProduct.Category == "Asset" || newProduct.Category == "Part")) + { + if (savedItem.ProductId != item.ProductId) + { + if (oldProduct != null && oldProduct.QuantityProduct > 0) { oldProduct.QuantityProduct = (oldProduct.QuantityProduct ?? 0) - 1; _centralDbContext.Products.Update(oldProduct); } + newProduct.QuantityProduct = (newProduct.QuantityProduct ?? 0) + 1; } - } - else if (originalProduct?.Category == "Disposable" && product.Category != "Disposable") - { - // Category changed from Disposable to Asset/Part: subtract old quantity - product.QuantityProduct -= savedItem.Quantity; - // Add 1 to the new product's quantity if it's now an Asset/Part - product.QuantityProduct = (product.QuantityProduct ?? 0) + 1; - } - // If both are Asset/Part, no quantity change needed for ProductModel based on ItemModel quantity - // If ProductId changes for Asset/Part, you need to decrement old product and increment new product by 1 - else if ((originalProduct?.Category == "Asset" || originalProduct?.Category == "Part") && (product.Category == "Asset" || product.Category == "Part")) - { - if (savedItem.ProductId != item.ProductId) // Product changed for Asset/Part - { - // Decrement old product quantity - var oldProduct = await _centralDbContext.Products.FirstOrDefaultAsync(p => p.ProductId == savedItem.ProductId); - if (oldProduct != null) - { - oldProduct.QuantityProduct = (oldProduct.QuantityProduct ?? 0) - 1; - _centralDbContext.Products.Update(oldProduct); - } - - // Increment new product quantity - product.QuantityProduct = (product.QuantityProduct ?? 0) + 1; - } + item.Quantity = 1; } - // Handle serial number for Disposable - if (product.Category == "Disposable") + // Handle serial number based on the new product's category + if (newProduct.Category == "Disposable") { item.SerialNumber = null; } - else if (product.Category == "Asset" || product.Category == "Part") - { - item.Quantity = 1; // Enforce quantity to 1 for Assets/Parts - } - // Update savedItem properties from item model savedItem.DefaultPrice = item.DefaultPrice; @@ -708,29 +697,21 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory savedItem.InvoiceNo = item.InvoiceNo; savedItem.InvoiceDate = item.InvoiceDate; savedItem.PartNumber = item.PartNumber; - savedItem.UniqueID = item.PartNumber; // This might need to be re-evaluated for UniqueID generation if PartNumber can change - _centralDbContext.Products.Update(product); // Update the product with the new quantity - _centralDbContext.Items.Update(savedItem); // Update the item + // Set ModifiedDate when editing an item + savedItem.ModifiedDate = DateTime.Now; - await _centralDbContext.SaveChangesAsync(); - - // Regenerate UniqueID if necessary (e.g., if PartNumber is part of it and changed) - // Note: The UniqueID generation logic seems to re-use PartNumber in your code, - // which might be fine, but if UniqueID should be truly unique and immutable - // after creation, you might reconsider updating it on edit. + // Re-generate UniqueID based on updated fields (if PartNumber is part of it) var companyDepartment = await GetDepartmentWithCompany(savedItem.CompanyId, savedItem.DepartmentId); - var itemProduct = _centralDbContext.Products.Where(p => p.ProductId == savedItem.ProductId).FirstOrDefault(); - - string? companyInitial = companyDepartment!.CompanyName?.ToString().Substring(0, 1).ToUpper(); - string? departmentInitial = companyDepartment!.DepartmentName?.ToString().Substring(0, 1).ToUpper(); string? deptCode = companyDepartment!.DepartmentCode?.ToString(); - char? initialCategory = itemProduct!.Category.ToString().Substring(0, 1).ToUpper().FirstOrDefault(); - string? productId = itemProduct!.ProductId.ToString("D3"); + char? initialCategory = newProduct!.Category.ToString().Substring(0, 1).ToUpper().FirstOrDefault(); + string? productId = newProduct!.ProductId.ToString("D3"); string? itemIdString = savedItem.ItemID.ToString("D5"); - savedItem.UniqueID = $"{deptCode}{initialCategory}{productId}{itemIdString}".ToUpper(); // Re-generate UniqueID based on updated fields + savedItem.UniqueID = $"{deptCode}{initialCategory}{productId}{itemIdString}".ToUpper(); + _centralDbContext.Products.Update(newProduct); _centralDbContext.Items.Update(savedItem); + await _centralDbContext.SaveChangesAsync(); var updatedItem = new @@ -754,6 +735,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory savedItem.EndWDate, savedItem.InvoiceDate, savedItem.PartNumber, + savedItem.CreatedDate, // Include in response + savedItem.ModifiedDate // Include in response }; return Json(updatedItem); } @@ -805,23 +788,24 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory public async Task GetItem(string id) { var item = await _centralDbContext.Items - .Include("CreatedBy") - .Include("Department") - .Include("Product") - .Include("Movement") - .Include(i => i.Movement) - .ThenInclude(m => m!.FromStore) - .Include(i => i.Movement) - .ThenInclude(m => m!.FromStation) - .Include(i => i.Movement) - .ThenInclude(m => m!.FromUser) - .Include(i => i.Movement) - .ThenInclude(m => m!.NextStore) - .Include(i => i.Movement) - .ThenInclude(m => m!.NextStation) - .Include(i => i.Movement) - .ThenInclude(m => m!.NextUser).FirstOrDefaultAsync(i => i.UniqueID == id); - if (item == null){ + .Include("CreatedBy") + .Include("Department") + .Include("Product") + .Include("Movement") + .Include(i => i.Movement) + .ThenInclude(m => m!.FromStore) + .Include(i => i.Movement) + .ThenInclude(m => m!.FromStation) + .Include(i => i.Movement) + .ThenInclude(m => m!.FromUser) + .Include(i => i.Movement) + .ThenInclude(m => m!.NextStore) + .Include(i => i.Movement) + .ThenInclude(m => m!.NextStation) + .Include(i => i.Movement) + .ThenInclude(m => m!.NextUser).FirstOrDefaultAsync(i => i.UniqueID == id); + if (item == null) + { return NotFound(new { success = false, message = "Item not found" }); } var singleItem = new @@ -866,13 +850,15 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory ToStoreName = item.Movement?.NextStore?.StoreName, ToStation = item.Movement?.ToStation, ToStationName = item.Movement?.NextStation?.StationName, - item.Movement?.ToOther, + item.Movement?.ToOther, item.Movement?.LatestStatus, item.Movement?.LastUser, item.Movement?.LastStore, item.Movement?.MovementComplete, remark = item.Movement?.Remark, - QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.UniqueID}" // Generate QR String + QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.UniqueID}", // Generate QR String + item.CreatedDate, // Include in response + item.ModifiedDate // Include in response }; return Json(singleItem); } diff --git a/Controllers/API/OvertimeAPI.cs b/Controllers/API/OvertimeAPI.cs index 6a92e65..12c7166 100644 --- a/Controllers/API/OvertimeAPI.cs +++ b/Controllers/API/OvertimeAPI.cs @@ -169,6 +169,12 @@ namespace PSTW_CentralSystem.Controllers.API { foreach (var rate in rates) { + // Change from rate.RateValue < 0 to rate.RateValue < 1 + if (rate.RateValue < 1) + { + return BadRequest("Salary cannot be less than 1."); // Update the error message + } + var existingRate = await _centralDbContext.Rates .FirstOrDefaultAsync(r => r.UserId == rate.UserId);