update movement user view only

This commit is contained in:
ArifHilmi 2025-02-27 16:32:32 +08:00
parent ebf8008b22
commit df2ec7e88c
6 changed files with 373 additions and 222 deletions

View File

@ -33,44 +33,15 @@
color: orange; /* Warna oren untuk 'Return' */ 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 { .ms-auto {
margin-left: auto !important; /* Push Complete/Incomplete to right */ margin-left: auto !important; /* Push Complete/Incomplete to right */
} }
</style> </style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml"); @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<div id="ItemMovement" class="row"> <div id="ItemMovement" class="row">
<div class="row mb-3"> <div class="row mb-3">
<h2 for="sortSelect" class="col-sm-1 col-form-h2">Sort by:</h2> <h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:140px;">Sort by:</h2>
<div class="col-sm-4"> <div class="col-sm-4">
<select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting"> <select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting">
<option value="all">All</option> <option value="all">All</option>
@ -78,6 +49,14 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-3" v-if="sortBy === 'item'">
<h4 class="col-sm-1 col-form-h2" style="min-width:140px;">Search Item:</h4>
<div class="col-sm-4">
<input type="text" class="form-control" v-model="searchQuery" placeholder="Search by item code...">
</div>
</div>
<div v-if="sortBy === 'all'"> <div v-if="sortBy === 'all'">
<div class="row card"> <div class="row card">
<div class="card-header"> <div class="card-header">
@ -99,76 +78,175 @@
</div> </div>
<div v-if="sortBy === 'item'"> <div v-if="sortBy === 'item'">
<div v-for="(group, itemId) in getGroupedByItem()" :key="itemId" class="row card"> <div v-for="(group, itemId) in filteredItems" :key="itemId" class="row card">
<div class="card-header"> <div class="card-header d-flex justify-content-between align-items-center">
<h2>Item Name: {{ group.productName }}</h2> <h2>Item : {{ group.uniqueID }}</h2>
<button class="btn btn-light" v-on:click="toggleCategory(itemId)">
<i :class="categoryVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Details
</button>
</div> </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"> <!-- Hide all details unless button is clicked -->
<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">
<!-- 📌 Show Only Latest Movement -->
<div v-if="index === 0" class="row">
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type -->
<h3 :class="movement.toOther === 'On Delivery' ? 'text-primary' : 'text-warning'"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.toOther === 'On Delivery' ? 'Receive' : 'Return' }} {{ movement.toOther === 'On Delivery' ? 'Receive' : 'Return' }}
</h3> </h3>
<div class="d-flex align-items-center gap-4"> <!-- Send Date -->
<h4 class="fixed-label">Send Date:</h4> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4>
<span class="fixed-value">{{ movement.sendDate }}</span> <span class="fixed-value">{{ movement.sendDate }}</span>
</div> </div>
<div class="d-flex align-items-center gap-4"> <!-- Receive Date -->
<h4 class="fixed-label">Receive Date:</h4> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <!-- Action -->
<h4 class="fixed-labelStatus">Action:</h4> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<span class="fixed-value">{{ movement.action}}</span> <h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value">{{ movement.action }}</span>
</div> </div>
<div class="d-flex align-items-center gap-2"> <!-- Status -->
<h4 class="fixed-labelStatus">Status:</h4> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span> <span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span>
</div> </div>
<button class="btn btn-info btn-sm me-3" v-on:click="toggleDetails(movement.id)">More Details</button> <!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
More Details
</button>
<h4 :class="movement.movementComplete == 1 ? 'text-success' : 'text-danger'" class="ms-auto"> <!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 ? 'text-success' : 'text-danger'" class="text-nowrap ms-3">
{{ movement.movementComplete == 1 ? 'Complete' : 'Incomplete' }} {{ movement.movementComplete == 1 ? 'Complete' : 'Incomplete' }}
</h4> </h4>
</div> </div>
</div>
<!-- Details Section -->
<div v-if="movement.showDetails" class="details-row mt-2">
<div class="row align-items-center"> <div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row">
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<i class="fas fa-warehouse fa-2x"></i> <i class="fas fa-warehouse fa-2x"></i>
<p><strong>Information:</strong> {{ movement.toOther }}</p> <p><strong>Start</strong></p>
<p><strong>User:</strong> {{ movement.toUserName }}</p> <p><strong>User:</strong> {{ movement.toUserName }}</p>
<p><strong>Station:</strong> {{ movement.toStationName }}</p> <p><strong>Station:</strong> {{ movement.toStationName }}</p>
<p><strong>Store:</strong> {{ movement.toStoreName }}</p> <p><strong>Store:</strong> {{ movement.toStoreName }}</p>
</div> </div>
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<p></p>
<i class="fas fa-arrow-right fa-2x"></i> <i class="fas fa-arrow-right fa-2x"></i>
<p>{{ movement.latestStatus || movement.toOther }}</p> <p>{{ movement.latestStatus || movement.toOther }}</p>
</div> </div>
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<i class="fas fa-user fa-2x"></i> <i class="fas fa-user fa-2x"></i>
<p><strong>Information:</strong> {{ movement.latestStatus }}</p> <p><strong>End</strong> {{ movement.latestStatus }}</p>
<p><strong>User:</strong> {{ movement.lastUserName }}</p> <p><strong>User:</strong> {{ movement.lastUserName }}</p>
<p><strong>Station:</strong> {{ movement.lastStationName }}</p> <p><strong>Station:</strong> {{ movement.lastStationName }}</p>
<p><strong>Store:</strong> {{ movement.lastStoreName }}</p> <p><strong>Store:</strong> {{ movement.lastStoreName }}</p>
</div> </div>
</div> </div>
</div> </div>
<hr> </div>
</div>
<!-- 📌 Single View History Button -->
<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
</button>
<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 class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type -->
<h3 :class="movement.toOther === 'On Delivery' ? 'text-primary' : 'text-warning'"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.toOther === 'On Delivery' ? 'Receive' : 'Return' }}
</h3>
<!-- Send Date -->
<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>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div>
<!-- Receive Date -->
<div 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>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div>
<!-- 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>
<span class="fixed-value text-truncate">{{ movement.action }}</span>
</div>
<!-- Status -->
<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">Status:</h4>
<span class="fixed-value text-truncate" style="max-width:120px;">{{ movement.latestStatus || movement.toOther }}</span>
</div>
<!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto"v-on:click="toggleDetails(movement.id)">
More Details
</button>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 ? 'text-success' : 'text-danger'" class="text-nowrap ms-3">
{{ movement.movementComplete == 1 ? 'Complete' : 'Incomplete' }}
</h4>
</div>
<!-- 📌 Details Section (Hidden by Default) -->
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row">
<div class="col-md-4 text-center">
<i class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></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">
<p></p>
<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>End</strong></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> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
@section Scripts { @section Scripts {
@ -183,88 +261,84 @@
data() { data() {
return { return {
itemMovements: [], itemMovements: [],
itemMovementCompleteDatatable : null, itemMovementCompleteDatatable: null,
itemMovementNotCompleteDatatable : null, itemMovementNotCompleteDatatable: null,
itemDatatables: {}, // Store tables by ItemId searchQuery: "",
sortBy: 'all', // Sorting option sortBy: "all",
historyVisible: {},
detailsVisible: {},
categoryVisible: {},
};
},
computed: {
groupedItems() {
return this.itemMovements.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
movements: [],
};
} }
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
},
filteredItems() {
if (!this.searchQuery.trim()) {
return this.groupedItems;
}
const searchLower = this.searchQuery.toLowerCase();
return Object.fromEntries(
Object.entries(this.groupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower)
)
);
},
}, },
mounted() { mounted() {
console.log("Vue app mounted!"); console.log("Vue app mounted!");
this.fetchItemMovement(); this.fetchItemMovement();
}, },
methods: { 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() { async fetchItemMovement() {
try { try {
const response = await fetch('/InvMainAPI/ItemMovementUser', { const response = await fetch("/InvMainAPI/ItemMovementUser", {
method: 'POST', method: "POST",
headers: { headers: { "Content-Type": "application/json" },
'Content-Type': 'application/json',
}
}); });
if (!response.ok) { if (!response.ok) throw new Error("Failed to fetch item movement");
throw new Error('Failed to fetch item movement');
}
const data = await response.json();
// Ensure showDetails is reactive const data = await response.json();
this.itemMovements = data.map(movement => ({ this.itemMovements = data.map((movement) => ({
...movement, ...movement,
showDetails: false showDetails: false,
})); }));
if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.clear().destroy();
}
if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.clear().destroy();
}
this.$forceUpdate();
this.renderTables(); this.renderTables();
} } catch (error) {
catch (error) { console.error("Error fetching item:", error);
console.error('Error fetching item:', error);
} }
}, },
renderTables() { renderTables() {
if (this.sortBy === 'all') { if (this.sortBy === "all") {
this.initAllTables(); this.initAllTables();
} }
}, },
initAllTables() { initAllTables() {
if (this.itemMovementNotCompleteDatatable) { if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.clear().destroy(); this.itemMovementNotCompleteDatatable.destroy();
} }
if (this.itemMovementCompleteDatatable) { if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.clear().destroy(); this.itemMovementCompleteDatatable.destroy();
} }
self = this;
this.itemMovementNotCompleteDatatable = $('#itemMovementNotCompleteDatatable').DataTable({ this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
"data": this.itemMovements.filter(movement => movement.movementComplete == 0), data: this.itemMovements.filter((m) => m.movementComplete == 0),
"columns": [ columns: [
{ { title: "Unique Id", data: "id" },
"title": "Unique Id", { title: "Product Code", data: "uniqueID" },
"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: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" }, { title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" }, { title: "From Station", data: "toStationName" },
@ -273,61 +347,104 @@
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Quantity", data: "quantity" }, { title: "Quantity", data: "quantity" },
{ title: "Send Date", data: "sendDate" }, { title: "Send Date", data: "sendDate" },
{ title: "Note", data: "consignmentNote" }, {
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" }, { title: "Remark", data: "remark" },
], ],
responsive: true, responsive: true,
}); });
this.itemMovementCompleteDatatable = $('#itemMovementCompleteDatatable').DataTable({ this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
"data": this.itemMovements.filter(movement => movement.movementComplete == 1), data: this.itemMovements.filter((m) => m.movementComplete == 1),
"columns": [ columns: [
{ { title: "Unique Id", data: "id" },
"title": "Unique Id", { title: "Product Code", data: "uniqueID" },
"data": "id", { title: "From User", data: "toUserName" },
"createdCell": function (td, cellData, rowData, row, col) { { title: "Last User", data: "lastUserName" },
// Assign a unique ID to the <td> element { title: "From Station", data: "toStationName" },
$(td).attr('id', `qr${cellData}`); { 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",
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": "From User", "data": "toUserName" }, { title: "Remark", data: "remark" },
{ "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, responsive: true,
}); });
this.loading = false;
}, },
toggleCategory(itemId) {
toggleDetails(id) { this.categoryVisible[itemId] = !this.categoryVisible[itemId];
const movement = this.itemMovements.find(mov => mov.id === id);
if (movement) {
movement.showDetails = !movement.showDetails; // Toggle value
}
}, },
toggleHistory(itemId) {
this.historyVisible[itemId] = !this.historyVisible[itemId];
resetForm() { },
this.itemMovement = ''; toggleDetails(movementId) {
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
}, },
handleSorting() { handleSorting() {
this.$nextTick(() => this.fetchItemMovement()); this.renderTables();
}, },
}, },
}); });
</script> </script>
} }

View File

@ -3,7 +3,7 @@
ViewData["Title"] = "Product Request"; ViewData["Title"] = "Product Request";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml"); @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<div id="requestProduct" class="row"> <div id="requestProduct" class="row">
<!-- Document Preview Modal --> <!-- Document Preview Modal -->
@ -576,14 +576,11 @@
this.productName = null; this.productName = null;
this.productCategory = null; this.productCategory = null;
this.stations = [];
this.selectedProduct = ""; this.selectedProduct = "";
this.selectedStation = ""; this.selectedStation = "";
this.selectedCategory = ""; this.selectedCategory = "";
this.showRequestModal = false; this.showRequestModal = false;
this.loading = false; this.loading = false;
this.product = [];
this.request = [];
this.currentUser = null; this.currentUser = null;
}, },

View File

@ -18,7 +18,7 @@
</style> </style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml"); @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<div id="registerItem" class="row"> <div id="registerItem" class="row">
<div class="row card"> <div class="row card">
<div class="card-header"> <div class="card-header">
@ -242,7 +242,7 @@
<div class="form-group row"> <div class="form-group row">
<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="text" class="form-control col-md-10" v-model="consignmentNote" /> <input type="file" v-model="consignmentNote" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
</div> </div>
</div> </div>
</div> </div>
@ -295,7 +295,7 @@
<div class="form-group row"> <div class="form-group row">
<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="text" class="form-control" v-model="consignmentNote" /> <input type="file" v-model="consignmentNote" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
</div> </div>
</div> </div>
<button type="submit" class="btn btn-primary">Return Item</button> <button type="submit" class="btn btn-primary">Return Item</button>
@ -361,6 +361,19 @@
this.startScanner(); this.startScanner();
}, },
methods: { methods: {
handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
this.consignmentNote = e.target.result.split(',')[1]; // Get Base64 string (remove metadata)
};
reader.readAsDataURL(file);
} else {
this.consignmentNote = null;
}
},
async updateItemMovement() { async updateItemMovement() {
const requiredFields = ['selectedStation']; const requiredFields = ['selectedStation'];
@ -507,9 +520,9 @@
if (response.ok) { if (response.ok) {
this.thisItem = await response.json(); this.thisItem = await response.json();
if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus == null && this.thisItem.currentUserId == this.currentUserId) { if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus == null && this.thisItem.currentUserId == this.currentUserId && this.movementComplete == 0) {
this.displayStatus = "arrived"; this.displayStatus = "arrived";
} else if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus != null && this.thisItem.currentUserId == this.currentUserId) { } else if (this.thisItem.movementId != null && this.thisItem.toOther === "On Delivery" && this.thisItem.latestStatus != null && this.thisItem.currentUserId == this.currentUserId && this.thisItem.latestStatus != "Ready To Deploy") {
this.displayStatus = "return"; this.displayStatus = "return";
} else if (this.thisItem.movementId != null && this.thisItem.toOther === "Return" && this.thisItem.latestStatus == null && this.thisItem.toUser == this.currentUserId) { } else if (this.thisItem.movementId != null && this.thisItem.toOther === "Return" && this.thisItem.latestStatus == null && this.thisItem.toUser == this.currentUserId) {
this.displayStatus = "requestAgain"; this.displayStatus = "requestAgain";

View File

@ -13,9 +13,9 @@
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml") @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
</div> </div>
@section Scripts { @section Scripts {
@{ @{
await Html.RenderPartialAsync("_ValidationScriptsPartial"); await Html.RenderPartialAsync("_ValidationScriptsPartial");
} }
<script> <script>
$(function () { $(function () {
app.mount('#invUser'); app.mount('#invUser');

View File

@ -752,6 +752,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.ToStation, i.ToStation,
i.ToStore, i.ToStore,
i.ToUser, i.ToUser,
UniqueID = i.Item?.UniqueID,
ProductName = i.Item?.Product?.ProductName, ProductName = i.Item?.Product?.ProductName,
LastUserName = i.FromUser?.FullName, LastUserName = i.FromUser?.FullName,
LastStoreName = i.FromStore?.StoreName, LastStoreName = i.FromStore?.StoreName,
@ -1171,7 +1172,30 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
try try
{ {
if (!string.IsNullOrEmpty(returnMovement.ConsignmentNote))
{
var bytes = Convert.FromBase64String(returnMovement.ConsignmentNote);
string filePath = "";
string uniqueName = $"{returnMovement.Id}_{Guid.NewGuid()}";
if (IsImage(bytes))
{
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + returnMovement.ItemId + "_Request.jpg");
returnMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + returnMovement.ItemId + "_Request.jpg";
}
else if (IsPdf(bytes))
{
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", uniqueName + returnMovement.ItemId + "_Request.pdf");
returnMovement.ConsignmentNote = "/media/inventory/request/" + uniqueName + returnMovement.ItemId + "_Request.pdf";
}
else
{
return BadRequest("Unsupported file format.");
}
await System.IO.File.WriteAllBytesAsync(filePath, bytes);
}
// 1. Simpan returnMovement dalam database // 1. Simpan returnMovement dalam database
_centralDbContext.ItemMovements.Add(returnMovement); _centralDbContext.ItemMovements.Add(returnMovement);
await _centralDbContext.SaveChangesAsync(); await _centralDbContext.SaveChangesAsync();

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB