Item Movement & request
This commit is contained in:
parent
37ea90be65
commit
1542750302
@ -577,8 +577,8 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
groupedItems() {
|
processedGroupedItems() {
|
||||||
return this.itemMovements.reduce((acc, movement) => {
|
let grouped = this.itemMovements.reduce((acc, movement) => {
|
||||||
if (!acc[movement.itemId]) {
|
if (!acc[movement.itemId]) {
|
||||||
acc[movement.itemId] = {
|
acc[movement.itemId] = {
|
||||||
uniqueID: movement.uniqueID,
|
uniqueID: movement.uniqueID,
|
||||||
@ -588,6 +588,24 @@
|
|||||||
acc[movement.itemId].movements.push(movement);
|
acc[movement.itemId].movements.push(movement);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
// Sort items from newest to oldest & filter them
|
||||||
|
for (let itemId in grouped) {
|
||||||
|
let movements = grouped[itemId].movements
|
||||||
|
.sort((a, b) => b.id - a.id); // Newest to oldest
|
||||||
|
|
||||||
|
let stopIndex = movements.findIndex(m =>
|
||||||
|
m.toOther === 'Return' && m.movementComplete == 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stopIndex !== -1) {
|
||||||
|
movements = movements.slice(0, stopIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped[itemId].movements = movements;
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped;
|
||||||
},
|
},
|
||||||
|
|
||||||
groupedByStation() {
|
groupedByStation() {
|
||||||
@ -659,11 +677,11 @@
|
|||||||
|
|
||||||
filteredItems() {
|
filteredItems() {
|
||||||
if (!this.searchQuery.trim()) {
|
if (!this.searchQuery.trim()) {
|
||||||
return this.groupedItems;
|
return this.processedGroupedItems;
|
||||||
}
|
}
|
||||||
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.processedGroupedItems).filter(([_, group]) =>
|
||||||
group.uniqueID.toLowerCase().includes(searchLower)
|
group.uniqueID.toLowerCase().includes(searchLower)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,62 @@
|
|||||||
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")
|
||||||
|
<style>
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-box input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn {
|
||||||
|
border: none;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content option {
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content option:hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body img {
|
||||||
|
max-width: 100%; /* Ensure it fits the modal */
|
||||||
|
max-height: 150px; /* Adjust max height */
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
<div id="requestProduct" class="row">
|
<div id="requestProduct" class="row">
|
||||||
|
|
||||||
<!-- Document Preview Modal -->
|
<!-- Document Preview Modal -->
|
||||||
@ -74,32 +130,71 @@
|
|||||||
<div class="row register-form">
|
<div class="row register-form">
|
||||||
<div class="col-md-13">
|
<div class="col-md-13">
|
||||||
|
|
||||||
<!-- Product Dropdown -->
|
<!-- Product Name -->
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-4 col-form-label">Product</label>
|
<label class="col-sm-2 col-form-label">Product</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="dropdown">
|
<div class="dropdown" v-click-outside="closeDropdown">
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="productId" required>
|
<!-- Button + Input dalam satu box -->
|
||||||
<option class="btn-light" value="" disabled selected>Select Product</option>
|
<div class="dropdown-toggle-box" v-on:click="dropdownOpen = !dropdownOpen">
|
||||||
<option class="btn-light" v-for="(item, index) in products" :key="index" :value="item.productId">
|
<input type="text" class="form-control" v-model="searchQuery"
|
||||||
|
placeholder="Search product..." v-on:focus="dropdownOpen = true" v-on:click.stop />
|
||||||
|
<button type="button" class="btn btn-primary dropdown-btn" v-on:click.stop="dropdownOpen = !dropdownOpen">
|
||||||
|
▼
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dropdown list -->
|
||||||
|
<div v-if="dropdownOpen" class="dropdown-content" v-on:click.stop>
|
||||||
|
<div v-for="(item, index) in filteredProducts"
|
||||||
|
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectProduct(item)">
|
||||||
{{ item.productName + ' (' + item.modelNo + ')' }}
|
{{ item.productName + ' (' + item.modelNo + ')' }}
|
||||||
</option>
|
</div>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Category Dropdown -->
|
<!-- Category Dropdown -->
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-4 col-form-label">Category:</label>
|
<label class="col-sm-2 col-form-label">Category:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="text" id="category" name="category" v-model="showProduct.category" class="form-control" readonly />
|
<input type="text" id="category" name="category" v-model="showProduct.category" class="form-control" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Image Product Dropdown-->
|
||||||
|
<div class="form-group row align-items-center">
|
||||||
|
<label for="imageProduct" class="col-sm-2 col-form-label">Product Image: </label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<img v-if="showProduct.imageProduct" :src="showProduct.imageProduct" alt="Product Image" class="img-fluid" data-toggle="modal" data-target="#imageModal" />
|
||||||
|
<input type="hidden" id="imageProduct" name="imageProduct" v-model="showProduct">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="imageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="imageModalLabel">Product Image</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<img :src="showProduct.imageProduct" alt="Product Image" class="img-fluid">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Quantity Input -->
|
<!-- Quantity Input -->
|
||||||
<div class="form-group row d-flex align-items-center">
|
<div class="form-group row d-flex align-items-center">
|
||||||
<label class="col-sm-4 col-form-label">Quantity:</label>
|
<label class="col-sm-2 col-form-label">Quantity:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="number" class="form-control" v-model="quantity" required />
|
<input type="number" class="form-control" v-model="quantity" required />
|
||||||
</div>
|
</div>
|
||||||
@ -107,7 +202,7 @@
|
|||||||
|
|
||||||
@* Who will assign to *@
|
@* Who will assign to *@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-4 col-form-label">Item Assignment : </label>
|
<label class="col-sm-2 col-form-label">Item Assignment : </label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-12" v-model="assign"required>
|
<select class="btn btn-primary dropdown-toggle col-md-12" v-model="assign"required>
|
||||||
@ -120,7 +215,7 @@
|
|||||||
|
|
||||||
<!-- Station Dropdown -->
|
<!-- Station Dropdown -->
|
||||||
<div class="form-group row" v-if="assign === 'Station'">
|
<div class="form-group row" v-if="assign === 'Station'">
|
||||||
<label class="col-sm-4 col-form-label">Station:</label>
|
<label class="col-sm-2 col-form-label">Station:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="stationId" required>
|
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="stationId" required>
|
||||||
@ -136,7 +231,7 @@
|
|||||||
|
|
||||||
<!-- Remark Input (Wider) -->
|
<!-- Remark Input (Wider) -->
|
||||||
<div class="form-group row d-flex align-items-center">
|
<div class="form-group row d-flex align-items-center">
|
||||||
<label class="col-sm-4 col-form-label">Remark:</label>
|
<label class="col-sm-2 col-form-label">Remark:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="text" class="form-control col-md-10" v-model="remark" />
|
<input type="text" class="form-control col-md-10" v-model="remark" />
|
||||||
</div>
|
</div>
|
||||||
@ -144,7 +239,7 @@
|
|||||||
|
|
||||||
<!-- Document/Picture Input -->
|
<!-- Document/Picture Input -->
|
||||||
<div class="form-group row d-flex align-items-center">
|
<div class="form-group row d-flex align-items-center">
|
||||||
<label class="col-sm-4 col-form-label">Document/Picture:</label>
|
<label class="col-sm-2 col-form-label">Document/Picture:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<input type="file" id="document" name="document" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
<input type="file" id="document" name="document" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
|
||||||
</div>
|
</div>
|
||||||
@ -205,6 +300,8 @@
|
|||||||
assign: "",
|
assign: "",
|
||||||
|
|
||||||
productName: null,
|
productName: null,
|
||||||
|
searchQuery: "",
|
||||||
|
dropdownOpen: false,
|
||||||
stations: [],
|
stations: [],
|
||||||
selectedProduct: "",
|
selectedProduct: "",
|
||||||
selectedStation: "",
|
selectedStation: "",
|
||||||
@ -236,8 +333,25 @@
|
|||||||
|
|
||||||
return product ? product : {};
|
return product ? product : {};
|
||||||
},
|
},
|
||||||
|
filteredProducts() {
|
||||||
|
return this.products.filter(item =>
|
||||||
|
item.productName.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||||
|
item.modelNo.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
closeDropdown() {
|
||||||
|
this.dropdownOpen = false; // Tutup dropdown
|
||||||
|
},
|
||||||
|
|
||||||
|
selectProduct(item) {
|
||||||
|
this.selectedProduct = item;
|
||||||
|
this.productId = item.productId;
|
||||||
|
this.searchQuery = item.productName + " (" + item.modelNo + ")";
|
||||||
|
this.dropdownOpen = false;
|
||||||
|
},
|
||||||
|
|
||||||
handleFileUpload(event) {
|
handleFileUpload(event) {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
|
|
||||||
@ -251,6 +365,7 @@
|
|||||||
this.document = null;
|
this.document = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async addRequest() {
|
async addRequest() {
|
||||||
try {
|
try {
|
||||||
const requiredFields = ['productId', 'quantity', 'assign'];
|
const requiredFields = ['productId', 'quantity', 'assign'];
|
||||||
@ -308,11 +423,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
this.resetForm();
|
||||||
alert('Success!', 'Request form has been successfully submitted.', 'success');
|
alert('Success!', 'Request form has been successfully submitted.', 'success');
|
||||||
const requestItem = await response.json();
|
const requestItem = await response.json();
|
||||||
this.request.push(requestItem);
|
this.request.push(requestItem);
|
||||||
this.fetchRequest();
|
this.fetchRequest();
|
||||||
this.resetForm();
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to submit form.');
|
throw new Error('Failed to submit form.');
|
||||||
}
|
}
|
||||||
@ -337,6 +452,15 @@
|
|||||||
$(td).attr('id', `qr${cellData}`);
|
$(td).attr('id', `qr${cellData}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ "title": "Product Image",
|
||||||
|
"data": "productPicture",
|
||||||
|
"render": function (data, type, full, meta) {
|
||||||
|
var image = `<a href="${data}" target="_blank" data-lightbox="image-1">
|
||||||
|
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
||||||
|
</a>`;
|
||||||
|
return image;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Product Id",
|
"title": "Product Id",
|
||||||
"data": "productName",
|
"data": "productName",
|
||||||
@ -432,6 +556,19 @@
|
|||||||
$(td).attr('id', `qr${cellData}`);
|
$(td).attr('id', `qr${cellData}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Status",
|
||||||
|
"data": "status",
|
||||||
|
},
|
||||||
|
{ "title": "Product Image",
|
||||||
|
"data": "productPicture",
|
||||||
|
"render": function (data, type, full, meta) {
|
||||||
|
var image = `<a href="${data}" target="_blank" data-lightbox="image-1">
|
||||||
|
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
|
||||||
|
</a>`;
|
||||||
|
return image;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Product Id",
|
"title": "Product Id",
|
||||||
"data": "productName",
|
"data": "productName",
|
||||||
@ -494,10 +631,6 @@
|
|||||||
{
|
{
|
||||||
"title": "Approval Date",
|
"title": "Approval Date",
|
||||||
"data": "approvalDate",
|
"data": "approvalDate",
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Status",
|
|
||||||
"data": "status",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
@ -605,6 +738,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetForm() {
|
resetForm() {
|
||||||
|
this.searchQuery = "";
|
||||||
this.stationId = "";
|
this.stationId = "";
|
||||||
this.productId = "";
|
this.productId = "";
|
||||||
this.remark = "";
|
this.remark = "";
|
||||||
@ -660,6 +794,22 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
directives: {
|
||||||
|
clickOutside: {
|
||||||
|
beforeMount(el, binding) {
|
||||||
|
el.clickOutsideEvent = (event) => {
|
||||||
|
if (!(el.contains(event.target))) {
|
||||||
|
binding.value?.(); // Guna optional chaining untuk elak error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.body.addEventListener("click", el.clickOutsideEvent);
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
document.body.removeEventListener("click", el.clickOutsideEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
@ -945,6 +945,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
|
|||||||
i.requestID,
|
i.requestID,
|
||||||
i.ProductId,
|
i.ProductId,
|
||||||
productName = i.Product?.ProductName,
|
productName = i.Product?.ProductName,
|
||||||
|
productPicture = i.Product?.ImageProduct,
|
||||||
i.UserId,
|
i.UserId,
|
||||||
i.status,
|
i.status,
|
||||||
i.StationId,
|
i.StationId,
|
||||||
@ -962,13 +963,20 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
|
|||||||
[HttpDelete("DeleteRequest/{requestId}")]
|
[HttpDelete("DeleteRequest/{requestId}")]
|
||||||
public async Task<IActionResult> DeleteRequest(int requestId)
|
public async Task<IActionResult> DeleteRequest(int requestId)
|
||||||
{
|
{
|
||||||
var request = await _centralDbContext.Requests.FindAsync(requestId);
|
var requestApprove = _centralDbContext.Requests.FirstOrDefault(r => r.requestID == requestId && r.approvalDate != null);
|
||||||
if (request == null)
|
var requestDelete = _centralDbContext.Requests.FirstOrDefault(r => r.requestID == requestId);
|
||||||
|
|
||||||
|
if (requestApprove != null)
|
||||||
|
{
|
||||||
|
return NotFound(new { success = false, message = "Request not found Or Admin Already Approve or Reject" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestDelete == null)
|
||||||
{
|
{
|
||||||
return NotFound(new { success = false, message = "Request not found" });
|
return NotFound(new { success = false, message = "Request not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
_centralDbContext.Requests.Remove(request);
|
_centralDbContext.Requests.Remove(requestDelete);
|
||||||
await _centralDbContext.SaveChangesAsync();
|
await _centralDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
return Ok(new { success = true, message = "Request deleted successfully" });
|
return Ok(new { success = true, message = "Request deleted successfully" });
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user