Update Inv Movement - ALL ITEM & QR
This commit is contained in:
parent
8276201e83
commit
8890cba87e
@ -184,6 +184,33 @@
|
|||||||
style="width:100%;border-style: solid; border-width: 1px"></table>
|
style="width:100%;border-style: solid; border-width: 1px"></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* All Item Table *@
|
||||||
|
<div class="row card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>All Item</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="row mb-4 align-items-center">
|
||||||
|
<label class="col-sm-2 col-form-label" style="font-weight: bold;">Sort Location :</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select form-control" v-model="selectedLocation" v-on:change="updateAllItemsTable">
|
||||||
|
<option value="all">All Locations</option>
|
||||||
|
<option value="user">My Items</option>
|
||||||
|
<option v-for="station in PicStations" :key="station.stationId" :value="'station_' + station.stationId">
|
||||||
|
{{ station.stationName }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover table-striped no-wrap" id="allItemsListDatatable" style="width:100%; border-style: solid; border-width: 1px"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="sortBy === 'logs'">
|
<div v-if="sortBy === 'logs'">
|
||||||
@ -702,13 +729,31 @@
|
|||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* QR Model *@
|
||||||
|
<div class="modal fade" id="zoomQrModal" tabindex="-1" aria-labelledby="zoomQrModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="zoomQrModalLabel">QR Code</h5>
|
||||||
|
<button type="button" class="btn-close closeModal" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body d-flex justify-content-center">
|
||||||
|
<div id="zoomedQrContainer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-center">
|
||||||
|
<h4 id="zoomedQrText" style="margin: 0; font-family: 'OCR A', monospace;"></h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@{
|
@{
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
$(function () {
|
||||||
app.mount('#registerItem');
|
app.mount('#registerItem');
|
||||||
|
|
||||||
@ -763,6 +808,8 @@
|
|||||||
currentPageStation : 1,
|
currentPageStation : 1,
|
||||||
itemsPerPageStation: 10,
|
itemsPerPageStation: 10,
|
||||||
dropdownOpen: false,
|
dropdownOpen: false,
|
||||||
|
selectedLocation: "all",
|
||||||
|
allItemsListDatatable: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -770,6 +817,11 @@
|
|||||||
this.fetchStation();
|
this.fetchStation();
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
PicStations() {
|
||||||
|
if (!this.currentUser) return [];
|
||||||
|
// Use == instead of === to safely compare string and int
|
||||||
|
return this.stations.filter(s => s.stationPicID == this.currentUser.id);
|
||||||
|
},
|
||||||
paginatedItems() {
|
paginatedItems() {
|
||||||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||||||
const end = start + this.itemsPerPage;
|
const end = start + this.itemsPerPage;
|
||||||
@ -815,7 +867,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
groupedByStation() {
|
groupedByStation() {
|
||||||
|
|
||||||
let groupedByItem = this.items.reduce((acc, movement) => {
|
let groupedByItem = this.items.reduce((acc, movement) => {
|
||||||
if (!acc[movement.uniqueID]) {
|
if (!acc[movement.uniqueID]) {
|
||||||
acc[movement.uniqueID] = {
|
acc[movement.uniqueID] = {
|
||||||
@ -843,6 +894,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (movements.length > 0) {
|
if (movements.length > 0) {
|
||||||
|
|
||||||
let latestMovement = movements[0];
|
let latestMovement = movements[0];
|
||||||
let station = latestMovement.toStationName || latestMovement.lastStationName || "Not Assigned";
|
let station = latestMovement.toStationName || latestMovement.lastStationName || "Not Assigned";
|
||||||
|
|
||||||
@ -863,22 +915,23 @@
|
|||||||
sortedKeys.forEach(key => {
|
sortedKeys.forEach(key => {
|
||||||
sortedGrouped[key] = groupedByStation[key];
|
sortedGrouped[key] = groupedByStation[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
return sortedGrouped;
|
return sortedGrouped;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
filteredItems() {
|
filteredItems() {
|
||||||
if (!this.searchQuery.trim()) {
|
if (!this.searchQuery.trim()) {
|
||||||
return this.groupedItems;
|
return this.groupedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchLower = this.searchQuery.toLowerCase();
|
const searchLower = this.searchQuery.toLowerCase();
|
||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(this.groupedItems).filter(([_, group]) =>
|
Object.entries(this.groupedItems).filter(([_, group]) =>
|
||||||
group.uniqueID.toLowerCase().includes(searchLower)
|
group.uniqueID.toLowerCase().includes(searchLower)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
filteredStation() {
|
filteredStation() {
|
||||||
if (!this.searchQueryStation) {
|
if (!this.searchQueryStation) {
|
||||||
return this.groupedByStation;
|
return this.groupedByStation;
|
||||||
@ -895,7 +948,6 @@
|
|||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatDate(dateString) {
|
formatDate(dateString) {
|
||||||
@ -938,7 +990,9 @@
|
|||||||
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
|
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
|
||||||
modal.show();
|
modal.show();
|
||||||
},
|
},
|
||||||
|
updateAllItemsTable() {
|
||||||
|
this.initAllTables();
|
||||||
|
},
|
||||||
consignmentNote(consignmentNote) {
|
consignmentNote(consignmentNote) {
|
||||||
if (!consignmentNote) {
|
if (!consignmentNote) {
|
||||||
this.consignmentNoteUrl = "No consignment note available.";
|
this.consignmentNoteUrl = "No consignment note available.";
|
||||||
@ -1058,7 +1112,7 @@
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
const myStationIds = this.stations
|
const myStationIds = this.stations
|
||||||
.filter(s => s.stationPicID === this.currentUser.id)
|
.filter(s => s.stationPicID == this.currentUser.id)
|
||||||
.map(s => s.stationId);
|
.map(s => s.stationId);
|
||||||
|
|
||||||
this.items = data.filter(item =>
|
this.items = data.filter(item =>
|
||||||
@ -1128,7 +1182,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (this.currentRole !== "Super Admin" && this.currentUser) {
|
||||||
|
this.stations = data.filter(s => s.stationPicID === this.currentUser.id);
|
||||||
|
} else {
|
||||||
this.stations = data;
|
this.stations = data;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching Station:', error);
|
console.error('Error fetching Station:', error);
|
||||||
@ -1156,6 +1215,7 @@
|
|||||||
|
|
||||||
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
|
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
|
||||||
data: this.items.filter((m) => m.movementComplete == 0),
|
data: this.items.filter((m) => m.movementComplete == 0),
|
||||||
|
|
||||||
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)}`; } },
|
||||||
@ -1180,6 +1240,7 @@
|
|||||||
|
|
||||||
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
|
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
|
||||||
data: this.items.filter((m) => m.movementComplete == 1 && m.action !== "Assign"),
|
data: this.items.filter((m) => m.movementComplete == 1 && m.action !== "Assign"),
|
||||||
|
|
||||||
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)}`; } },
|
||||||
@ -1209,6 +1270,7 @@
|
|||||||
m.action === "Change" ||
|
m.action === "Change" ||
|
||||||
m.toStation !== null
|
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)}`; } },
|
||||||
@ -1225,6 +1287,145 @@
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.allItemsListDatatable) {
|
||||||
|
this.allItemsListDatatable.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let myStationIds = this.PicStations ? this.PicStations.map(s => s.stationId) : [];
|
||||||
|
let currentUserId = this.currentUser ? this.currentUser.id : null;
|
||||||
|
let currentUserStoreId = this.currentUser ? this.currentUser.store : null;
|
||||||
|
|
||||||
|
let latestMovementsMap = {};
|
||||||
|
this.items.forEach(movement => {
|
||||||
|
let id = movement.itemId;
|
||||||
|
if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) {
|
||||||
|
latestMovementsMap[id] = movement;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let latestMovements = Object.values(latestMovementsMap);
|
||||||
|
let allItemsData = [];
|
||||||
|
|
||||||
|
latestMovements.forEach(movement => {
|
||||||
|
|
||||||
|
let isDelivered = movement.latestStatus === 'Delivered';
|
||||||
|
let isReturned = movement.toOther === 'Return' &&
|
||||||
|
(movement.latestStatus === null || movement.latestStatus === 'Ready To Deploy');
|
||||||
|
|
||||||
|
if (isDelivered && !isReturned && movement.movementComplete == 1) {
|
||||||
|
|
||||||
|
// Check ownership (Is it with the user or their station or store)
|
||||||
|
let isWithUser = movement.toUser === currentUserId;
|
||||||
|
let isDeployedToUserStation = myStationIds.includes(movement.toStation);
|
||||||
|
let isAtUserStore = (currentUserStoreId !== null && movement.toStore === currentUserStoreId);
|
||||||
|
|
||||||
|
if (isWithUser || isDeployedToUserStation || isAtUserStore) {
|
||||||
|
|
||||||
|
if (this.selectedLocation === "all" || this.selectedLocation === "" || !this.selectedLocation) {
|
||||||
|
allItemsData.push(movement);
|
||||||
|
}
|
||||||
|
else if (this.selectedLocation === "user") {
|
||||||
|
// Under "My Items" - show items assigned to the user OR their store
|
||||||
|
if (isWithUser || isAtUserStore) {
|
||||||
|
allItemsData.push(movement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.selectedLocation.startsWith("station_")) {
|
||||||
|
let stationId = parseInt(this.selectedLocation.split("_")[1]);
|
||||||
|
if (movement.toStation === stationId) {
|
||||||
|
allItemsData.push(movement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.allItemsListDatatable = $("#allItemsListDatatable").DataTable({
|
||||||
|
data: allItemsData,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: "Product Code",
|
||||||
|
data: "uniqueID",
|
||||||
|
render: function (data, type, row) {
|
||||||
|
const displayData = data ? data : "N/A";
|
||||||
|
return `<strong>${displayData}</strong><div id="qr_movement_${row.id}" class="mt-2"></div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Product Name",
|
||||||
|
data: "productName",
|
||||||
|
render: (data, type, full) => {
|
||||||
|
return `${data || 'Unknown'} <br> ${renderFile(full.productImage)}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: "Current Location", data: "toStationName" },
|
||||||
|
{ title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) },
|
||||||
|
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
|
||||||
|
{ title: "Action", data: "action" },
|
||||||
|
{ title: "From User", data: "lastUserName" },
|
||||||
|
{ title: "From Station", data: "lastStationName" },
|
||||||
|
{ title: "From Store", data: "lastStoreName" },
|
||||||
|
{ title: "Last User", data: "toUserName" },
|
||||||
|
{ title: "Last Station", data: "toStationName" },
|
||||||
|
{ title: "Last Store", data: "toStoreName" },
|
||||||
|
{ title: "Qty", data: "quantity" },
|
||||||
|
],
|
||||||
|
|
||||||
|
order: [[0, "desc"]],
|
||||||
|
responsive: true,
|
||||||
|
|
||||||
|
drawCallback: function (settings) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const api = this.api();
|
||||||
|
api.rows().every(function () {
|
||||||
|
const data = this.data();
|
||||||
|
|
||||||
|
// Use data.id to ensure the element ID is unique per row
|
||||||
|
const containerId = `qr_movement_${data.id}`;
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
// Construct the full URL for the QR code dynamically
|
||||||
|
if (!data.uniqueID) return;
|
||||||
|
const qrText = `${window.location.origin}/I/${data.uniqueID}`;
|
||||||
|
|
||||||
|
// Generate Small QR
|
||||||
|
new QRCode(container, {
|
||||||
|
text: qrText,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.M
|
||||||
|
});
|
||||||
|
|
||||||
|
container.style.cursor = "pointer";
|
||||||
|
container.title = "Click to enlarge";
|
||||||
|
|
||||||
|
// Open Modal with Large QR
|
||||||
|
container.onclick = function () {
|
||||||
|
|
||||||
|
const zoomContainer = document.getElementById("zoomedQrContainer");
|
||||||
|
zoomContainer.innerHTML = "";
|
||||||
|
|
||||||
|
new QRCode(zoomContainer, {
|
||||||
|
text: qrText,
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.M
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("zoomedQrText").innerText = data.uniqueID;
|
||||||
|
$('#zoomQrModal').modal('show');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
function renderFile(data, type, full, meta) {
|
function renderFile(data, type, full, meta) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return "No Document";
|
return "No Document";
|
||||||
@ -1236,27 +1437,27 @@
|
|||||||
|
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
return `<a href="${data}" target="_blank" data-lightbox="image-1">
|
return `<a href="${data}" target="_blank" data-lightbox="image-1">
|
||||||
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" /></a>`;
|
||||||
</a>`;
|
|
||||||
}
|
}
|
||||||
else if (isPdf) {
|
else if (isPdf) {
|
||||||
return `<a href="${data}" target="_blank">
|
return `<a href="${data}" target="_blank">
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
|
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
|
||||||
alt="PDF Document" class="img-thumbnail"
|
alt="PDF Document" class="img-thumbnail"
|
||||||
style="width: 50px; height: 50px;" />
|
style="width: 50px; height: 50px;" />
|
||||||
<br>View PDF
|
<br>View PDF</a>`;
|
||||||
</a>`;
|
|
||||||
} else {
|
} else {
|
||||||
return `<a href="${data}" target="_blank">Download File</a>`;
|
return `<a href="${data}" target="_blank">Download File</a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleCategory(itemId) {
|
toggleCategory(itemId) {
|
||||||
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
|
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
|
||||||
|
|
||||||
this.detailsVisible = {};
|
this.detailsVisible = {};
|
||||||
this.historyVisible = {};
|
this.historyVisible = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleHistory(itemId) {
|
toggleHistory(itemId) {
|
||||||
// Jika item yang ditekan sudah terbuka, tutup
|
// Jika item yang ditekan sudah terbuka, tutup
|
||||||
if (this.historyVisible[itemId]) {
|
if (this.historyVisible[itemId]) {
|
||||||
@ -1269,10 +1470,10 @@
|
|||||||
this.historyVisible[itemId] = true;
|
this.historyVisible[itemId] = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleDetails(movementId) {
|
toggleDetails(movementId) {
|
||||||
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
|
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
clickOutside: {
|
clickOutside: {
|
||||||
@ -1290,5 +1491,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
@ -924,7 +924,6 @@
|
|||||||
isStationPIC() {
|
isStationPIC() {
|
||||||
if (!this.thisItem || !this.stationlist || !this.currentUser) return false;
|
if (!this.thisItem || !this.stationlist || !this.currentUser) return false;
|
||||||
|
|
||||||
// Now this.thisItem.toStation will have the ID from the backend
|
|
||||||
const targetStationId = this.thisItem.toStation;
|
const targetStationId = this.thisItem.toStation;
|
||||||
|
|
||||||
return this.stationlist.some(station =>
|
return this.stationlist.some(station =>
|
||||||
@ -1244,7 +1243,6 @@
|
|||||||
let receiveToStation = null;
|
let receiveToStation = null;
|
||||||
|
|
||||||
if (this.thisItem.toStation) {
|
if (this.thisItem.toStation) {
|
||||||
// Only keep Station. Clear User and Store as requested.
|
|
||||||
receiveToUser = null;
|
receiveToUser = null;
|
||||||
receiveToStore = null;
|
receiveToStore = null;
|
||||||
receiveToStation = this.thisItem.toStation;
|
receiveToStation = this.thisItem.toStation;
|
||||||
@ -1258,19 +1256,16 @@
|
|||||||
receiveToStore = null;
|
receiveToStore = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. BINA PAKEJ DATA (Lengkap dengan Last & To)
|
|
||||||
const formData = {
|
const formData = {
|
||||||
Id: this.thisItem.movementId,
|
Id: this.thisItem.movementId,
|
||||||
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
||||||
Remark: this.thisItem.remark,
|
Remark: this.thisItem.remark,
|
||||||
LatestStatus: statusToSave,
|
LatestStatus: statusToSave,
|
||||||
|
|
||||||
// Data Penerima (Update kepada siapa yang klik receive sekarang)
|
|
||||||
ToUser: receiveToUser,
|
ToUser: receiveToUser,
|
||||||
ToStore: receiveToStore,
|
ToStore: receiveToStore,
|
||||||
ToStation: receiveToStation,
|
ToStation: receiveToStation,
|
||||||
|
|
||||||
// Data Penghantar (Kekalkan maklumat asal sebagai rujukan sejarah)
|
|
||||||
LastUser: this.thisItem.lastUser,
|
LastUser: this.thisItem.lastUser,
|
||||||
LastStore: this.thisItem.lastStore,
|
LastStore: this.thisItem.lastStore,
|
||||||
LastStation: this.thisItem.lastStation
|
LastStation: this.thisItem.lastStation
|
||||||
@ -1570,13 +1565,10 @@
|
|||||||
const formData = {
|
const formData = {
|
||||||
ItemId: this.thisItem.itemID,
|
ItemId: this.thisItem.itemID,
|
||||||
|
|
||||||
// ORIGIN: Where is it right now?
|
|
||||||
LastUser: this.thisItem.toUser,
|
LastUser: this.thisItem.toUser,
|
||||||
LastStore: this.thisItem.toStore,
|
LastStore: this.thisItem.toStore,
|
||||||
LastStation: this.thisItem.toStation,
|
LastStation: this.thisItem.toStation,
|
||||||
|
|
||||||
// DESTINATION: Set to null here. The C# backend will query the DB
|
|
||||||
// and fill these in automatically based on history!
|
|
||||||
ToUser: null,
|
ToUser: null,
|
||||||
ToStore: null,
|
ToStore: null,
|
||||||
ToStation: null,
|
ToStation: null,
|
||||||
@ -1615,7 +1607,7 @@
|
|||||||
alert('Error: ' + error.message);
|
alert('Error: ' + error.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Show the Station Modal
|
|
||||||
StationMessage() {
|
StationMessage() {
|
||||||
this.selectedStation = "";
|
this.selectedStation = "";
|
||||||
$("#stationMessageModal").modal("show");
|
$("#stationMessageModal").modal("show");
|
||||||
@ -1630,7 +1622,6 @@
|
|||||||
try {
|
try {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// 1. LOGIC: Dynamically Identify the Origin (Where is the item right now?)
|
|
||||||
let originUser = null;
|
let originUser = null;
|
||||||
let originStore = null;
|
let originStore = null;
|
||||||
let originStation = null;
|
let originStation = null;
|
||||||
@ -1642,18 +1633,16 @@
|
|||||||
} else if (this.thisItem.toUser) {
|
} else if (this.thisItem.toUser) {
|
||||||
originUser = this.thisItem.toUser;
|
originUser = this.thisItem.toUser;
|
||||||
} else {
|
} else {
|
||||||
originUser = this.currentUserId; // Fallback
|
originUser = this.currentUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
ItemId: this.thisItem.itemID,
|
ItemId: this.thisItem.itemID,
|
||||||
|
|
||||||
// ORIGINS: Shift the current location to the "Last" fields
|
|
||||||
LastStation: originStation,
|
LastStation: originStation,
|
||||||
LastStore: originStore,
|
LastStore: originStore,
|
||||||
LastUser: originUser,
|
LastUser: originUser,
|
||||||
|
|
||||||
// DESTINATIONS: To the newly selected station (User and Store must be null)
|
|
||||||
ToStation: this.selectedStation,
|
ToStation: this.selectedStation,
|
||||||
ToUser: null,
|
ToUser: null,
|
||||||
ToStore: null,
|
ToStore: null,
|
||||||
@ -1662,7 +1651,7 @@
|
|||||||
Action: "Assign",
|
Action: "Assign",
|
||||||
Quantity: this.thisItem.movementQuantity || 1,
|
Quantity: this.thisItem.movementQuantity || 1,
|
||||||
Remark: this.remark ? (this.remark + ' / Deployed to station') : 'Deployed to station',
|
Remark: this.remark ? (this.remark + ' / Deployed to station') : 'Deployed to station',
|
||||||
ConsignmentNote: this.document, // Ensure this matches how your file is stored in Vue data
|
ConsignmentNote: this.document,
|
||||||
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
||||||
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
||||||
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
|
||||||
|
|||||||
@ -152,6 +152,33 @@
|
|||||||
style="width:100%;border-style: solid; border-width: 1px"></table>
|
style="width:100%;border-style: solid; border-width: 1px"></table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* All Item User *@
|
||||||
|
<div class="row card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>All Item</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="row mb-4 align-items-center">
|
||||||
|
<label class="col-sm-2 col-form-label" style="font-weight: bold;">Sort Location :</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select form-control" v-model="selectedLocation" v-on:change="updateAllItemsTable">
|
||||||
|
<option value="all">All Locations</option>
|
||||||
|
<option value="user">My Items</option>
|
||||||
|
<option v-for="station in stations" :key="station.stationId" :value="'station_' + station.stationId">
|
||||||
|
{{ station.stationName }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover table-striped no-wrap" id="allItemsListDatatable" style="width:100%; border-style: solid; border-width: 1px"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
|
<!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
|
||||||
@ -629,6 +656,24 @@
|
|||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* QR Modal *@
|
||||||
|
<div class="modal fade" id="zoomQrModal" tabindex="-1" aria-labelledby="zoomQrModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="zoomQrModalLabel">QR Code</h5>
|
||||||
|
<button type="button" class="btn-close closeModal" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body d-flex justify-content-center">
|
||||||
|
<div id="zoomedQrContainer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-center">
|
||||||
|
<h4 id="zoomedQrText" style="margin: 0; font-family: 'OCR A', monospace;"></h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@ -663,6 +708,8 @@
|
|||||||
dropdownOpen: false,
|
dropdownOpen: false,
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
stations:[],
|
stations:[],
|
||||||
|
selectedLocation: "all",
|
||||||
|
allItemsListDatatable: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -823,11 +870,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.fetchItemMovement();
|
try {
|
||||||
await this.fetchUser();
|
await this.fetchUser();
|
||||||
await Promise.all([
|
await this.fetchStation();
|
||||||
this.fetchStation(),
|
await this.fetchItemMovement();
|
||||||
]);
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Initialization failed:", error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatDate(dateString) {
|
formatDate(dateString) {
|
||||||
@ -876,7 +926,159 @@
|
|||||||
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
|
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
|
||||||
modal.show();
|
modal.show();
|
||||||
},
|
},
|
||||||
|
updateAllItemsTable() {
|
||||||
|
if (this.allItemsListDatatable) {
|
||||||
|
this.allItemsListDatatable.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
let filteredData = [];
|
||||||
|
let latestMovementsMap = {};
|
||||||
|
|
||||||
|
this.itemMovements.forEach(movement => {
|
||||||
|
let id = movement.itemId;
|
||||||
|
if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) {
|
||||||
|
latestMovementsMap[id] = movement;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let latestMovements = Object.values(latestMovementsMap);
|
||||||
|
|
||||||
|
latestMovements.forEach(movement => {
|
||||||
|
|
||||||
|
let isReturned = movement.toOther === 'Return' && movement.movementComplete == 1;
|
||||||
|
let isCanceled = movement.latestStatus === 'Ready To Deploy' && movement.movementComplete == 1;
|
||||||
|
|
||||||
|
if (!isReturned && !isCanceled && movement.movementComplete == 1) {
|
||||||
|
|
||||||
|
// Check if the item is currently with the user OR deployed to a station
|
||||||
|
let isWithUser = movement.toUser === this.userId;
|
||||||
|
|
||||||
|
// Extract all station IDs where the current user is the PIC
|
||||||
|
let userStationIds = this.stations.map(s => s.stationId);
|
||||||
|
let isDeployedToUserStation = userStationIds.includes(movement.toStation);
|
||||||
|
|
||||||
|
if (isWithUser || isDeployedToUserStation) {
|
||||||
|
|
||||||
|
if (this.selectedLocation === "all" || this.selectedLocation === "" || !this.selectedLocation) {
|
||||||
|
filteredData.push(movement);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (this.selectedLocation === "user") {
|
||||||
|
if (isWithUser) {
|
||||||
|
filteredData.push(movement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (this.selectedLocation.startsWith("station_")) {
|
||||||
|
let stationId = parseInt(this.selectedLocation.split("_")[1]);
|
||||||
|
if (movement.toStation === stationId) {
|
||||||
|
filteredData.push(movement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderFile(data, type, full, meta) {
|
||||||
|
if (!data) {
|
||||||
|
return "No Document";
|
||||||
|
}
|
||||||
|
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
|
||||||
|
var isPdf = /\.pdf$/i.test(data);
|
||||||
|
if (isImage) {
|
||||||
|
return `<a href="${data}" target="_blank" data-lightbox="image-1">
|
||||||
|
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
||||||
|
</a>`;
|
||||||
|
} else if (isPdf) {
|
||||||
|
return `<a href="${data}" target="_blank">
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
|
||||||
|
alt="PDF Document" class="img-thumbnail" style="width: 50px; height: 50px;" />
|
||||||
|
<br>View PDF
|
||||||
|
</a>`;
|
||||||
|
} else {
|
||||||
|
return `<a href="${data}" target="_blank">Download File</a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allItemsListDatatable = $("#allItemsListDatatable").DataTable({
|
||||||
|
data: filteredData,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: "Unique ID",
|
||||||
|
data: "uniqueID",
|
||||||
|
render: function (data, type, row) {
|
||||||
|
return `
|
||||||
|
<strong>${data}</strong>
|
||||||
|
<div id="qr_movement_${row.itemId}" class="mt-2"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
|
||||||
|
{ title: "Current Location", data: null, render: (data, type, row) => {
|
||||||
|
return row.toStationName ? row.toStationName : (row.toUserName ? row.toUserName : "Unknown");
|
||||||
|
}},
|
||||||
|
{ title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) },
|
||||||
|
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
|
||||||
|
{ title: "Action", data: "action" },
|
||||||
|
{ title: "From User", data: "lastUserName" },
|
||||||
|
{ title: "From Station", data: "lastStationName" },
|
||||||
|
{ title: "From Store", data: "lastStoreName" },
|
||||||
|
{ title: "Last User", data: "toUserName" },
|
||||||
|
{ title: "Last Station", data: "toStationName" },
|
||||||
|
{ title: "Last Store", data: "toStoreName" },
|
||||||
|
{ title: "Quantity", data: "quantity" },
|
||||||
|
],
|
||||||
|
responsive: true,
|
||||||
|
drawCallback: function (settings) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const api = this.api();
|
||||||
|
api.rows().every(function () {
|
||||||
|
const data = this.data();
|
||||||
|
|
||||||
|
const containerId = `qr_movement_${data.itemId}`;
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
const qrText = data.qrString ? data.qrString : data.uniqueID;
|
||||||
|
if (!qrText) return;
|
||||||
|
|
||||||
|
// small QR
|
||||||
|
new QRCode(container, {
|
||||||
|
text: qrText,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.M
|
||||||
|
});
|
||||||
|
|
||||||
|
container.style.cursor = "pointer";
|
||||||
|
container.title = "Click to enlarge";
|
||||||
|
|
||||||
|
container.onclick = function () {
|
||||||
|
const zoomContainer = document.getElementById("zoomedQrContainer");
|
||||||
|
zoomContainer.innerHTML = "";
|
||||||
|
|
||||||
|
// big QR
|
||||||
|
new QRCode(zoomContainer, {
|
||||||
|
text: qrText,
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: QRCode.CorrectLevel.M
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("zoomedQrText").innerText = data.uniqueID;
|
||||||
|
$('#zoomQrModal').modal('show');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
consignmentNote(consignmentNote) {
|
consignmentNote(consignmentNote) {
|
||||||
if (!consignmentNote) {
|
if (!consignmentNote) {
|
||||||
this.consignmentNoteUrl = "No consignment note available.";
|
this.consignmentNoteUrl = "No consignment note available.";
|
||||||
@ -884,10 +1086,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pastikan URL betul
|
|
||||||
this.consignmentNoteUrl = consignmentNote;
|
this.consignmentNoteUrl = consignmentNote;
|
||||||
|
|
||||||
// Tunggu Vue update sebelum buka modal
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
|
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
|
||||||
});
|
});
|
||||||
@ -960,6 +1160,7 @@
|
|||||||
renderTables() {
|
renderTables() {
|
||||||
if (this.sortBy === "all") {
|
if (this.sortBy === "all") {
|
||||||
this.initAllTables();
|
this.initAllTables();
|
||||||
|
this.updateAllItemsTable(); // Initialize the new list table on load
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1382,7 +1382,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
|
|||||||
i.ToStation,
|
i.ToStation,
|
||||||
i.LatestStatus,
|
i.LatestStatus,
|
||||||
i.receiveDate,
|
i.receiveDate,
|
||||||
i.MovementComplete
|
i.MovementComplete,
|
||||||
|
QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{i.Item?.UniqueID}"
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user