333 lines
13 KiB
Plaintext
333 lines
13 KiB
Plaintext
|
|
@{
|
|
ViewData["Title"] = "Item Movement";
|
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
}
|
|
<style>
|
|
@@font-face {
|
|
font-family: 'OCR-A';
|
|
src: url('../assets/fonts/ocraext.ttf');
|
|
}
|
|
|
|
.QrPrintFont {
|
|
font-family: 'OCR-A', monospace;
|
|
}
|
|
.table td img {
|
|
display: block !important;
|
|
}
|
|
|
|
|
|
.text-true {
|
|
color: green;
|
|
}
|
|
|
|
.text-false {
|
|
color: red;
|
|
}
|
|
|
|
.text-primary {
|
|
color: blue; /* Warna asal untuk 'Receive' */
|
|
}
|
|
|
|
.text-warning {
|
|
color: orange; /* Warna oren untuk 'Return' */
|
|
}
|
|
|
|
.fixed-label {
|
|
margin-left:100px;
|
|
font-weight: bold;
|
|
min-width: 120px; /* Ensure labels have same width */
|
|
}
|
|
|
|
.fixed-labelStatus {
|
|
margin-left: 25px;
|
|
font-weight: bold;
|
|
min-width: 20px; /* Ensure labels have same width */
|
|
}
|
|
.fixed-value {
|
|
min-width: 150px;
|
|
margin-right:-20px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.gap-4 {
|
|
gap: 30px; /* Increase spacing between Send Date and Receive Date */
|
|
}
|
|
|
|
.gap-2 {
|
|
gap: 1rem !important; /* Ensure Status is closer to its value */
|
|
}
|
|
|
|
.me-5 {
|
|
margin-right: 2rem !important; /* Move Receive/Return further from Send Date */
|
|
}
|
|
|
|
.ms-auto {
|
|
margin-left: auto !important; /* Push Complete/Incomplete to right */
|
|
}
|
|
</style>
|
|
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml");
|
|
|
|
<div id="ItemMovement" class="row">
|
|
<div class="row mb-3">
|
|
<h2 for="sortSelect" class="col-sm-1 col-form-h2">Sort by:</h2>
|
|
<div class="col-sm-4">
|
|
<select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting">
|
|
<option value="all">All</option>
|
|
<option value="item">Item</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div v-if="sortBy === 'all'">
|
|
<div class="row card">
|
|
<div class="card-header">
|
|
<h2>Pending Item Movement</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementNotCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row card">
|
|
<div class="card-header">
|
|
<h2>Complete Item Movement</h2>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="sortBy === 'item'">
|
|
<div v-for="(group, itemId) in getGroupedByItem()" :key="itemId" class="row card">
|
|
<div class="card-header">
|
|
<h2>Item Name: {{ group.productName }}</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div v-for="movement in group.movements" :key="movement.id" class="movement-row">
|
|
<div class="row">
|
|
<div class="col-md-12 d-flex align-items-center flex-wrap">
|
|
|
|
<h3 :class="movement.toOther === 'On Delivery' ? 'text-primary' : 'text-warning'" class="me-5">
|
|
{{ movement.toOther === 'On Delivery' ? 'Receive' : 'Return' }}
|
|
</h3>
|
|
|
|
<div class="d-flex align-items-center gap-4">
|
|
<h4 class="fixed-label">Send Date:</h4>
|
|
<span class="fixed-value">{{ movement.sendDate }}</span>
|
|
</div>
|
|
|
|
<div class="d-flex align-items-center gap-4">
|
|
<h4 class="fixed-label">Receive Date:</h4>
|
|
<span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span>
|
|
</div>
|
|
|
|
<div class="d-flex align-items-center gap-2">
|
|
<h4 class="fixed-labelStatus">Action:</h4>
|
|
<span class="fixed-value">{{ movement.action}}</span>
|
|
</div>
|
|
|
|
<div class="d-flex align-items-center gap-2">
|
|
<h4 class="fixed-labelStatus">Status:</h4>
|
|
<span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span>
|
|
</div>
|
|
|
|
<button class="btn btn-info btn-sm me-3" v-on:click="toggleDetails(movement.id)">More Details</button>
|
|
|
|
<h4 :class="movement.movementComplete == 1 ? 'text-success' : 'text-danger'" class="ms-auto">
|
|
{{ movement.movementComplete == 1 ? 'Complete' : 'Incomplete' }}
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details Section -->
|
|
<div v-if="movement.showDetails" class="details-row mt-2">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-4 text-center">
|
|
<i class="fas fa-warehouse fa-2x"></i>
|
|
<p><strong>Information:</strong> {{ movement.toOther }}</p>
|
|
<p><strong>User:</strong> {{ movement.toUserName }}</p>
|
|
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
|
|
<p><strong>Store:</strong> {{ movement.toStoreName }}</p>
|
|
</div>
|
|
<div class="col-md-4 text-center">
|
|
<i class="fas fa-arrow-right fa-2x"></i>
|
|
<p>{{ movement.latestStatus || movement.toOther }}</p>
|
|
</div>
|
|
<div class="col-md-4 text-center">
|
|
<i class="fas fa-user fa-2x"></i>
|
|
<p><strong>Information:</strong> {{ movement.latestStatus }}</p>
|
|
<p><strong>User:</strong> {{ movement.lastUserName }}</p>
|
|
<p><strong>Station:</strong> {{ movement.lastStationName }}</p>
|
|
<p><strong>Store:</strong> {{ movement.lastStoreName }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
@{
|
|
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
}
|
|
<script>
|
|
$(function () {
|
|
app.mount('#ItemMovement');
|
|
});
|
|
const app = Vue.createApp({
|
|
data() {
|
|
return {
|
|
itemMovements: [],
|
|
itemMovementCompleteDatatable : null,
|
|
itemMovementNotCompleteDatatable : null,
|
|
itemDatatables: {}, // Store tables by ItemId
|
|
sortBy: 'all', // Sorting option
|
|
}
|
|
},
|
|
mounted() {
|
|
console.log("Vue app mounted!");
|
|
this.fetchItemMovement();
|
|
},
|
|
methods: {
|
|
getGroupedByItem() {
|
|
return this.itemMovements.reduce((acc, movement) => {
|
|
if (!acc[movement.itemId]) {
|
|
acc[movement.itemId] = {
|
|
productName: movement.productName,
|
|
movements: []
|
|
};
|
|
}
|
|
acc[movement.itemId].movements.push(movement); // Jangan reset showDetails
|
|
return acc;
|
|
}, {});
|
|
},
|
|
async fetchItemMovement() {
|
|
try {
|
|
const response = await fetch('/InvMainAPI/ItemMovementUser', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch item movement');
|
|
}
|
|
const data = await response.json();
|
|
|
|
// Ensure showDetails is reactive
|
|
this.itemMovements = data.map(movement => ({
|
|
...movement,
|
|
showDetails: false
|
|
}));
|
|
|
|
if (this.itemMovementNotCompleteDatatable) {
|
|
this.itemMovementNotCompleteDatatable.clear().destroy();
|
|
}
|
|
if (this.itemMovementCompleteDatatable) {
|
|
this.itemMovementCompleteDatatable.clear().destroy();
|
|
}
|
|
this.$forceUpdate();
|
|
this.renderTables();
|
|
}
|
|
catch (error) {
|
|
console.error('Error fetching item:', error);
|
|
}
|
|
},
|
|
|
|
renderTables() {
|
|
if (this.sortBy === 'all') {
|
|
this.initAllTables();
|
|
}
|
|
},
|
|
|
|
initAllTables() {
|
|
if (this.itemMovementNotCompleteDatatable) {
|
|
this.itemMovementNotCompleteDatatable.clear().destroy();
|
|
}
|
|
if (this.itemMovementCompleteDatatable) {
|
|
this.itemMovementCompleteDatatable.clear().destroy();
|
|
}
|
|
self = this;
|
|
this.itemMovementNotCompleteDatatable = $('#itemMovementNotCompleteDatatable').DataTable({
|
|
"data": this.itemMovements.filter(movement => movement.movementComplete == 0),
|
|
"columns": [
|
|
{
|
|
"title": "Unique Id",
|
|
"data": "id",
|
|
"createdCell": function (td, cellData, rowData, row, col) {
|
|
// Assign a unique ID to the <td> element
|
|
$(td).attr('id', `qr${cellData}`);
|
|
},
|
|
},
|
|
{ title: "From User", data: "toUserName" },
|
|
{ title: "Last User", data: "lastUserName" },
|
|
{ title: "From Station", data: "toStationName" },
|
|
{ title: "From Store", data: "toStoreName" },
|
|
{ title: "Action", data: "action" },
|
|
{ title: "Start Status", data: "toOther" },
|
|
{ title: "Quantity", data: "quantity" },
|
|
{ title: "Send Date", data: "sendDate" },
|
|
{ title: "Note", data: "consignmentNote" },
|
|
{ title: "Remark", data: "remark" },
|
|
],
|
|
responsive: true,
|
|
});
|
|
|
|
this.itemMovementCompleteDatatable = $('#itemMovementCompleteDatatable').DataTable({
|
|
"data": this.itemMovements.filter(movement => movement.movementComplete == 1),
|
|
"columns": [
|
|
{
|
|
"title": "Unique Id",
|
|
"data": "id",
|
|
"createdCell": function (td, cellData, rowData, row, col) {
|
|
// Assign a unique ID to the <td> element
|
|
$(td).attr('id', `qr${cellData}`);
|
|
},
|
|
},
|
|
{ "title": "From User", "data": "toUserName" },
|
|
{ "title": "Last User", "data": "lastUserName" },
|
|
{ "title": "From Station", "data": "toStationName" },
|
|
{ "title": "Last Station", "data": "lastStationName" },
|
|
{ "title": "From Store", "data": "toStoreName" },
|
|
{ "title": "Last Store", "data": "lastStoreName" },
|
|
{ "title": "Action", "data": "action" },
|
|
{ "title": "Start Status", "data": "toOther" },
|
|
{ "title": "Latest Status", "data": "latestStatus" },
|
|
{ "title": "Qty", "data": "quantity" },
|
|
{ "title": "Send Date", "data": "sendDate" },
|
|
{ "title": "Receive Date", "data": "receiveDate" },
|
|
{ "title": "Note", "data": "consignmentNote" },
|
|
{ "title": "Remark", "data": "remark" },
|
|
],
|
|
responsive: true,
|
|
});
|
|
this.loading = false;
|
|
},
|
|
|
|
toggleDetails(id) {
|
|
const movement = this.itemMovements.find(mov => mov.id === id);
|
|
if (movement) {
|
|
movement.showDetails = !movement.showDetails; // Toggle value
|
|
}
|
|
},
|
|
|
|
|
|
resetForm() {
|
|
this.itemMovement = '';
|
|
},
|
|
|
|
handleSorting() {
|
|
this.$nextTick(() => this.fetchItemMovement());
|
|
},
|
|
|
|
|
|
},
|
|
});
|
|
</script>
|
|
} |