This commit is contained in:
Naz 2026-03-05 13:00:25 +08:00
parent 815769fab7
commit 0636109ac9
8 changed files with 395 additions and 219 deletions

View File

@ -1044,44 +1044,43 @@
this.loading = true; this.loading = true;
await this.fetchUser(); await this.fetchUser();
try { try {
// const token = localStorage.getItem('token'); // Get the token from localStorage
const response = await fetch('/InvMainAPI/ItemMovementList', { const response = await fetch('/InvMainAPI/ItemMovementList', {
method: 'POST', // Specify the HTTP method method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' }
'Content-Type': 'application/json', // Set content type
// 'Authorization': `Bearer ${token}` // Include the token in the headers
}
}); });
if (!response.ok) { if (!response.ok) throw new Error('Failed to fetch item');
throw new Error('Failed to fetch item');
} const data = await response.json();
if(this.currentRole == "Super Admin"){ if(this.currentRole == "Super Admin"){
this.items = await response.json(); this.items = data;
this.initAllTables();
} else { } else {
const data = await response.json();
const myStationIds = this.stations
.filter(s => s.stationPicID === this.currentUser.id)
.map(s => s.stationId);
this.items = data.filter(item => this.items = data.filter(item =>
item.lastUser === this.currentUser.id || item.lastUser === this.currentUser.id ||
item.toUser === this.currentUser.id || item.toUser === this.currentUser.id ||
item.lastStore === this.currentUser.store || (item.lastStation && myStationIds.includes(item.lastStation)) ||
item.toStore === this.currentUser.store (item.toStation && myStationIds.includes(item.toStation)) ||
item.lastStore === this.currentUser.store ||
item.toStore === this.currentUser.store
); );
this.initAllTables();
} }
this.initAllTables();
if (this.itemDatatable) { if (this.itemDatatable) {
this.itemDatatable.clear().destroy(); this.itemDatatable.clear().destroy();
} }
this.initiateTable(); this.initiateTable();
} }
catch (error) { catch (error) {
console.error('Error fetching item:', error); console.error('Error fetching item:', error);
} }
this.loading = false; this.loading = false;
}, },
async fetchUser() { async fetchUser() {
@ -1164,10 +1163,10 @@
{ title: "Action", data: "action" }, { 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: "From User", data: "lastUserName" },
{ title: "Last User", data: "toUserName" },
{ title: "From Station", data: "lastStationName" }, { title: "From Station", data: "lastStationName" },
{ title: "Last Station", data: "toStationName" },
{ title: "From Store", data: "lastStoreName" }, { title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" }, { title: "Last Store", data: "toStoreName" },
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Product Category", data: "productCategory" }, { title: "Product Category", data: "productCategory" },
@ -1189,10 +1188,10 @@
{ title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) }, { title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) },
{ title: "Action", data: "action" }, { title: "Action", data: "action" },
{ title: "From User", data: "lastUserName" }, { title: "From User", data: "lastUserName" },
{ title: "Last User", data: "toUserName" },
{ title: "From Station", data: "lastStationName" }, { title: "From Station", data: "lastStationName" },
{ title: "Last Station", data: "toStationName" },
{ title: "From Store", data: "lastStoreName" }, { title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" }, { title: "Last Store", data: "toStoreName" },
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" }, { title: "Latest Status", data: "latestStatus" },
@ -1205,12 +1204,16 @@
}); });
this.assignStationDatatable = $("#assignStationDatatable").DataTable({ this.assignStationDatatable = $("#assignStationDatatable").DataTable({
data: this.items.filter((m) => m.action === "Assign" ), data: this.items.filter((m) =>
m.action === "Assign" ||
m.action === "Change" ||
m.toStation !== null
),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Product Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate", render: this.formatDate.bind(this) }, { title: "Assign Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Action", data: "action" }, { title: "Action", data: "action" },
{ title: "Station User PIC", data: "lastUserName" }, { title: "Station User PIC", data: "lastUserName" },
{ title: "From Station", data: "lastStationName" }, { title: "From Station", data: "lastStationName" },

View File

@ -337,7 +337,7 @@
<div class="form-group row d-flex align-items-center"> <div class="form-group row d-flex align-items-center">
<label class="col-sm-2 col-form-label">Quantity:</label> <label class="col-sm-2 col-form-label">Quantity:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" class="form-control" v-model="quantity" required /> <input type="number" class="form-control" v-model="quantity" min="1" required />
</div> </div>
</div> </div>
@ -426,11 +426,11 @@
// Show the modal // Show the modal
$('.modal').modal('hide'); $('.modal').modal('hide');
}); });
$('.submit-button').on('click', function () { // $('.submit-button').on('click', function () {
// Show the modal // Show the modal
$('#rejectModal').modal('hide'); // $('#rejectModal').modal('hide');
$('#approveModal').modal('hide'); // $('#approveModal').modal('hide');
}); // });
}); });
const app = Vue.createApp({ const app = Vue.createApp({
data() { data() {
@ -536,7 +536,7 @@
// Prepare data as JSON (No file upload) // Prepare data as JSON (No file upload)
const requestDatas = { const requestDatas = {
ProductId: this.productId, ProductId: this.productId,
StationId: this.stationId, // StationId: this.stationId,
UserId: this.userId, UserId: this.userId,
ProductCategory: this.productCategory, ProductCategory: this.productCategory,
RequestQuantity: this.quantity, RequestQuantity: this.quantity,
@ -720,8 +720,8 @@
"columns": [ "columns": [
{ "title": "Request ID", "data": "requestID" }, { "title": "Request ID", "data": "requestID" },
{ "title": "Product", "data": "productName", "render": renderDocument }, { "title": "Product", "data": "productName", "render": renderDocument },
{ "title": "From Store", "data": "fromStoreItem" }, { "title": "From Store", "data": "fromStoreName" },
{ "title": "Assign Store", "data": "assignStoreItem" }, { "title": "Assign Store", "data": "assignStoreName" },
{ "title": "Product Category", "data": "productCategory" }, { "title": "Product Category", "data": "productCategory" },
{ "title": "Request Quantity", "data": "requestQuantity" }, { "title": "Request Quantity", "data": "requestQuantity" },
{ "title": "Document/Picture", "data": "document", "render": renderDocument }, { "title": "Document/Picture", "data": "document", "render": renderDocument },
@ -748,8 +748,8 @@
{ "title": "Action", "data": "requestID", "render": renderActionButtons, "className": "align-middle" }, { "title": "Action", "data": "requestID", "render": renderActionButtons, "className": "align-middle" },
{ "title": "Product", "data": "productName", "render": renderDocument }, { "title": "Product", "data": "productName", "render": renderDocument },
{ "title": "Requested by User", "data": "userName" }, { "title": "Requested by User", "data": "userName" },
{ "title": "From Store", "data": "fromStoreItem" }, { "title": "From Store", "data": "fromStoreName" },
{ "title": "Assign Store", "data": "assignStoreItem" }, { "title": "Assign Store", "data": "assignStoreName" },
{ "title": "Product Category", "data": "productCategory" }, { "title": "Product Category", "data": "productCategory" },
{ "title": "Request Quantity", "data": "requestQuantity" }, { "title": "Request Quantity", "data": "requestQuantity" },
{ "title": "Document/Picture", "data": "document", "render": renderDocument }, { "title": "Document/Picture", "data": "document", "render": renderDocument },
@ -766,8 +766,8 @@
{ "title": "Request ID", "data": "requestID" }, { "title": "Request ID", "data": "requestID" },
{ "title": "Product", "data": "productName", "render": renderDocument }, { "title": "Product", "data": "productName", "render": renderDocument },
{ "title": "Requested by User", "data": "userName" }, { "title": "Requested by User", "data": "userName" },
{ "title": "From Store", "data": "fromStoreItem" }, { "title": "From Store", "data": "fromStoreName" },
{ "title": "Assign Store", "data": "assignStoreItem" }, { "title": "Assign Store", "data": "assignStoreName" },
{ "title": "Product Category", "data": "productCategory" }, { "title": "Product Category", "data": "productCategory" },
{ "title": "Request Quantity", "data": "requestQuantity" }, { "title": "Request Quantity", "data": "requestQuantity" },
{ "title": "Document/Picture", "data": "document", "render": renderDocument }, { "title": "Document/Picture", "data": "document", "render": renderDocument },
@ -976,107 +976,79 @@
} }
}, },
async approveRequest() { async approveRequest() {
// if (!confirm("Are you sure you want to approve this request?")) {
// return; if (!this.approveremark || this.approveremark.trim() === "") {
// } alert("Please enter a remark before approving this request.");
return;
}
const formData = { const formData = {
RemarkMasterInv: this.rejectremark, RemarkMasterInv: this.approveremark,
};
}; let requestID = this.currentrequestID;
this.loading = true;
let requestID = this.currentrequestID;
try { try {
const response = await fetch(`/InvMainAPI/ApproveRequest/${requestID}`, { const response = await fetch(`/InvMainAPI/ApproveRequest/${requestID}`, {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json',
},
body: JSON.stringify(formData) body: JSON.stringify(formData)
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
alert(result.message); alert(result.message);
this.approveremark = "";
//static update this.fetchRequest();
const row = $(`.approve-btn[data-id="${requestID}"]`).closest('tr');
let rowData = this.requestDatatable.row(row).data();
// Update the status and remark
rowData.status = "Approved";
// Remove row from requestDatatable
this.requestDatatable.row(row).remove().draw();
// Add updated row to settledrequestDatatable
this.settledrequestDatatable.row.add(rowData).draw();
} else { } else {
alert(result.message); alert(result.message);
} }
} }
catch (error) { catch (error) {
console.error("Error approving request:", error); console.error("Error approving request:", error);
// alert("An error occurred while approving the request.");
} }
finally { finally {
this.loading = false; this.loading = false;
$('#approveModal').modal('hide');
} }
}, },
async rejectRequest() { async rejectRequest() {
if (!this.rejectremark || this.rejectremark.trim() === "") {
alert("Please enter a remark before rejecting this request.");
return;
}
const formData = { const formData = {
RemarkMasterInv: this.rejectremark, RemarkMasterInv: this.rejectremark,
};
};
let requestID = this.currentrequestID; let requestID = this.currentrequestID;
this.loading = true;
try { try {
const response = await fetch(`/InvMainAPI/RejectRequest/${requestID}`, { const response = await fetch(`/InvMainAPI/RejectRequest/${requestID}`, {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json',
// 'Authorization': `Bearer ${this.token}`
},
body: JSON.stringify(formData) body: JSON.stringify(formData)
}); });
if (response.ok) { if (response.ok) {
// If the form submission was successful, display a success message alert('Request has been Rejected.');
// alert('Success!', 'Request has been Rejected.', 'success'); this.rejectremark = "";
this.fetchRequest();
const row = $(`.approve-btn[data-id="${requestID}"]`).closest('tr');
let rowData = this.requestDatatable.row(row).data();
// Update the status and remark
rowData.status = "Rejected";
rowData.remarkMasterInv = this.rejectremark;
// Remove row from requestDatatable
this.requestDatatable.row(row).remove().draw();
// Add updated row to settledrequestDatatable
this.settledrequestDatatable.row.add(rowData).draw();
} else { } else {
throw new Error('Failed to submit form.'); throw new Error('Failed to submit form.');
} }
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
// Displaying error message
alert('Inventory PSTW Error', `An error occurred: ${error.message}`, 'error'); alert('Inventory PSTW Error', `An error occurred: ${error.message}`, 'error');
} }
finally { finally {
this.loading = false; this.loading = false;
$('#rejectModal').modal('hide');
} }
}, },
async rejectRequestModal(requestID, remark) { async rejectRequestModal(requestID, remark) {

View File

@ -280,8 +280,10 @@
<button type="button" v-on:click="ReturnMessage" class="btn btn-warning m-2" style="width: 200px; padding: 10px;"> <button type="button" v-on:click="ReturnMessage" class="btn btn-warning m-2" style="width: 200px; padding: 10px;">
<i class="fas fa-undo me-2"></i>Return Item <i class="fas fa-undo me-2"></i>Return Item
</button> </button>
<button type="button" v-on:click="StationMessage" class="btn btn-primary m-2" style="width: 200px; padding: 10px;"> <button type="button" v-on:click="StationMessage" class="btn btn-primary m-2" style="width: 200px; padding: 10px;">
<i class="fas fa-broadcast-tower me-2"></i>Deploy To Station <i class="fas fa-broadcast-tower me-2"></i>
{{ (thisItem?.currentStationId || thisItem?.toStationId) ? "Change Station" : "Deploy To Station" }}
</button> </button>
</div> </div>
</div> </div>
@ -353,11 +355,24 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">To User:</label> <label class="col-sm-4 col-form-label">To User:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="dropdown"> <div class="custom-searchable-dropdown" style="position: relative;" @@click.stop>
<select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedUser" required style="width: 100%;"> <div class="input-group">
<option class="btn-light" value="" disabled selected>Select User</option> <input type="text" class="form-control" v-model="userSearchQuery"
<option class="btn-light" v-for="(user, index) in userlist" :key="index" :value="user.id">{{user.fullName}}</option> placeholder="Search and Select User..."
</select> @@focus ="userDropdownOpen = true" />
<button type="button" class="btn btn-primary" @@click ="userDropdownOpen = !userDropdownOpen">
{{ userDropdownOpen ? '▲' : '▼' }}
</button>
</div>
<ul class="list-group shadow-sm" v-if="userDropdownOpen" style="position: absolute; z-index: 1000; width: 100%; max-height: 200px; overflow-y: auto;">
<li class="list-group-item text-muted" v-if="searchedUserList.length === 0">No users found</li>
<li class="list-group-item list-group-item-action"
v-for="(user, index) in searchedUserList" :key="index"
@@click ="selectUser(user.id, user.fullName)"
style="cursor: pointer;">
{{ user.fullName }}
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -411,12 +426,24 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">To Station:</label> <label class="col-sm-4 col-form-label">To Station:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="dropdown"> <div class="custom-searchable-dropdown" style="position: relative;" @@click.stop>
<select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedStation" required style="width: 100%;"> <div class="input-group">
<option class="btn-light" value="" disabled selected>Select Station</option> <input type="text" class="form-control" v-model="stationSearchQuery"
<option class="btn-light" v-for="(station, index) in stationlist" :key="index" :value="station.stationId">{{station.stationName}}</option> placeholder="Search and Select Station..."
</select> @@focus ="stationDropdownOpen = true" />
<button type="button" class="btn btn-primary" @@click ="stationDropdownOpen = !stationDropdownOpen">
{{ stationDropdownOpen ? '▲' : '▼' }}
</button>
</div>
<ul class="list-group shadow-sm" v-if="stationDropdownOpen" style="position: absolute; z-index: 1000; width: 100%; max-height: 200px; overflow-y: auto;">
<li class="list-group-item text-muted" v-if="searchedStationListForDropdown.length === 0">No stations found</li>
<li class="list-group-item list-group-item-action"
v-for="(station, index) in searchedStationListForDropdown" :key="index"
@@click ="selectStation(station.stationId, station.stationName)"
style="cursor: pointer;">
{{ station.stationName }}
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -538,11 +565,24 @@
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">To Supplier:</label> <label class="col-sm-4 col-form-label">To Supplier:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="dropdown"> <div class="custom-searchable-dropdown" style="position: relative;" @@click.stop>
<select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedSupplier" required style="width: 100%;"> <div class="input-group">
<option class="btn-light" value="" disabled selected>Select Supplier</option> <input type="text" class="form-control" v-model="supplierSearchQuery"
<option class="btn-light" v-for="(supplier, index) in supplierlist" :key="index" :value="supplier.supplierCompName">{{supplier.supplierCompName}}</option> placeholder="Search and Select Supplier..."
</select> @@focus ="supplierDropdownOpen = true" />
<button type="button" class="btn btn-primary" @@click ="supplierDropdownOpen = !supplierDropdownOpen">
{{ supplierDropdownOpen ? '▲' : '▼' }}
</button>
</div>
<ul class="list-group shadow-sm" v-if="supplierDropdownOpen" style="position: absolute; z-index: 1000; width: 100%; max-height: 200px; overflow-y: auto;">
<li class="list-group-item text-muted" v-if="searchedSupplierList.length === 0">No suppliers found</li>
<li class="list-group-item list-group-item-action"
v-for="(supplier, index) in searchedSupplierList" :key="index"
@@click ="selectSupplier(supplier.supplierCompName)"
style="cursor: pointer;">
{{ supplier.supplierCompName }}
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -664,7 +704,7 @@
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<label class="col-sm-4 col-form-label">Remark:</label> <label class="col-sm-4 col-form-label">Remark:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" class="form-control" v-model="remark" required /> <input type="text" class="form-control" v-model="remark"/>
</div> </div>
</div> </div>
<div class="form-group row mb-3"> <div class="form-group row mb-3">
@ -683,43 +723,51 @@
</div> </div>
@* Model Deploy to Station *@ @* Model Deploy to Station *@
<div class="modal fade" id="stationMessageModal" tabindex="-1" role="dialog"> <div class="modal fade" id="stationMessageModal" tabindex="-1" role="dialog" aria-labelledby="stationModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Deploy to Station</h5> <h5 class="modal-title" id="stationModalLabel">
<button type="button" class="close closeModal" data-dismiss="modal"> {{ (thisItem?.currentStationId || thisItem?.toStationId) ? "Change Station" : "Deploy to Station" }}
<span>&times;</span> </h5>
<button type="button" class="close closeModal" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form v-on:submit.prevent="confirmDeployStation"> <form v-on:submit.prevent="confirmDeployStation">
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<label class="col-sm-4 col-form-label">Select Station:</label> <label class="col-sm-4 col-form-label">Deploy Station : </label>
<div class="col-sm-8"> <div class="col-sm-8">
<select class="form-select" v-model="selectedStation" required> <select class="btn btn-primary dropdown-toggle col-md-10" v-model="selectedStation" required>
<option value="" disabled>-- Select Station --</option> <option class="btn-light" value="" disabled selected>-- Select Station --</option>
<option v-for="station in filteredStationList" :key="station.stationId" :value="station.stationId"> <option v-if="filteredStationList.length === 0" class="btn-light" disabled>No Station Assigned to You</option>
<option class="btn-light" v-for="station in filteredStationList" :key="station.stationId" :value="station.stationId">
{{ station.stationName }} {{ station.stationName }}
</option> </option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<label class="col-sm-4 col-form-label">Remark:</label> <label class="col-sm-4 col-form-label">Remark:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" class="form-control" v-model="remark" placeholder="Optional" /> <input type="text" class="form-control" v-model="remark" placeholder="Optional" />
</div> </div>
</div> </div>
<div class="form-group row mb-3"> <div class="form-group row mb-3">
<label class="col-sm-4 col-form-label">Consignment Note:</label> <label class="col-sm-4 col-form-label">Consignment Note:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/*, application/pdf" /> <input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/*, application/pdf" />
</div> </div>
</div> </div>
<div class="text-center">
<button type="submit" class="btn btn-success">Confirm Deployment</button> <button type="submit" class="btn btn-primary">
</div> {{ (thisItem?.currentStationId || thisItem?.toStationId) ? "Confirm Change" : "Confirm Deployment" }}
</button>
</form> </form>
</div> </div>
</div> </div>
@ -794,7 +842,16 @@
videoInputDevices: [], videoInputDevices: [],
selectedCameraId: null, selectedCameraId: null,
scanStartTime: null, scanStartTime: null,
scanTime: null scanTime: null,
supplierDropdownOpen: false,
supplierSearchQuery: "",
userDropdownOpen: false,
userSearchQuery: "",
stationDropdownOpen: false,
stationSearchQuery: "",
} }
}, },
mounted() { mounted() {
@ -875,6 +932,22 @@
station.stationPicID == this.currentUser.id station.stationPicID == this.currentUser.id
); );
}, },
searchedUserList() {
if (!this.userlist) return [];
if (!this.userSearchQuery) return this.userlist;
return this.userlist.filter(u => u.fullName.toLowerCase().includes(this.userSearchQuery.toLowerCase()));
},
searchedSupplierList() {
if (!this.supplierlist) return [];
if (!this.supplierSearchQuery) return this.supplierlist;
return this.supplierlist.filter(s => s.supplierCompName.toLowerCase().includes(this.supplierSearchQuery.toLowerCase()));
},
searchedStationListForDropdown() {
if (!this.stationlist) return [];
if (!this.stationSearchQuery) return this.stationlist;
return this.stationlist.filter(s => s.stationName.toLowerCase().includes(this.stationSearchQuery.toLowerCase()));
}
}, },
methods: { methods: {
// Split Url dapatkan unique ID Je // Split Url dapatkan unique ID Je
@ -904,6 +977,27 @@
this.error = message; this.error = message;
}, },
selectUser(userId, userName) {
this.selectedUser = userId;
this.userSearchQuery = userName;
this.userDropdownOpen = false;
},
selectSupplier(supplierName) {
this.selectedSupplier = supplierName;
this.supplierSearchQuery = supplierName;
this.supplierDropdownOpen = false;
},
selectStation(stationId, stationName) {
this.selectedStation = stationId;
this.stationSearchQuery = stationName;
this.stationDropdownOpen = false;
},
closeAllDropdowns() {
this.userDropdownOpen = false;
this.supplierDropdownOpen = false;
this.stationDropdownOpen = false;
},
//Setting Camera //Setting Camera
async onCameraReady(videoElement) { async onCameraReady(videoElement) {
@ -1189,8 +1283,8 @@
}); });
if (response.ok) { if (response.ok) {
this.resetForm(); this.resetScanner();
window.location.href = '/Inventory/InventoryMaster/ItemMovement'; // window.location.href = '/Inventory/InventoryMaster/ItemMovement';
} else { } else {
throw new Error('Failed to submit form.'); throw new Error('Failed to submit form.');
} }
@ -1276,7 +1370,9 @@
this.cancelRemark = ""; this.cancelRemark = "";
this.quantity = 1; // Reset quantity when the form is reset this.quantity = 1; // Reset quantity when the form is reset
this.maxQuantity = null; // Clear maxQuantity on form reset this.maxQuantity = null; // Clear maxQuantity on form reset
this.userSearchQuery = "";
this.supplierSearchQuery = "";
this.stationSearchQuery = "";
}, },
@ -1585,7 +1681,7 @@
alert('Success! Item has been deployed to the station.'); alert('Success! Item has been deployed to the station.');
$('#stationMessageModal').modal('hide'); $('#stationMessageModal').modal('hide');
this.resetScanner(); this.resetScanner();
window.location.href = '/Inventory/InventoryMaster/ItemMovement'; // window.location.href = '/Inventory/InventoryMaster/ItemMovement';
} else { } else {
const errorText = await response.text(); const errorText = await response.text();
throw new Error(errorText || 'Failed to deploy station.'); throw new Error(errorText || 'Failed to deploy station.');

View File

@ -963,6 +963,70 @@
} }
}, },
// initAllTables() {
// if (this.itemMovementNotCompleteDatatable) {
// this.itemMovementNotCompleteDatatable.destroy();
// }
// if (this.itemMovementCompleteDatatable) {
// this.itemMovementCompleteDatatable.destroy();
// }
// if (this.assignStationDatatable) {
// this.assignStationDatatable.destroy();
// }
// Get latest movement per uniqueID after filtering
// function getLatestMovements(data) {
// let latestMovements = {};
// data.forEach(movement => {
// let id = movement.uniqueID;
// if (!latestMovements[id] || latestMovements[id].id < movement.id) {
// latestMovements[id] = movement;
// }
// });
// return Object.values(latestMovements);
// }
// Filter movements based on conditions
// function filterMovements(movements) {
// let stopIndex = movements.findIndex(m =>
// m.toOther === 'Return' && m.movementComplete == 1
// );
// let nextIndex = movements.findIndex(m =>
// m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
// );
// if (stopIndex !== -1) {
// movements = movements.slice(0, stopIndex);
// }
// if (nextIndex !== -1) {
// movements = movements.slice(0, nextIndex);
// }
// return movements;
// }
// let latestMovements = getLatestMovements(this.itemMovements);
// let notCompleteData = [];
// let completeData = [];
// let completeStationData = [];
// latestMovements.forEach(movement => {
// let filteredMovements = filterMovements([movement]);
// if (filteredMovements.length > 0) {
// if (movement.movementComplete == 0) {
// notCompleteData.push(movement);
// } else if (movement.movementComplete == 1 && movement.action !== "Assign") {
// completeData.push(movement);
// } else {
// completeStationData.push(movement);
// }
// }
// });
initAllTables() { initAllTables() {
if (this.itemMovementNotCompleteDatatable) { if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.destroy(); this.itemMovementNotCompleteDatatable.destroy();
@ -974,55 +1038,39 @@
this.assignStationDatatable.destroy(); this.assignStationDatatable.destroy();
} }
// Get latest movement per uniqueID after filtering
function getLatestMovements(data) {
let latestMovements = {};
data.forEach(movement => {
let id = movement.uniqueID;
if (!latestMovements[id] || latestMovements[id].id < movement.id) {
latestMovements[id] = movement;
}
});
return Object.values(latestMovements);
}
// Filter movements based on conditions
function filterMovements(movements) {
let stopIndex = movements.findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
return movements;
}
let latestMovements = getLatestMovements(this.itemMovements);
let notCompleteData = []; let notCompleteData = [];
let completeData = []; let completeData = [];
let completeStationData = []; let completeStationData = [];
latestMovements.forEach(movement => {
let filteredMovements = filterMovements([movement]); let latestMovementsMap = {};
if (filteredMovements.length > 0) { this.itemMovements.forEach(movement => {
let id = movement.uniqueID;
if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) {
latestMovementsMap[id] = movement;
}
// Push ALL station history to the Assign Station table!
if (movement.movementComplete == 1 && (movement.action === "Assign" || movement.action === "Change" || movement.toStation !== null)) {
completeStationData.push(movement);
}
});
let latestMovements = Object.values(latestMovementsMap);
latestMovements.forEach(movement => {
let isReturned = movement.toOther === 'Return' && movement.movementComplete == 1;
let isReady = movement.latestStatus === 'Ready To Deploy' && movement.movementComplete == 1;
if (!isReturned && !isReady) {
if (movement.movementComplete == 0) { if (movement.movementComplete == 0) {
notCompleteData.push(movement); notCompleteData.push(movement);
} else if (movement.movementComplete == 1 && movement.action !== "Assign") { } else if (movement.movementComplete == 1 && movement.action !== "Assign") {
completeData.push(movement); completeData.push(movement);
} else {
completeStationData.push(movement);
} }
} }
}); });
@ -1038,10 +1086,10 @@
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) }, { title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "From User", data: "lastUserName" }, { title: "From User", data: "lastUserName" },
{ title: "Last User", data: "toUserName" },
{ title: "From Station", data: "lastStationName" }, { title: "From Station", data: "lastStationName" },
{ title: "Last Station", data: "toStationName" },
{ title: "From Store", data: "lastStoreName" }, { title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" }, { title: "Last Store", data: "toStoreName" },
{ title: "Quantity", data: "quantity" }, { title: "Quantity", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile }, { title: "Note", data: "consignmentNote", render: renderFile },
@ -1063,10 +1111,10 @@
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" }, { title: "Latest Status", data: "latestStatus" },
{ title: "From User", data: "lastUserName" }, { title: "From User", data: "lastUserName" },
{ title: "Last User", data: "toUserName" },
{ title: "From Station", data: "lastStationName" }, { title: "From Station", data: "lastStationName" },
{ title: "Last Station", data: "toStationName" },
{ title: "From Store", data: "lastStoreName" }, { title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" }, { title: "Last Store", data: "toStoreName" },
{ title: "Qty", data: "quantity" }, { title: "Qty", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile }, { title: "Note", data: "consignmentNote", render: renderFile },

View File

@ -196,7 +196,7 @@
<div class="form-group row d-flex align-items-center"> <div class="form-group row d-flex align-items-center">
<label class="col-sm-2 col-form-label">Quantity:</label> <label class="col-sm-2 col-form-label">Quantity:</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="number" class="form-control" v-model="quantity" required /> <input type="number" class="form-control" v-model="quantity" min="1" required />
</div> </div>
</div> </div>

View File

@ -114,7 +114,9 @@
<div class="col-12 mb-3"> <div class="col-12 mb-3">
<p class="h5 fw-bold"> <p class="h5 fw-bold">
<i class="fas fa-user-tie me-2 text-secondary"></i>PIC: <i class="fas fa-user-tie me-2 text-secondary"></i>PIC:
<span class="text-muted">{{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }}</span> <span class="text-muted">
{{ currentUser?.fullName || 'N/A' }}
</span>
@* <span class="text-muted">{{thisItem.currentUserFullName}}</span> *@ @* <span class="text-muted">{{thisItem.currentUserFullName}}</span> *@
</p> </p>
</div> </div>
@ -123,7 +125,7 @@
<div class="col-12 mb-3"> <div class="col-12 mb-3">
<p class="h5 fw-bold"> <p class="h5 fw-bold">
<i class="fas fa-user-tie me-2 text-secondary"></i>Station: <i class="fas fa-user-tie me-2 text-secondary"></i>Station:
<span class="text-muted">{{thisItem.currentStation || 'No Station Deploy (Self Assign)' }}</span> <span class="text-muted">{{ thisItem.toStationName || 'No Station Deploy (Self Assign)' }}</span>
</p> </p>
</div> </div>
@ -135,14 +137,25 @@
<i class="fas fa-info-circle me-2"></i>Receiver Information <i class="fas fa-info-circle me-2"></i>Receiver Information
</h5> </h5>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<!-- User -->
<li class="list-group-item"> <li class="list-group-item" v-if="thisItem.toUser || thisItem.toUserFullName">
<div class="d-flex justify-content-between align-items-start"> <div class="d-flex justify-content-between align-items-start">
<span class="fw-bold"> <span class="fw-bold">
<i class="fas fa-user me-2 text-secondary"></i>User: <i class="fas fa-user me-2 text-secondary"></i>User:
</span> </span>
<span class="text-muted text-end" style="max-width: 70%; word-wrap: break-word;"> <span class="text-muted text-end" style="max-width: 70%; word-wrap: break-word;">
{{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }} {{ thisItem.toUserFullName || thisItem.toUserName || 'N/A' }}
</span>
</div>
</li>
<li class="list-group-item" v-if="thisItem.toStationId || thisItem.toStation">
<div class="d-flex justify-content-between align-items-start">
<span class="fw-bold">
<i class="fas fa-charging-station me-2 text-secondary"></i>Station:
</span>
<span class="text-muted text-end">
{{ thisItem.toStationName || thisItem.toStation || 'N/A' }}
</span> </span>
</div> </div>
</li> </li>
@ -159,13 +172,24 @@
<i class="fas fa-info-circle me-2"></i>Sender Information <i class="fas fa-info-circle me-2"></i>Sender Information
</h5> </h5>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<!-- User -->
<li class="list-group-item"> <li class="list-group-item" v-if="thisItem.lastStation">
<div class="d-flex justify-content-between align-items-start">
<span class="fw-bold">
<i class="fas fa-charging-station me-2 text-secondary"></i>Station:
</span>
<span class="text-muted text-end">
{{ thisItem.currentStation || 'N/A' }}
</span>
</div>
</li>
<li class="list-group-item" v-else-if="thisItem.lastUser">
<div class="d-flex justify-content-between align-items-start"> <div class="d-flex justify-content-between align-items-start">
<span class="fw-bold"> <span class="fw-bold">
<i class="fas fa-user me-2 text-secondary"></i>User: <i class="fas fa-user me-2 text-secondary"></i>User:
</span> </span>
<span class="text-muted text-end" style="max-width: 70%; word-wrap: break-word;"> <span class="text-muted text-end">
{{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }} {{ thisItem.currentUserFullName || thisItem.currentUser || 'N/A' }}
</span> </span>
</div> </div>
@ -480,7 +504,7 @@
ToOther: "Delivered", ToOther: "Delivered",
Action: "Assign", Action: "Assign",
Quantity: this.thisItem.quantity || 1, Quantity: this.thisItem.movementQuantity || 1,
Remark: this.remark, Remark: this.remark,
ConsignmentNote: this.consignmentNote, ConsignmentNote: this.consignmentNote,
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),

View File

@ -50,7 +50,7 @@ namespace PSTW_CentralSystem.Controllers.API
.Select(u => new .Select(u => new
{ {
id = u.Id, id = u.Id,
email = u.NormalizedEmail, fullName = u.FullName,
company = u.Department!.Company!.CompanyName, company = u.Department!.Company!.CompanyName,
department = u.Department, department = u.Department,
role = userRole, role = userRole,

View File

@ -970,6 +970,9 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
ToStoreName = item.Movement?.NextStore?.StoreName, ToStoreName = item.Movement?.NextStore?.StoreName,
LastStation = item.Movement?.LastStation, LastStation = item.Movement?.LastStation,
ToStationName = item.Movement?.NextStation?.StationName, ToStationName = item.Movement?.NextStation?.StationName,
ToUserFullName = item.Movement?.NextUser?.FullName,
item.Movement?.ToOther, item.Movement?.ToOther,
item.Movement?.LatestStatus, item.Movement?.LatestStatus,
item.Movement?.ToUser, item.Movement?.ToUser,
@ -1330,6 +1333,12 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return NotFound("Item movement record not found."); return NotFound("Item movement record not found.");
} }
// Fetch the StationIds where the user is the PIC
var userStationIds = await _centralDbContext.Stations
.Where(s => s.StationPicID == user.Id)
.Select(s => s.StationId)
.ToListAsync();
var itemMovementUser = await _centralDbContext.ItemMovements var itemMovementUser = await _centralDbContext.ItemMovements
.Include(i => i.Item) .Include(i => i.Item)
.ThenInclude(i => i.Product) .ThenInclude(i => i.Product)
@ -1339,45 +1348,47 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
.Include(i => i.NextStore) .Include(i => i.NextStore)
.Include(i => i.NextStation) .Include(i => i.NextStation)
.Include(i => i.NextUser) .Include(i => i.NextUser)
.Where(i => i.LastUser == user.Id || i.ToUser == user.Id) .Where(i => i.LastUser == user.Id ||
i.ToUser == user.Id ||
(i.LastStation != null && userStationIds.Contains(i.LastStation.Value)) ||
(i.ToStation != null && userStationIds.Contains(i.ToStation.Value)))
.ToListAsync(); .ToListAsync();
return Json(itemMovementUser.Select(i => new return Json(itemMovementUser.Select(i => new
{ {
i.Id, i.Id,
i.ItemId, i.ItemId,
i.LastStation, i.LastStation,
i.LastStore, i.LastStore,
i.LastUser, i.LastUser,
UniqueID = i.Item?.UniqueID, UniqueID = i.Item?.UniqueID,
ProductName = i.Item?.Product?.ProductName, ProductName = i.Item?.Product?.ProductName,
ProductImage = i.Item?.Product?.ImageProduct, ProductImage = i.Item?.Product?.ImageProduct,
LastUserName = i.FromUser?.FullName, LastUserName = i.FromUser?.FullName,
LastStoreName = i.FromStore?.StoreName, LastStoreName = i.FromStore?.StoreName,
LastStationName = i.FromStation?.StationName, LastStationName = i.FromStation?.StationName,
ToUserName = i.NextUser?.FullName, ToUserName = i.NextUser?.FullName,
ToStoreName = i.NextStore?.StoreName, ToStoreName = i.NextStore?.StoreName,
ToStationName = i.NextStation?.StationName, ToStationName = i.NextStation?.StationName,
i.ToOther, i.ToOther,
i.sendDate, i.sendDate,
i.Action, i.Action,
i.Quantity, i.Quantity,
i.Remark, i.Remark,
i.ConsignmentNote, i.ConsignmentNote,
i.Date, i.Date,
i.ToUser, i.ToUser,
i.ToStore, i.ToStore,
i.ToStation, i.ToStation,
i.LatestStatus, i.LatestStatus,
i.receiveDate, i.receiveDate,
i.MovementComplete i.MovementComplete
})); }));
} }
catch (Exception ex) catch (Exception ex)
{ {
return BadRequest(ex.Message); return BadRequest(ex.Message);
} }
} }
#endregion #endregion
@ -1622,12 +1633,20 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
var masterDeptId = masterUser.departmentId; var masterDeptId = masterUser.departmentId;
var myStoreIds = await _centralDbContext.InventoryMasters
.Where(im => im.UserId == currentUserId)
.Select(im => im.StoreId)
.ToListAsync();
// 4. Fetch requests where the Requester belongs to the same department // 4. Fetch requests where the Requester belongs to the same department
var itemRequestList = await _centralDbContext.Requests var itemRequestList = await _centralDbContext.Requests
.Include(i => i.Product) .Include(i => i.Product)
.Include(i => i.User) .Include(i => i.User)
.Include(i => i.Station) .Include(i => i.Station)
.Where(i => i.User.departmentId == masterDeptId) .Include(i => i.Store)
.Include(i => i.Stores)
.Where(i => i.User.departmentId == masterDeptId ||
(i.fromStoreItem.HasValue && myStoreIds.Contains(i.fromStoreItem.Value)))
.ToListAsync(); .ToListAsync();
return Json(itemRequestList.Select(i => new return Json(itemRequestList.Select(i => new
@ -1649,7 +1668,9 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.remarkMasterInv, i.remarkMasterInv,
i.remarkUser, i.remarkUser,
i.assignStoreItem, i.assignStoreItem,
i.fromStoreItem i.fromStoreItem,
fromStoreName = i.Store?.StoreName ?? "N/A",
assignStoreName = i.Stores?.StoreName ?? "N/A",
})); }));
} }
@ -2113,6 +2134,18 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
stationMovement.ConsignmentNote = "/" + relativePath; stationMovement.ConsignmentNote = "/" + relativePath;
} }
// Close the previous active movement record
var currentItem = await _centralDbContext.Items.FindAsync(stationMovement.ItemId);
if (currentItem != null && currentItem.MovementId.HasValue)
{
var previousMovement = await _centralDbContext.ItemMovements.FindAsync(currentItem.MovementId.Value);
if (previousMovement != null)
{
previousMovement.MovementComplete = true; // Close the old history
_centralDbContext.ItemMovements.Update(previousMovement);
}
}
_centralDbContext.ItemMovements.Add(stationMovement); _centralDbContext.ItemMovements.Add(stationMovement);
await _centralDbContext.SaveChangesAsync(); await _centralDbContext.SaveChangesAsync();