Qr & Movement Display

This commit is contained in:
ArifHilmi 2025-03-05 09:38:24 +08:00
parent 3ec456afbd
commit f8be5be392
4 changed files with 216 additions and 106 deletions

View File

@ -33,6 +33,10 @@
color: orange; /* Warna oren untuk 'Return' */ color: orange; /* Warna oren untuk 'Return' */
} }
.text-success {
color: greenyellow;
}
.ms-auto { .ms-auto {
margin-left: auto !important; /* Push Complete/Incomplete to right */ margin-left: auto !important; /* Push Complete/Incomplete to right */
} }
@ -84,6 +88,13 @@
<div class="card-body"> <div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table> <table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
</div> </div>
<div class="card-header">
<h3>Assign Station Movement</h3>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="stationDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div> </div>
</div> </div>
@ -100,43 +111,40 @@
<!-- Hide all details unless button is clicked --> <!-- Hide all details unless button is clicked -->
<div v-show="categoryVisible[itemId]" class="card-body"> <div v-show="categoryVisible[itemId]" class="card-body">
<div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row"> <div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row">
<!-- 📌 Show Only Latest Movement -->
<div v-if="index === 0" class="row"> <div v-if="index === 0" class="row">
<strong>Latest Movement</strong> <strong>Latest Movement</strong>
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom"> <div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type --> <h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign', 'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
'text-info': movement.action === 'Assign'}" class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
class="flex-shrink-0 text-nowrap"
style="max-width:90px; min-width:90px;">
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }} {{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
<!-- Action --> <!-- Action -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4> <h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value">{{ movement.action }}</span> <span class="fixed-value text-truncate">{{ movement.action }}</span>
</div> </div>
<!-- Status --> <!-- Status -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4> <h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span> <span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
</div> </div>
<!-- More Details Button --> <!-- More Details Button -->
@ -156,7 +164,8 @@
<div class="row"> <div class="row">
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<!-- Conditionally render Start Icon --> <!-- Conditionally render Start Icon -->
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i> <i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i> <i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p> <p><strong>Start</strong></p>
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p> <p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
@ -194,7 +203,6 @@
</div> </div>
</div> </div>
<!-- 📌 Single View History Button -->
<button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)"> <button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)">
<i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History <i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History
</button> </button>
@ -202,24 +210,22 @@
<div v-show="historyVisible[itemId]" class="history-row"> <div v-show="historyVisible[itemId]" class="history-row">
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2"> <div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom"> <div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type --> <h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign', 'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
'text-info': movement.action === 'Assign'}" class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
class="flex-shrink-0 text-nowrap"
style="max-width:90px; min-width:90px;">
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }} {{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -237,7 +243,7 @@
</div> </div>
<!-- More Details Button --> <!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto"v-on:click="toggleDetails(movement.id)"> <button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
More Details More Details
</button> </button>
@ -248,13 +254,12 @@
</h4> </h4>
</div> </div>
<!-- 📌 Details Section (Hidden by Default) -->
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2"> <div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row"> <div class="row">
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<!-- Conditionally render Start Icon --> <!-- Conditionally render Start Icon -->
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i> <i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i> <i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p> <p><strong>Start</strong></p>
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p> <p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
@ -300,7 +305,7 @@
<!--------------------------------------------STATION CATEGORY----------------------------------------------------------------------> <!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
<div v-if="sortBy === 'station'"> <div v-if="sortBy === 'station'">
<div v-for="(items, station) in filteredStation" :key="stationName" :class="{'bg-light-gray': station === 'Unassign Station', 'bg-white': station !== 'Unassign Station'}" class="station-category card mt-3"> <div v-for="(items, station) in filteredStation" :key="stationName" :class="{'bg-light-gray': station === 'Unassign Station', 'bg-white': station !== 'Unassign Station'}" class="station-category card mt-3">
<!-- 📌 Station Header --> <!-- Station Header -->
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h3>{{ station }}</h3> <h3>{{ station }}</h3>
<button class="btn btn-light" v-on:click="toggleCategory(station)"> <button class="btn btn-light" v-on:click="toggleCategory(station)">
@ -308,10 +313,10 @@
</button> </button>
</div> </div>
<!-- 📌 Show Items Under Each Station --> <!-- Show Items Under Each Station -->
<div v-show="categoryVisible[station]" class="card-body"> <div v-show="categoryVisible[station]" class="card-body">
<div v-for="(group, itemId) in items" :key="itemId" class="row card"> <div v-for="(group, itemId) in items" :key="itemId" class="row card">
<!-- 📌 Item Header --> <!-- Item Header -->
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h2>Item : {{ group.uniqueID }}</h2> <h2>Item : {{ group.uniqueID }}</h2>
<button class="btn btn-light" v-on:click="toggleCategory(itemId)"> <button class="btn btn-light" v-on:click="toggleCategory(itemId)">
@ -319,46 +324,52 @@
</button> </button>
</div> </div>
<!-- 📌 Show Movements for Each Item --> <!-- Show Movements for Each Item -->
<div v-show="categoryVisible[itemId]" class="card-body"> <div v-show="categoryVisible[itemId]" class="card-body">
<div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row"> <div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row">
<div v-if="index === 0" class="row"> <div v-if="index === 0" class="row">
<strong>Latest Movement</strong> <strong>Latest Movement</strong>
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom"> <div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign', <h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-info': movement.action === 'Assign'}" 'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
class="flex-shrink-0 text-nowrap" class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
style="max-width:90px; min-width:90px;">
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }} {{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3> </h3>
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <!-- Send Date -->
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<span class="fixed-value">{{ movement.sendDate }}</span> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div> </div>
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <!-- Receive Date -->
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <!-- Action -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4> <h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value">{{ movement.action }}</span> <span class="fixed-value text-truncate">{{ movement.action }}</span>
</div> </div>
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1"> <!-- Status -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4> <h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span> <span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
</div> </div>
<!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)"> <button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
More Details More Details
</button> </button>
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'" class="text-nowrap ms-3"> <!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }} {{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4> </h4>
</div> </div>
@ -366,7 +377,8 @@
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2"> <div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row"> <div class="row">
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i> <i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i> <i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p> <p><strong>Start</strong></p>
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p> <p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
@ -401,7 +413,7 @@
</div> </div>
</div> </div>
<!-- 📌 Single View History Button --> <!-- Single View History Button -->
<button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)"> <button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)">
<i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History <i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History
</button> </button>
@ -410,19 +422,22 @@
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2"> <div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom"> <div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type --> <!-- Movement Type -->
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign', <h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-info': movement.action === 'Assign'}" class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;"> 'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }} class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -452,7 +467,7 @@
</div> </div>
<!-- 📌 Details Section (Hidden by Default) --> <!-- Details Section (Hidden by Default) -->
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2"> <div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row"> <div class="row">
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
@ -548,6 +563,7 @@
return { return {
itemMovements: [], itemMovements: [],
itemMovementCompleteDatatable: null, itemMovementCompleteDatatable: null,
stationDatatable: null,
itemMovementNotCompleteDatatable: null, itemMovementNotCompleteDatatable: null,
searchQuery: "", searchQuery: "",
searchStation: "", searchStation: "",
@ -572,28 +588,63 @@
return acc; return acc;
}, {}); }, {});
}, },
groupedByStation() { groupedByStation() {
let grouped = {}; let grouped = {};
this.itemMovements.forEach((movement) => { this.itemMovements.forEach((movement) => {
let station = movement.toStationName || movement.lastStationName || "Unassign Station";
let itemId = movement.uniqueID;
if (!grouped[station]) { if (movement.toStation !== null) {
grouped[station] = {}; let station = movement.toStationName;
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
} }
if (!grouped[station][itemId]) { if (movement.lastStation !== null) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] }; let station = movement.lastStationName;
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
}
else if (movement.lastStation == null || movement.toStation == null) {
let station = "Self";
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
} }
grouped[station][itemId].movements.push(movement);
}); });
// Sort stations and move "Unassign Station" to the last position // Sort stations and move "Unassign Station" to the last position
let sortedKeys = Object.keys(grouped).sort((a, b) => { let sortedKeys = Object.keys(grouped).sort((a, b) => {
if (a === "Unassign Station") return 1; // Move Unassign Station to the end if (a === "Unassign Station") return 1;
if (b === "Unassign Station") return -1; if (b === "Unassign Station") return -1;
return a.localeCompare(b); // Normal sorting for other stations return a.localeCompare(b);
}); });
let sortedGrouped = {}; let sortedGrouped = {};
@ -603,6 +654,7 @@
return sortedGrouped; return sortedGrouped;
}, },
filteredItems() { filteredItems() {
if (!this.searchQuery.trim()) { if (!this.searchQuery.trim()) {
return this.groupedItems; return this.groupedItems;
@ -614,6 +666,7 @@
) )
); );
}, },
filteredStation() { filteredStation() {
if (!this.searchStation) { if (!this.searchStation) {
return this.groupedByStation; return this.groupedByStation;
@ -684,6 +737,7 @@
this.initAllTables(); this.initAllTables();
} }
}, },
initAllTables() { initAllTables() {
if (this.itemMovementNotCompleteDatatable) { if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.destroy(); this.itemMovementNotCompleteDatatable.destroy();
@ -691,20 +745,23 @@
if (this.itemMovementCompleteDatatable) { if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.destroy(); this.itemMovementCompleteDatatable.destroy();
} }
if(this.stationDatatable) {
this.stationDatatable.destroy();
}
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({ this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
data: this.itemMovements.filter((m) => m.movementComplete == 0), data: this.itemMovements.filter((m) => m.movementComplete == 0),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Action", data: "action" },
{ title: "Send Date", data: "sendDate" },
{ title: "From User", data: "toUserName" }, { title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" }, { title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" }, { title: "From Station", data: "toStationName" },
{ title: "From Store", data: "toStoreName" }, { title: "From Store", data: "toStoreName" },
{ title: "Action", data: "action" },
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Quantity", data: "quantity" }, { title: "Quantity", data: "quantity" },
{ title: "Send Date", data: "sendDate" },
{ {
title: "Note", title: "Note",
data: "consignmentNote", data: "consignmentNote",
@ -740,22 +797,22 @@
}); });
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({ this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
data: this.itemMovements.filter((m) => m.movementComplete == 1), data: this.itemMovements.filter((m) => m.movementComplete == 1 && m.action !== "Assign"),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" },
{ title: "Action", data: "action" },
{ title: "From User", data: "toUserName" }, { title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" }, { title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" }, { title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" }, { title: "Last Station", data: "lastStationName" },
{ title: "From Store", data: "toStoreName" }, { title: "From Store", data: "toStoreName" },
{ title: "Last Store", data: "lastStoreName" }, { title: "Last Store", data: "lastStoreName" },
{ title: "Action", data: "action" },
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" }, { title: "Latest Status", data: "latestStatus" },
{ title: "Qty", data: "quantity" }, { title: "Qty", data: "quantity" },
{ title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" },
{ title: "Note", { title: "Note",
data: "consignmentNote", data: "consignmentNote",
render: function (data, type, full, meta) { render: function (data, type, full, meta) {
@ -788,16 +845,64 @@
], ],
responsive: true, responsive: true,
}); });
this.stationDatatable = $("#stationDatatable").DataTable({
data: this.itemMovements.filter((m) => m.action === "Assign" ),
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate" },
{ title: "From User", data: "toUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" },
{ title: "Qty", data: "quantity" },
{
title: "Note",
data: "consignmentNote",
render: function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{ title: "Remark", data: "remark" },
],
responsive: true,
});
}, },
toggleCategory(itemId) { toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId]; this.categoryVisible[itemId] = !this.categoryVisible[itemId];
}, },
toggleHistory(itemId) { toggleHistory(itemId) {
this.historyVisible[itemId] = !this.historyVisible[itemId]; this.historyVisible[itemId] = !this.historyVisible[itemId];
}, },
toggleDetails(movementId) { toggleDetails(movementId) {
this.detailsVisible[movementId] = !this.detailsVisible[movementId]; this.detailsVisible[movementId] = !this.detailsVisible[movementId];
}, },
handleSorting() { handleSorting() {
this.renderTables(); this.renderTables();
}, },

View File

@ -261,6 +261,19 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Remark:</label>
<div class="col-sm-8">
<input type="text" class="form-control" v-model="remark" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Consignment Note:</label>
<div class="col-sm-8">
<input type="file" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
</div>
</div>
<button type="submit" class="btn btn-primary">Deploy Station</button> <button type="submit" class="btn btn-primary">Deploy Station</button>
</form> </form>
</div> </div>
@ -457,7 +470,7 @@
ConsignmentNote: this.consignmentNote, ConsignmentNote: this.consignmentNote,
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
LastUser: this.currentUserId, LastUser: this.currentUserId,
LastStore: this.thisItem.currentStoreId, LastStore: this.thisItem.toStore,
LastStation: this.selectedStation, LastStation: this.selectedStation,
LatestStatus: "Delivered", LatestStatus: "Delivered",
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
@ -498,7 +511,8 @@
try { try {
const now = new Date(); const now = new Date();
const formData = { const formData = {
Id : this.thisItem.id, Id: this.thisItem.id,
LastStore: this.thisItem.toStore,
LatestStatus: "Delivered", LatestStatus: "Delivered",
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
MovementComplete: true, MovementComplete: true,

View File

@ -1248,20 +1248,24 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
{ {
if (!string.IsNullOrEmpty(returnMovement.ConsignmentNote)) if (!string.IsNullOrEmpty(returnMovement.ConsignmentNote))
{ {
var findUniqueCode = _centralDbContext.Items.Include(i => i.Product).FirstOrDefault(r => r.ItemID == returnMovement.ItemId);
var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == returnMovement.ToUser);
var bytes = Convert.FromBase64String(returnMovement.ConsignmentNote); var bytes = Convert.FromBase64String(returnMovement.ConsignmentNote);
string filePath = ""; string filePath = "";
string uniqueName = $"{returnMovement.Id}_{Guid.NewGuid()}"; var uniqueAbjad = new string(Enumerable.Range(0, 8).Select(_ => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[new Random().Next(36)]).ToArray());
if (IsImage(bytes)) if (IsImage(bytes))
{ {
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + returnMovement.ItemId + "_Request.jpg"); filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Return.jpg");
returnMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + returnMovement.ItemId + "_Request.jpg"; returnMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Return.jpg";
} }
else if (IsPdf(bytes)) else if (IsPdf(bytes))
{ {
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + returnMovement.ItemId + "_Request.pdf"); filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "Return.pdf");
returnMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + returnMovement.ItemId + "_Request.pdf"; returnMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Return.pdf";
} }
else else
{ {
@ -1270,16 +1274,13 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
await System.IO.File.WriteAllBytesAsync(filePath, bytes); await System.IO.File.WriteAllBytesAsync(filePath, bytes);
} }
// 1. Simpan returnMovement dalam database
_centralDbContext.ItemMovements.Add(returnMovement); _centralDbContext.ItemMovements.Add(returnMovement);
await _centralDbContext.SaveChangesAsync(); await _centralDbContext.SaveChangesAsync();
// 2. Cari item movement yang ada ItemId & MovementComplete = false
var updateItemIdMovement = await _centralDbContext.ItemMovements var updateItemIdMovement = await _centralDbContext.ItemMovements
.FirstOrDefaultAsync(m => m.Id == returnMovement.Id && m.MovementComplete == false); .FirstOrDefaultAsync(m => m.Id == returnMovement.Id && m.MovementComplete == false);
// 3. Jika wujud, update MovementId
if (updateItemIdMovement != null) if (updateItemIdMovement != null)
{ {
var returnItems = await _centralDbContext.Items.FindAsync(updateItemIdMovement.ItemId); var returnItems = await _centralDbContext.Items.FindAsync(updateItemIdMovement.ItemId);
@ -1289,21 +1290,10 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
returnItems.MovementId = updateItemIdMovement.Id; returnItems.MovementId = updateItemIdMovement.Id;
returnItems.ItemStatus = 2; returnItems.ItemStatus = 2;
_centralDbContext.Items.Update(returnItems); _centralDbContext.Items.Update(returnItems);
await _centralDbContext.SaveChangesAsync(); // Simpan perubahan
} }
} }
//4. Update Assign Row (Untuk ToStore = Ada value , kepada , ToStore = null)
var updateToStoreAssignStation = await _centralDbContext.ItemMovements.Where(i => i.Action == "Assign").ToListAsync();
foreach (var item in updateToStoreAssignStation)
{
item.ToStore = null; // Set ToStore to null for each matching row
_centralDbContext.ItemMovements.Update(item);
}
await _centralDbContext.SaveChangesAsync(); // Simpan perubahan
return Json(new return Json(new
{ {
updateItemIdMovement.Id, updateItemIdMovement.Id,
@ -1326,7 +1316,6 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
updateItemIdMovement.MovementComplete updateItemIdMovement.MovementComplete
}); });
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1346,20 +1335,24 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
{ {
if (!string.IsNullOrEmpty(stationMovement.ConsignmentNote)) if (!string.IsNullOrEmpty(stationMovement.ConsignmentNote))
{ {
var findUniqueCode = _centralDbContext.Items.Include(i => i.Product).FirstOrDefault(r => r.ItemID == stationMovement.ItemId);
var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == stationMovement.ToUser);
var bytes = Convert.FromBase64String(stationMovement.ConsignmentNote); var bytes = Convert.FromBase64String(stationMovement.ConsignmentNote);
string filePath = ""; string filePath = "";
string uniqueName = $"{stationMovement.Id}_{Guid.NewGuid()}"; var uniqueAbjad = new string(Enumerable.Range(0, 8).Select(_ => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[new Random().Next(36)]).ToArray());
if (IsImage(bytes)) if (IsImage(bytes))
{ {
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + stationMovement.ItemId + "_Request.jpg"); filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.jpg");
stationMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + stationMovement.ItemId + "_Request.jpg"; stationMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.jpg";
} }
else if (IsPdf(bytes)) else if (IsPdf(bytes))
{ {
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + stationMovement.ItemId + "_Request.pdf"); filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/itemmovement", findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "Return.pdf");
stationMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + stationMovement.ItemId + "_Request.pdf"; stationMovement.ConsignmentNote = "/media/inventory/itemmovement/" + findUniqueUser.FullName + " " + findUniqueCode.Product?.ModelNo + "(" + uniqueAbjad + ") Station.pdf";
} }
else else
{ {
@ -1368,16 +1361,14 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
await System.IO.File.WriteAllBytesAsync(filePath, bytes); await System.IO.File.WriteAllBytesAsync(filePath, bytes);
} }
// 1. Simpan returnMovement dalam database
_centralDbContext.ItemMovements.Add(stationMovement); _centralDbContext.ItemMovements.Add(stationMovement);
await _centralDbContext.SaveChangesAsync(); await _centralDbContext.SaveChangesAsync();
// 2. Cari item movement yang ada ItemId & MovementComplete = false
var updateItemIdMovement = await _centralDbContext.ItemMovements.Include(i => i.Item) var updateItemIdMovement = await _centralDbContext.ItemMovements.Include(i => i.Item)
.FirstOrDefaultAsync(m => m.Id == stationMovement.Id); .FirstOrDefaultAsync(m => m.Id == stationMovement.Id);
// 3. Jika wujud, update MovementId
if (updateItemIdMovement != null) if (updateItemIdMovement != null)
{ {
var returnItems = await _centralDbContext.Items.FindAsync(updateItemIdMovement.ItemId); var returnItems = await _centralDbContext.Items.FindAsync(updateItemIdMovement.ItemId);

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB