Merge branch 'misya' into Beta_1.2

This commit is contained in:
Anis Nadia Binti Mohamad Zakaria 2025-07-02 11:34:12 +08:00
commit b9a830f86a
36 changed files with 5073 additions and 804 deletions

View File

@ -26,6 +26,12 @@ namespace PSTW_CentralSystem.Areas.Inventory.Models
public DateTime? approvalDate { get; set; }
public int? RequestQuantity { get; set; }
public string? Document { get; set; }
public int? fromStoreItem { get; set; }
[ForeignKey("fromStoreItem")]
public virtual StoreModel? Store { get; set; }
public int? assignStoreItem { get; set; }
[ForeignKey("assignStoreItem")]
public virtual StoreModel? Stores { get; set; }
}
}

View File

@ -49,7 +49,7 @@
margin-left: auto !important; /* Push Complete/Incomplete to right */
}
</style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div id="registerItem" class="row">
<div class="row mb-3" >
<h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:140px;">Sort by:</h2>
@ -118,7 +118,8 @@
<h2>Item Movement List</h2>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemDatatable" style=" width:100%;border-style: solid; border-width: 1px"></table>
<table class="table table-bordered table-hover table-striped no-wrap" id="itemDatatable"
style=" width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
@ -131,7 +132,9 @@
</div>
<div v-for="(group, itemId) in filteredItems" :key="itemId" class="row card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>Item : {{ group.uniqueID }}</h2>
@* <h2>Item : {{ group.uniqueID }}</h2> *@
<h2 v-if="group.uniqueID">Item : {{ group.uniqueID }}</h2>
<h2 v-else>Item : No Item Tracked</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>
@ -250,7 +253,10 @@
'text-weird': movement.action === 'Register'}"
class="flex-shrink-0 text-nowrap" style="max-width:140px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : ( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther : ( movement.action == 'Register' ? 'Register' : 'Assign')))) }}
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' :
( movement.toStation !== null ? 'Change' :
( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther :
( movement.action == 'Register' ? 'Register' : 'Assign')))) }}
</h3>
<!-- Send Date -->
@ -283,9 +289,13 @@
</button>
<!-- Completion Status -->
<h4 :class="movement.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
<h4 :class="movement.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' :
movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
class="text-nowrap ms-3">
{{ movement.action == 'Register' ? 'Complete' : (movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : ( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete'))) }}
{{ movement.action == 'Register' ? 'Complete' :
(movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' :
( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'Complete' :
(movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete'))) }}
</h4>
</div>
@ -341,7 +351,8 @@
<!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
<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 -->
<div class="card-header d-flex justify-content-between align-items-center">
<h3>{{ station }}</h3>
@ -355,7 +366,9 @@
<div v-for="(group, itemId) in items" :key="itemId" class="row card">
<!-- Item Header -->
<div class="card-header d-flex justify-content-between align-items-center">
<h2>Item : {{ group.uniqueID }}</h2>
@* <h2>Item : {{ group.uniqueID }}</h2> *@
<h2 v-if="group.uniqueID">Item : {{ group.uniqueID }}</h2>
<h2 v-else>Item : No Item Tracked</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>
@ -664,26 +677,13 @@
return acc;
}, {});
console.log(grouped);
// 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
// console.log(movements);
// let stopIndex = movements.findIndex(m =>
// m.toOther === 'Return' && m.movementComplete == 1
// );
// if (stopIndex !== -1) {
// movements = movements.slice(0, stopIndex + 1);
// }
// Ensure at least 3 movements before stopping
let stopIndex = movements.slice(3).findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let stopIndex = movements.slice(3).findIndex(m => m.toOther === 'Return' && m.movementComplete == 1);
if (stopIndex !== -1) {
stopIndex += 3; // Adjust index since we sliced after the first 3
@ -692,7 +692,6 @@
grouped[itemId].movements = movements;
}
return grouped;
},
@ -716,9 +715,7 @@
.sort((a, b) => b.id - a.id); // Newest → Oldest
// Ensure at least 3 movements before stopping
let stopIndex = movements.slice(3).findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let stopIndex = movements.slice(3).findIndex(m => m.toOther === 'Return' && m.movementComplete == 1);
// Remove older movements
if (stopIndex !== -1) {
@ -730,9 +727,7 @@
let latestMovement = movements[0];
let station = latestMovement.lastStationName || latestMovement.toStationName || "Not Assigned";
if (!groupedByStation[station]) {
groupedByStation[station] = {};
}
if (!groupedByStation[station]) { groupedByStation[station] = {}; }
groupedByStation[station][itemId] = { uniqueID: itemId, movements };
}
@ -752,8 +747,6 @@
return sortedGrouped;
},
filteredItems() {
if (!this.searchQuery.trim()) {
@ -781,9 +774,7 @@
filtered[station] = grouped[station];
}
});
return filtered;
},
},
@ -914,8 +905,6 @@
}
if(this.currentRole == "Super Admin"){
this.items = await response.json();
console.log(this.items);
this.initAllTables();
} else {
@ -929,10 +918,6 @@
this.initAllTables();
// console.log(this.items);
}
if (this.itemDatatable) {
@ -982,16 +967,10 @@
this.renderTables();
},
renderTables() {
// if (this.sortBy === "logs") {
// // this.initAllTables();
// this.initiateTable();
// } else
// if (this.sortBy === "all") {
// this.initAllTables();
// // this.initiateTable();
// }
this.initAllTables();
this.initiateTable();
},
initAllTables() {
if (this.itemMovementNotCompleteDatatable) {

View File

@ -603,8 +603,8 @@
"data": "convertPrice",
},
{
"title": "Invoice Date",
"data": "invoiceDate",
"title": "Register Date",
"data": "createDate",
},
{
"title": "Warranty Until",
@ -638,18 +638,27 @@
],
responsive: true,
drawCallback: function (settings) {
// Generate QR codes after rows are rendered
setTimeout(() => {
const api = this.api();
api.rows().every(function () {
const data = this.data(); // Row data
const data = this.data();
const containerId = `qr${data.uniqueID}`;
const container = $(`#${containerId}`);
container.empty();
container.append(`${data.uniqueID}`);
// console.log(container[0]);
if (container) {
// Generate QR code only if not already generated
new QRCode(container[0], {
const container = document.getElementById(containerId);
if (!container) {
return;
}
container.innerHTML = "";
container.append(data.uniqueID);
// Ensure qrString is valid before generating QR code
if (!data.qrString) {
return;
}
// Generate QR Code
new QRCode(container, {
text: data.qrString,
width: 100,
height: 100,
@ -657,20 +666,18 @@
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M
});
}
// container.on('click', function() {
// window.open(data.qrString, '_blank');
// });
});
},
}, 100); // Small delay to ensure elements exist
}
})
// Attach click event listener to the delete buttons
$('#itemDatatable tbody').on('click', '.delete-btn', function () {
$('#itemDatatable tbody').off('click', '.delete-btn').on('click', '.delete-btn', function () {
const itemId = $(this).data('id');
self.deleteItem(itemId);
});
$('#itemDatatable tbody').on('click', '.print-btn', function () {
const $button = $(this); // The clicked button
const $row = $button.closest('tr'); // The parent row of the button
@ -686,9 +693,8 @@
// For expanded view: Find the img in the first column of the current row
imageSrc = $row.find('td:nth-child(1) img').attr('src');
}
if (imageSrc) {
self.printItem(itemId, imageSrc); // Call the print function with the itemId and imageSrc
self.printItem(itemId, imageSrc); //\ Call the print function with the itemId and imageSrc
} else {
console.error("Image source not found.");
}
@ -870,8 +876,6 @@
.row($(`.delete-btn[data-id="${itemId}"]`).closest('tr'))
.remove()
.draw();
} else {
alert(result.message);
}
}
catch (error) {
@ -887,7 +891,6 @@
this.thisQRInfo.uniqueID = itemId;
const uniqueQR = itemId;
const container = document.getElementById("QrContainer");
if (!container) {
console.error("Container not found.");
return;
@ -940,7 +943,11 @@
console.error("Items list is not available or is not an array.");
return null;
}
return this.items.find(item => item.uniqueID === uniqueID);
//FIX ERROR
const foundItem = this.items.find(item => String(item.uniqueID).trim() === String(uniqueID).trim());
return foundItem ? JSON.parse(JSON.stringify(foundItem)) : null;
},
printQRInfo() {
// Create a virtual DOM element
@ -1019,7 +1026,6 @@
});
},
},
});
</script>

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
Layout = "~/Views/Shared/_Layout.cshtml";
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div id="app">
<div class="row card">
<div class="card-header">

View File

@ -4,7 +4,7 @@
string userId = ViewBag.UserId;
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div class="row">
<div id="registerProduct" class="card m-1">
<div class="row" v-if="addSection == true">
@ -145,7 +145,6 @@
},
methods: {
initiateTable() {
console.log(this.products)
this.productDatatable = $('#productDatatable').DataTable({
"data": this.products,
"columns": [
@ -180,7 +179,7 @@
"title": "Delete",
"data": "productId",
"render": function (data) {
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data.productId}">Delete</button>`;
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
return deleteButton;
},
}

View File

@ -145,7 +145,7 @@
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
<div style="text-align: center; margin: 20px 0; padding: 20px;">
<div v-if="itemlateststatus == 'On Delivery' && this.itemassignedtouser">
<div v-if="itemlateststatus == 'On Delivery' && this.thisItem.toUser == this.currentUser.id">
<h2 class="register-heading">Item is on Delivery</h2>
<div class="col-sm-3"></div>
<div class="col-sm-6 offset-sm-3">
@ -169,6 +169,23 @@
</div>
</div>
<div v-if="itemlateststatus == 'On Delivery' && thisItem.lastStore == currentUser.store">
<h2 class="register-heading">Receive Item</h2>
<div class="col-sm-3"></div>
<div class="col-sm-6 offset-sm-3">
<form v-on:submit.prevent="receiveItemMovement" data-aos="fade-right">
<div class="row register-form">
<div style="display: flex; justify-content: center; margin-top: 20px;">
<button type="submit" class="btn btn-primary" style="width: 200px; padding: 10px; font-size: 16px;">
Receive Item
</button>
</div>
</div>
</form>
</div>
</div>
<div v-if="(itemlateststatus == 'Repair' || itemlateststatus == 'Calibration') && this.itemassignedtouser">
<h2 class="register-heading">Receive Repair / Calibration</h2>
<div class="col-sm-3"></div>
@ -226,7 +243,7 @@
@* </div> *@
@* </div> *@
<div v-if="itemlateststatus == 'Faulty'">
<div v-if="itemlateststatus == 'Faulty' && this.itemassignedtouser">
<h2 class="register-heading">Add Item Movement</h2>
<div class="col-sm-3"></div>
<div class="col-sm-6 offset-sm-3">
@ -240,7 +257,7 @@
</div>
</div>
<div v-if="itemlateststatus == 'Ready To Deploy'">
<div v-if="itemlateststatus == 'Ready To Deploy' && this.itemassignedtouser">
<h2 class="register-heading">Add Item Movement</h2>
<div class="col-sm-3"></div>
<div class="col-sm-6 offset-sm-3">
@ -730,8 +747,9 @@
})
.then(() => { console.log("📷 Applied Constraintsss:", track.getSettings());
}).catch(err =>
// console.error("❌ Failed to apply constraints:", err)
})
.catch(err =>
console.error("❌ Failed to apply constraints:", err)
);
} else {
@ -812,9 +830,9 @@
...(this.selectedAction === 'user' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: 'On Delivery', SendDate: this.assigndate, lastUser: this.selectedUser, MovementComplete: false, Remark: this.remark, ConsignmentNote: this.document} : {}),
...(this.selectedAction === 'station' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: 'On Delivery', SendDate: this.assigndate, lastStation: this.selectedStation, lastUser: this.selectedStationPIC, MovementComplete: false, Remark: this.remark, ConsignmentNote: this.document} : {}),
...(this.selectedAction === 'store' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: 'On Delivery', SendDate: this.assigndate, lastUser: this.selectedStore, MovementComplete: false, Remark: this.remark, ConsignmentNote: this.document} : {}),
...(this.selectedAction === 'supplier' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: this.selectedOther, SendDate: this.assigndate, Remark: this.remark + '. Item sent to ' + this.selectedSupplier + ' for ' + this.selectedOther, ConsignmentNote: this.document, lastUser: this.currentUser.id, MovementComplete: false, } : {}),
...(this.selectedAction === 'faulty' ? { toStore: this.currentUser.store, toUser: this.currentUser.id,toOther: 'Faulty', Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Remark: this.remark, ConsignmentNote: this.document, MovementComplete: true, } : {}),
...(this.selectedAction === 'store' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: 'On Delivery', SendDate: this.assigndate, lastStore: this.selectedStore, MovementComplete: false, Remark: this.remark, ConsignmentNote: this.document} : {}),
...(this.selectedAction === 'supplier' ? { toStore: this.currentUser.store, toUser: this.currentUser.id, toOther: this.selectedOther, SendDate: this.assigndate, Remark: this.remark + '. Item sent to ' + this.selectedSupplier + ' for ' + this.selectedOther, ConsignmentNote: this.document, lastUser: this.currentUser.id, lastStore: this.currentUser.store, MovementComplete: false, } : {}),
...(this.selectedAction === 'faulty' ? { toStore: this.currentUser.store, toUser: this.currentUser.id,toOther: 'Faulty', SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Remark: this.remark, ConsignmentNote: this.document, MovementComplete: true, } : {}),
ItemId: this.thisItem.itemID,
Action: 'Stock Out',
@ -863,19 +881,23 @@
this.serialNumber = "";
}
if(this.thisItem.toOther === "On Delivery"){
if(this.thisItem.toOther === "On Delivery" && this.thisItem.toUser == this.currentUser.id){
if(!window.confirm("Are you sure you want to cancel item delivery?")){
return;
}
}
const now = new Date();
console.log('currentuser'+this.currentUser.id);
console.log('lastuser'+this.thisItem.lastuser);
const formData = {
Id: this.thisItem.movementId,
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
Remark: this.thisItem.toOther === "On Delivery" ? this.thisItem.remark + ". Inventory Master cancelled delivery with remark: " + this.remark : this.thisItem.remark,
LatestStatus: this.thisItem.toOther === "Return" ? "Faulty" : (this.thisItem.toOther === "Calibration" || this.thisItem.toOther === "Repair" || this.thisItem.toOther === "On Delivery" ) ? "Ready To Deploy" : ""
Remark: this.thisItem.toOther === "On Delivery" && this.thisItem.toUser == this.currentUser.id ? this.thisItem.remark + ". Inventory Master cancelled delivery with remark: " + this.remark : this.thisItem.remark,
LastUser: this.thisItem.lastuser == null ? this.currentUser.id : this.thisItem.lastuser,
LatestStatus: this.thisItem.toOther === "Return" ? "Faulty" : (this.thisItem.toOther === "Calibration" || this.thisItem.toOther === "Repair" || this.thisItem.toOther === "On Delivery" ) ? "Ready To Deploy" : (this.itemlateststatus == 'On Delivery' && this.thisItem.lastStore == this.currentUser.store ? "Delivered" : "")
};
@ -923,9 +945,19 @@
// this.thisItem = await response.json();
this.thisItem = await response.json();
console.log('last store'+this.thisItem.lastStore);
this.itemlateststatus = this.thisItem.latestStatus ? this.thisItem.latestStatus : this.thisItem.toOther;
this.itemassignedtouser = this.thisItem.toUser == this.currentUser.id || this.thisItem.lastUser == this.currentUser.id ? true : false;
this.itemassignedtouser = (this.thisItem.toUser === this.currentUser.id || this.thisItem.lastUser === this.currentUser.id ) && this.thisItem.lastStore === this.currentUser.store ? true : false;
// console.log(this.thisItem);
// console.log(this.itemassignedtouser);
console.log(this.thisItem.lastStore);
console.log( this.thisItem.lastStore == this.currentUser.store? true : false);
console.log(this.thisItem.toUser == this.currentUser.id? true : false);
console.log( this.thisItem.lastUser == this.currentUser.id ? true : false);
console.log(((this.thisItem.toUser == this.currentUser.id) || ( this.thisItem.lastUser == this.currentUser.id)) ? true : false);
console.log('currentuser store'+this.currentUser.store);
} else {
console.error('Failed to fetch item information');

View File

@ -3,7 +3,7 @@
Layout = "~/Views/Shared/_Layout.cshtml";
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div id="registerStation">
<form v-on:submit.prevent="addStation" data-aos="fade-right" id="registerStationForm" v-if="registerStationForm">
<div class="container register" data-aos="fade-right">

View File

@ -3,7 +3,7 @@
Layout = "~/Views/Shared/_Layout.cshtml";
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div id="registerSupplier">
<form v-on:submit.prevent="addSupplier" data-aos="fade-right" id="registerSupplierForm" v-if="registerSupplierForm">
<div class="container register" data-aos="fade-right">

View File

@ -64,7 +64,8 @@
<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>
<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>
@ -73,7 +74,8 @@
<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>
<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>
@ -284,7 +286,8 @@
<!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
<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 -->
<div class="card-header d-flex justify-content-between align-items-center">
<h3>{{ station }}</h3>
@ -569,7 +572,8 @@
return acc;
}, {});
// Sort items from newest to oldest & filter them
let filteredGrouped = {};
for (let itemId in grouped) {
let movements = grouped[itemId].movements
.sort((a, b) => b.id - a.id); // Newest to oldest
@ -578,14 +582,27 @@
m.toOther === 'Return' && m.movementComplete == 1
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
grouped[itemId].movements = movements;
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
return grouped;
if (movements.length > 0) {
filteredGrouped[itemId] = {
uniqueID: grouped[itemId].uniqueID,
movements: movements,
};
}
}
return filteredGrouped;
},
groupedByStation() {
@ -611,11 +628,18 @@
m.toOther === 'Return' && m.movementComplete == 1
);
// Remove older movements
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
if (movements.length > 0) {
let latestMovement = movements[0];
let station = latestMovement.lastStationName || latestMovement.toStationName || "Self Assigned";
@ -648,11 +672,17 @@
return this.processedGroupedItems;
}
const searchLower = this.searchQuery.toLowerCase();
return Object.fromEntries(
Object.entries(this.processedGroupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower)
)
);
let grouped = this.processedGroupedItems;
let filtered = {};
Object.keys(grouped).forEach(item => {
if (item.toLowerCase().includes(searchLower)) {
if (grouped[item] > 0) {
filtered[item] = grouped[item];
}
}
});
return filtered;
},
filteredStation() {
@ -720,6 +750,10 @@
}
},
handleSorting() {
this.renderTables();
},
renderTables() {
if (this.sortBy === "all") {
this.initAllTables();
@ -737,7 +771,7 @@
this.stationDatatable.destroy();
}
// Get latest movement per uniqueID
// Get latest movement per uniqueID after filtering
function getLatestMovements(data) {
let latestMovements = {};
data.forEach(movement => {
@ -749,18 +783,42 @@
return Object.values(latestMovements);
}
// Distribute items based on priority
// Filter movements based on conditions
function filterMovements(movements) {
let stopIndex = movements.findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
return movements;
}
let latestMovements = getLatestMovements(this.itemMovements);
let notCompleteData = [];
let completeData = [];
let assignedData = [];
latestMovements.forEach(movement => {
let filteredMovements = filterMovements([movement]);
if (filteredMovements.length > 0) {
if (movement.movementComplete == 0) {
notCompleteData.push(movement);
} else if (movement.movementComplete == 1) {
completeData.push(movement);
}
}
});
// Table 1: Not Complete Movements
@ -833,6 +891,7 @@
}
},
toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
@ -857,9 +916,6 @@
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
},
handleSorting() {
this.renderTables();
},
},
});

View File

@ -440,193 +440,13 @@
initiateTable() {
self = this;
this.requestDatatable = $('#requestDatatable').DataTable({
"data": this.request.filter(request => request.status == "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
"createdCell": function (td, cellData, rowData, row, col) {
// Assign a unique ID to the <td> element
$(td).attr('id', `qr${cellData}`);
},
},
{
"title": "Product Name",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
let self = this;
var imageSrc = full.productPicture;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
console.log(full);
function renderDocument(data, full) {
if (!data) return "No Document";
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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>`;
}
},
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Document / Picture",
"data": "document",
"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": "remarkUser",
},
{
"title": "Station Deploy",
"data": "stationName",
"render": function (data, type, full, meta) {
return data ? data : "Self Assign";
}
},
{
"title": "Request Date",
"data": "requestDate",
},
{
"title": "Status",
"data": "status",
},
{
"title": "Delete",
"data": "requestID",
"render": function (data) {
var deleteButton = `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
return deleteButton;
},
"className": "align-middle",
}
],
responsive: true,
});
this.requestDatatable = $('#settledrequestDatatable').DataTable({
"data": this.request.filter(request => request.status !== "Requested"),
"columns": [
{
"title": "Request ID",
"data": "requestID",
"createdCell": function (td, cellData, rowData, row, col) {
// Assign a unique ID to the <td> element
$(td).attr('id', `qr${cellData}`);
},
},
{
"title": "Status",
"data": "status",
},
{
"title": "Product Name",
"data": "productName",
"render": function (data, type, full, meta) {
if (!data) {
return "No Document";
}
var imageSrc = full.productPicture;
// Check if the document is an image based on file extension
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(imageSrc);
var isPdf = /\.pdf$/i.test(imageSrc);
// var imageSrc = full.productImage; Fallback to data if imgsrc is unavailable
console.log(full);
if (isImage) {
return ` <div class="row"><td>${data}</td></div>
<a href="${imageSrc}" target="_blank" data-lightbox="image-1">
<img src="${imageSrc}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
}
else if (isPdf) {
return `<div class="row"><td>${data}</td></div>
<a href="${imageSrc}" 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>`;
}
},
},
{
"title": "Product Category",
"data": "productCategory",
},
{
"title": "Request Quantity",
"data": "requestQuantity",
},
{
"title": "Station Deploy",
"data": "stationName",
"render": function (data, type, full, meta) {
return data ? data : "Self Assign";
}
},
{
"title": "Document / Picture",
"data": "document",
"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);
let isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
let isPdf = /\.pdf$/i.test(data);
if (isImage) {
return `<a href="${data}" target="_blank" data-lightbox="image-1">
@ -642,25 +462,43 @@
} else {
return `<a href="${data}" target="_blank">Download File</a>`;
}
},
},
{
"title": "Remark",
"data": "remarkUser",
},
{
"title": "Remark (Master)",
"data": "remarkMasterInv",
},
{
"title": "Request Date",
"data": "requestDate",
},
{
"title": "Approval Date",
"data": "approvalDate",
}
function renderDeleteButton(data) {
return `<button type="button" class="btn btn-danger delete-btn" data-id="${data}">Delete</button>`;
}
this.pendingRequestDatatable = $('#requestDatatable').DataTable({
"data": this.request.filter(req => req.status === "Requested"),
"columns": [
{ "title": "Request ID", "data": "requestID", "createdCell": (td, cellData) => $(td).attr('id', `qr${cellData}`) },
{ "title": "Product Name", "data": "productName", "render": (data, type, full) => renderDocument(full.productPicture) },
{ "title": "Product Category", "data": "productCategory" },
{ "title": "Request Quantity", "data": "requestQuantity" },
{ "title": "Document / Picture", "data": "document", "render": (data, type, full) => renderDocument(data) },
{ "title": "Remark", "data": "remarkUser" },
{ "title": "Station Deploy", "data": "stationName", "render": (data) => data || "Self Assign" },
{ "title": "Request Date", "data": "requestDate" },
{ "title": "Status", "data": "status" },
{ "title": "Delete", "data": "requestID", "render": renderDeleteButton, "className": "align-middle" }
],
responsive: true,
});
this.settledRequestDatatable = $('#settledrequestDatatable').DataTable({
"data": this.request.filter(req => req.status !== "Requested"),
"columns": [
{ "title": "Request ID", "data": "requestID", "createdCell": (td, cellData) => $(td).attr('id', `qr${cellData}`) },
{ "title": "Status", "data": "status" },
{ "title": "Product Name", "data": "productName", "render": (data, type, full) => renderDocument(full.productPicture) },
{ "title": "Product Category", "data": "productCategory" },
{ "title": "Request Quantity", "data": "requestQuantity" },
{ "title": "Station Deploy", "data": "stationName", "render": (data) => data || "Self Assign" },
{ "title": "Document / Picture", "data": "document", "render": (data, type, full) => renderDocument(data) },
{ "title": "Remark", "data": "remarkUser" },
{ "title": "Remark (Master)", "data": "remarkMasterInv" },
{ "title": "Request Date", "data": "requestDate" },
{ "title": "Approval Date", "data": "approvalDate" }
],
responsive: true,
});
@ -672,9 +510,9 @@
self.deleteRequestItem(requestID);
});
this.loading = false;
},
}
async fetchRequest() {
try

View File

@ -145,14 +145,6 @@
</span>
</div>
</li>
<!-- Station -->
<!-- <li class="list-group-item d-flex justify-content-between align-items-center"> -->
<!-- <span class="fw-bold"> -->
<!-- <i class="fas fa-map-marker-alt me-2 text-secondary"></i>Station: -->
<!-- </span> -->
<!-- <span class="text-muted">{{ thisItem.currentStation || 'N/A' }}</span> -->
<!-- </li> -->
</ul>
</div>
</div>
@ -542,15 +534,6 @@
async returnItemMovement() {
// const requiredFields = ['remark', 'consignmentNote'];
// for (let field of requiredFields) {
// if (!this[field]) {
// alert(`Request Error: Please fill in required field ${field}.`, 'warning');
// return;
// }
// }
if (!confirm("Are you sure you want to return this item?")) {
return false;
}

View File

@ -19,7 +19,7 @@
<h1 class="font-light text-white">
<i class="mdi mdi-factory"></i>
</h1>
<h6 class="text-white">Manifacturer</h6>
<h6 class="text-white">Manufacturer</h6>
</div>
</a>
</div>

View File

@ -0,0 +1,415 @@
using Microsoft.AspNetCore.Mvc;
using PSTW_CentralSystem.DBContext;
//using PSTW_CentralSystem.Areas.MMS.Models;
//using System.IO;
//using System.Linq;
using PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator;
using QuestPDF.Fluent;
//using System.Threading.Tasks;
//using System.Threading;
//using System.Collections.Generic;
//using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
namespace PSTW_CentralSystem.Areas.MMS.Controllers
{
public class TarballPdfDto //data transfer object, only holds data, used to move data between programs
{
// From tbl_marine_tarball
public int Id { get; set; }
public required string StationID { get; set; }
public required string Longitude { get; set; }
public required string Latitude { get; set; }
public required DateTime DateSample { get; set; }
public required TimeSpan TimeSample { get; set; }
public required string ClassifyID { get; set; }
public string? OptionalName1 { get; set; }
public string? OptionalName2 { get; set; }
public string? OptionalName3 { get; set; }
public string? OptionalName4 { get; set; }
public required string FirstSampler { get; set; }
// From joined tables
public required string LocationName { get; set; } // From tbl_marine_station
public required string StateName { get; set; } // From tbl_state
public required string FullName { get; set; } // From tbl_user
public required string LevelName { get; set; } // From tbl_level
}
[Area("MMS")]
public class MarineController : Controller
{
private readonly MMSSystemContext _context;//Used in TarBallForm and GeneratePdfResponse to query the database.
private readonly NetworkShareAccess _networkAccessService;//used in GetImage and GeneratePdfResponse
private const string PhotoBasePath = @"\\192.168.12.42\images\marine\manual_tarball";//used in GetImage and GeneratePdfResponse
public MarineController(MMSSystemContext context, NetworkShareAccess networkAccessService)
{
_context = context;
_networkAccessService = networkAccessService;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> TarBallForm()//make it async in case of traffic/frequent usage
{
try
{
var marineTarballs = await _context.MarineTarballs
.Select(t => new
{
id = t.Id,
date = t.DateSample.ToString("yyyy/MM/dd"),
station = t.StationID,
time = t.TimeSample.ToString("hh\\:mm\\:ss")
})
.ToListAsync();
Console.WriteLine($"Marine Tarballs Count: {marineTarballs.Count}"); //???
return View(marineTarballs);
}
catch (Exception ex)
{
return Content($"Error: {ex.Message}<br/>{ex.StackTrace}", "text/html");
}
}
[HttpGet] // Explicitly mark as a GET endpoint
//removal TBD===============!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
public IActionResult TestCredentials()
{
try
{
// Use the EXACT same path/credentials as in Program.cs
var testService = new NetworkShareAccess(
@"\\192.168.12.42\images\marine\manual_tarball",
"installer",
"mms@pstw"
);
testService.ConnectToNetworkPath();
testService.DisconnectFromNetworkShare();
return Ok("Network credentials and path are working correctly!");
}
catch (Exception ex)
{
// Log the full error (including stack trace)
Console.WriteLine($"TestCredentials failed: {ex}");
return StatusCode(500, $"Credentials test failed: {ex.Message}");
}
}
//====================!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
public IActionResult GetImage(string fileName)
{
if (string.IsNullOrEmpty(fileName))
{
return BadRequest("Filename cannot be empty");
}
// Sanitize filename to prevent path traversal attacks
var sanitizedFileName = Path.GetFileName(fileName);
if (sanitizedFileName != fileName)
{
return BadRequest("Invalid filename");
}
int retryCount = 0;
const int maxRetries = 3;
bool connectionSuccess = false;
// Retry loop for network connection
while (retryCount < maxRetries && !connectionSuccess)
{
try
{
Console.WriteLine($"Attempt {retryCount + 1} to connect to network share...");
// Connect to network share
_networkAccessService.ConnectToNetworkPath();
connectionSuccess = true;
Console.WriteLine("Network share connected successfully");
}
catch (Exception ex)
{
retryCount++;
Console.WriteLine($"Connection attempt {retryCount} failed: {ex.Message}");
if (retryCount >= maxRetries)
{
Console.WriteLine($"Max connection attempts reached. Last error: {ex}");
return StatusCode(503, $"Could not establish connection to image server after {maxRetries} attempts");
}
// Wait before retrying (1s, 2s, 3s)
Thread.Sleep(1000 * retryCount);
}
}
try
{
string imagePath = Path.Combine(PhotoBasePath, sanitizedFileName);
Console.WriteLine($"Attempting to access image at: {imagePath}");
// Verify file exists
//removal TBD
if (!System.IO.File.Exists(imagePath))
{
Console.WriteLine($"Image not found at path: {imagePath}");
return NotFound($"Image '{sanitizedFileName}' not found on server");
}
// Verify file is an image
//consider removal if/since verification already happened during image uploading (other program)
if (!IsImageValid(imagePath))
{
Console.WriteLine($"Invalid image file at path: {imagePath}");
return BadRequest("The requested file is not a valid image");
}
// Read and return the image
byte[] imageBytes = System.IO.File.ReadAllBytes(imagePath);
Console.WriteLine($"Successfully read image: {sanitizedFileName} ({imageBytes.Length} bytes)");
// Determine content type based on extension
//removal TBD??? IF image type verification already done during image uploading =========!!!!!!!!!!!
string contentType = "image/jpeg"; // default
string extension = Path.GetExtension(sanitizedFileName).ToLower();
//string extension = Path.GetExtension(sanitizedFileName)?.ToLower();
if (extension == ".png")
{
contentType = "image/png";
}
else if (extension == ".gif")
{
contentType = "image/gif";
}
return File(imageBytes, contentType);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied to image: {ex}");
return StatusCode(403, "Access to the image was denied");
}
catch (IOException ex)
{
Console.WriteLine($"IO error accessing image: {ex}");
return StatusCode(503, "Error accessing image file");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex}");
return StatusCode(500, "An unexpected error occurred while processing the image");
}
finally
{
try
{
if (connectionSuccess)
{
Console.WriteLine("Disconnecting from network share...");
_networkAccessService.DisconnectFromNetworkShare();
}
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Error disconnecting from share: {ex.Message}");
// Don't fail the request because of disconnect issues
}
}
}
public async Task<IActionResult> GenerateReport(int id)//calls GeneratePdfResponse to generate a PDF for inline viewing
{
return await GeneratePdfResponse(id, true);
}
//public async Task<IActionResult> DownloadPDF(int id)
//{
// return await GeneratePdfResponse(id, true);
//}
public IActionResult ViewPDF(int id)
{
try
{
// Add timeout for safety
var task = Task.Run(() => GeneratePdfResponse(id, false));
if (task.Wait(TimeSpan.FromSeconds(30))) // 30 second timeout
{
return task.Result;
}
return StatusCode(500, "PDF generation took too long");
}
catch (Exception ex)
{
Console.WriteLine($"PDF VIEW ERROR: {ex}");
return StatusCode(500, $"Error showing PDF: {ex.Message}");
}
}
private async Task<IActionResult> GeneratePdfResponse(int id, bool forceDownload)
{
Console.WriteLine($"Requested ID in {(forceDownload ? "GenerateReport" : "ViewPDF")}: {id}");
// Test network connection first
try
{
_networkAccessService.ConnectToNetworkPath();
_networkAccessService.DisconnectFromNetworkShare();
}
catch (Exception ex)
{
return StatusCode(500, $"Cannot access network: {ex.Message}");
}
try
{
_networkAccessService.ConnectToNetworkPath();
// ===== 1. Get Data from Database =====
var query = @"
SELECT
marine.*,
station.LocationName,
state.StateName,
user.FullName,
level.LevelName
FROM tbl_marine_tarball marine
JOIN tbl_marine_station station ON marine.StationID = station.StationID
JOIN tbl_state state ON station.StateID = state.StateID
JOIN tbl_user user ON marine.FirstSampler = user.FullName
JOIN tbl_level level ON user.LevelID = level.LevelID
WHERE marine.Id = @id";
var tarball = await _context.Database
.SqlQueryRaw<TarballPdfDto>(query, new MySqlParameter("@id", id))
.FirstOrDefaultAsync();
if (tarball == null)
return NotFound("Record not found");
// Prepare boolean values for PDF
bool tarBallYes = tarball.ClassifyID != "NO";
bool tarBallNo = tarball.ClassifyID == "NO";
bool isSand = tarball.ClassifyID == "SD";
bool isNonSandy = tarball.ClassifyID == "NS";
bool isCoquina = tarball.ClassifyID == "CO";
// ===== 2. Get Images =====
// For date (stored as DATE in DB → "2025-01-30" becomes "20250130")
var sampleDateString = tarball.DateSample.ToString("yyyyMMdd");
// For time (stored as TIME in DB → "16:49:02" becomes "164902")
var sampleTimeString = tarball.TimeSample.ToString("hhmmss");
var stationFolder = Path.Combine(PhotoBasePath, tarball.StationID);
var stationImages = new Dictionary<string, string>();
var foundAnyImages = false;
if (Directory.Exists(stationFolder))
{
var basePattern = $"{tarball.StationID}_{sampleDateString}_{sampleTimeString}_";
var allImages = Directory.GetFiles(stationFolder, $"{basePattern}*");
foreach (var imagePath in allImages)
{
var fileName = Path.GetFileNameWithoutExtension(imagePath);
var type = fileName.Split('_').Last();
stationImages[type] = imagePath;
foundAnyImages = true;
}
}
if (!foundAnyImages)
{
return StatusCode(404, "No images found for this record");
}
// Verify mandatory images exist
var mandatoryImages = new[] {
"LEFTSIDECOASTALVIEW",
"RIGHTSIDECOASTALVIEW",
"DRAWINGVERTICALLINES",
"DRAWINGHORIZONTALLINES"
};
foreach (var type in mandatoryImages)
{
if (!stationImages.ContainsKey(type))
{
return StatusCode(400, $"Missing mandatory image: {type}");
}
}
// ===== 3. Generate PDF =====
var pdf = new TarBallPDF(
tarball.StateName,
tarball.StationID,
tarball.LocationName,
tarball.Longitude,
tarball.Latitude,
tarball.DateSample,
tarball.TimeSample,
tarball.ClassifyID,
tarBallYes,
tarBallNo,
isSand,
isNonSandy,
isCoquina,
stationImages["LEFTSIDECOASTALVIEW"],
stationImages["RIGHTSIDECOASTALVIEW"],
stationImages["DRAWINGVERTICALLINES"],
stationImages["DRAWINGHORIZONTALLINES"],
stationImages.GetValueOrDefault("OPTIONAL01"),
stationImages.GetValueOrDefault("OPTIONAL02"),
stationImages.GetValueOrDefault("OPTIONAL03"),
stationImages.GetValueOrDefault("OPTIONAL04"),
tarball.OptionalName1,
tarball.OptionalName2,
tarball.OptionalName3,
tarball.OptionalName4,
tarball.FirstSampler,
tarball.FullName,
tarball.LevelName
).GeneratePdf();
// ===== 4. Return PDF =====
var downloadName = $"{tarball.StationID}_{sampleDateString}_{sampleTimeString}.pdf";
return forceDownload
? File(pdf, "application/pdf", downloadName)
: File(pdf, "application/pdf");
}
catch (Exception ex)
{
return StatusCode(500, $"PDF generation failed: {ex.Message}");
}
finally
{
_networkAccessService.DisconnectFromNetworkShare();
}
}
private bool IsImageValid(string imagePath)
{
try
{
using (var image = System.Drawing.Image.FromFile(imagePath))
return true;
}
catch
{
Console.WriteLine($"Invalid image skipped: {imagePath}");
return false;
}
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.ComponentModel;
using System.Threading;
public class NetworkShareAccess : IDisposable
{
private readonly string _networkPath;
private readonly string _username;
private readonly string _password;
private bool _isConnected = false;
private static readonly object _lock = new object();
public NetworkShareAccess(string networkPath, string username, string password)
{
_networkPath = networkPath?.Trim().Replace('/', '\\') ?? throw new ArgumentNullException(nameof(networkPath));
if (!_networkPath.StartsWith(@"\\"))
throw new ArgumentException("Network path must start with \\\\");
_username = username ?? throw new ArgumentNullException(nameof(username));
_password = password ?? throw new ArgumentNullException(nameof(password));
}
public void ConnectToNetworkPath()
{
lock (_lock)
{
if (_isConnected) return;
int retries = 0;
while (retries < 3)
{
try
{
var netResource = new NetResource
{
Scope = ResourceScope.GlobalNetwork,
ResourceType = ResourceType.Disk,
DisplayType = ResourceDisplayType.Share,
RemoteName = _networkPath
};
int result = WNetAddConnection2(netResource, _password, _username, 0);
if (result == 0)
{
_isConnected = true;
Console.WriteLine($"Connected to {_networkPath}");
return;
}
if (result == 1219) // Multiple connections
{
WNetCancelConnection2(_networkPath, 0, true);
Thread.Sleep(1000);
retries++;
continue;
}
throw new IOException($"Failed to connect. Error {result}: {GetNetworkErrorDescription(result)}");
}
catch
{
if (++retries >= 3) throw;
Thread.Sleep(1000);
}
}
}
}
public void DisconnectFromNetworkShare()
{
lock (_lock)
{
if (!_isConnected) return;
try
{
int result = WNetCancelConnection2(_networkPath, 0, true);
if (result != 0)
Console.WriteLine($"Warning: Disconnect failed (Error {result})");
}
finally
{
_isConnected = false;
}
}
}
public void Dispose() => DisconnectFromNetworkShare();
private string GetNetworkErrorDescription(int errorCode) => errorCode switch
{
5 => "Access denied",
53 => "Network path not found",
67 => "Network name not found",
85 => "Network connection already exists",
86 => "Invalid password",
1219 => "Multiple connections to a server or shared resource not allowed",
_ => new Win32Exception(errorCode).Message
};
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
private static extern int WNetCancelConnection2(string name, int flags, bool force);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private class NetResource
{
public ResourceScope Scope;
public ResourceType ResourceType;
public ResourceDisplayType DisplayType;
public int Usage;
public string? LocalName; //? or required TBD
public string? RemoteName; //
public string? Comment; //
public string? Provider; //
}
private enum ResourceScope { Connected = 1, GlobalNetwork, Remembered, Recent, Context }
private enum ResourceType { Any = 0, Disk = 1, Print = 2, Reserved = 8 }
private enum ResourceDisplayType { Generic = 0x0, Domain = 0x01, Server = 0x02, Share = 0x03, File = 0x04, Group = 0x05 }
}

View File

@ -0,0 +1,405 @@
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.Helpers;
using Google.Protobuf.WellKnownTypes;
using PSTW_CentralSystem.Models;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
{
public class TarBallPDF(string stateName, string stationID, string locationName,
string longitude, string latitude, DateTime dateSample, TimeSpan timeSample,
string classifyID, bool tarBallYes, bool tarBallNo, bool isSand, bool isNonSandy,
bool isCoquina, string photoPath1, string photoPath2, string photoPath3, string photoPath4,
string? photoPath5, string? photoPath6, string? photoPath7, string? photoPath8,
string? optionalName1, string? optionalName2, string? optionalName3, string? optionalName4,
string firstSampler, string fullName, string levelName
)
: IDocument
{
//have to be arranged accordingly(?)
private readonly string _stateName = stateName;
private readonly string _stationId = stationID;
private readonly string _locationName = locationName;
private readonly string _longitude = longitude;
private readonly string _latitude = latitude;
private readonly DateTime _dateSample = dateSample;
private readonly TimeSpan _timeSample = timeSample;
private readonly string _classifyID = classifyID;
private readonly bool _tarBallYes = tarBallYes;
private readonly bool _tarBallNo = tarBallNo;
private readonly bool _isSand = isSand;
private readonly bool _isNonSandy = isNonSandy;
private readonly bool _isCoquina = isCoquina;
private readonly string _photoPath1 = photoPath1;
private readonly string _photoPath2 = photoPath2;
private readonly string _photoPath3 = photoPath3;
private readonly string _photoPath4 = photoPath4;
private readonly string? _photoPath5 = photoPath5;
private readonly string? _photoPath6 = photoPath6;
private readonly string? _photoPath7 = photoPath7;
private readonly string? _photoPath8 = photoPath8;
private readonly string? _optionalName1 = optionalName1;
private readonly string? _optionalName2 = optionalName2;
private readonly string? _optionalName3 = optionalName3;
private readonly string? _optionalName4 = optionalName4;
private readonly string _firstSampler = firstSampler;
private readonly string _fullName = fullName;
private readonly string _levelName = levelName;
private Image? LoadImage(string? imagePath)
{
if (string.IsNullOrEmpty(imagePath)) //check if photo is missing
{
return null; // returns 'nothing' (safe)
}
try
{
return Image.FromFile(imagePath); //load photo
}
catch
{
return null; //if loading fails, returns 'nothing' (safe)
}
}
public DocumentMetadata GetMetadata() => new()
{
Title = "TARBALL SAMPLING FORM",
Author = "PAKAR SCIENO TW Integrated Environmental Solutions",
Subject = "Environmental Survey and Observations"
};
// Compose the PDF content
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
// Page Setup
page.Size(PageSizes.A4);
page.Margin(1.1f, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
//HEADER SECTION ==========================================================================
//will be included in each page
page.Header().Row(row =>
{
row.RelativeItem(1).Element(CellStyle)
.AlignMiddle()
.AlignCenter()
.Image("wwwroot/assets/images/pstw-logo.jpg")
.FitArea();
row.RelativeItem(1).Element(CellStyle)
.AlignMiddle()
.AlignCenter()
.Text("TARBALL SAMPLING FORM")
.FontSize(16)
.FontColor("#4B0082");
row.RelativeItem(1).Column(column =>
{
column.Spacing(0);
column.Item().Row(innerRow =>
{
innerRow.RelativeItem(1).Element(CellStyle).Text("Document:")
.AlignLeft();
innerRow.RelativeItem(1).Element(CellStyle).Text("F-MM06")
.AlignLeft().Bold();
});
column.Item().Row(innerRow =>
{
innerRow.RelativeItem(1).Element(CellStyle).Text("Effective Date:")
.AlignLeft();
innerRow.RelativeItem(1).Element(CellStyle).Text("1 April 2025")
.AlignLeft();
});
column.Item().Row(innerRow =>
{
innerRow.RelativeItem(1).Element(CellStyle).Text("Revision No.")
.AlignLeft();
innerRow.RelativeItem(1).Element(CellStyle).Text("02")
.AlignLeft();
});
});
static IContainer CellStyle(IContainer container)
=> container.Border(0.5f).Padding(5);
});
//HEADER SECTION END ==========================================================================
// CONTENT SECTION ============================================================================
page.Content().Column(column =>
{
// Observations Table
column.Item().Element(container =>
{
container
.PaddingTop(20)
.PaddingBottom(10)
.Text("Please be informed that we have observed the following conditions:");
});
column.Item().Table(table =>
{
column.Spacing(0);
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3);
columns.RelativeColumn(3);
});
table.Cell()
.Background(Colors.Grey.Lighten2).Element(CellStyle).Text("STATE")
.Bold();
table.Cell().Element(CellStyle).Text(_stateName);
table.Cell()
.Background(Colors.Grey.Lighten2).Element(CellStyle).Text("STATION ID")
.Bold();
table.Cell().Element(CellStyle).Text(_stationId);
table.Cell()
.Background(Colors.Grey.Lighten2).Element(CellStyle).Text("LOCATION")
.Bold();
table.Cell().Element(CellStyle).Text(_locationName);
table.Cell()
.Background(Colors.Grey.Lighten2).Element(CellStyle).Text("TARBALL SURVEY LOCATION LONGITUDE & LATITUDE")
.Bold();
table.Cell().Element(CellStyle).Text($"{_longitude}, {_latitude}");
table.Cell()
.Background(Colors.Grey.Lighten2).Element(CellStyle).Text("DATE / TIME")
.Bold();
table.Cell().Element(CellStyle).Text($"{_dateSample:yyyy-MM-dd} {_timeSample:hh\\:mm\\:ss}");
});
column.Spacing(3);
// Survey Findings =======================================================================================
column.Item()
.PaddingTop(10)
.PaddingBottom(10)
.Text("SURVEY FINDING:")
.Bold().FontSize(12);
column.Item()
.PaddingBottom(10)
.Text(text =>
{
text.Span("Tar Ball: ").Style(TextStyle.Default.FontSize(10));
text.Span(_classifyID == "NO" ? " ☐ YES ☑ NO" : " ☑ YES ☐ NO").Style(TextStyle.Default.FontSize(10));
});
column.Item()
.PaddingBottom(10)
.Text("If YES, Tar Ball falls under the Classification of:")
.FontSize(10);
column.Item()
.PaddingBottom(10)
.Text(text =>
{
text.Span(_classifyID == "SD" ? "☑ Sand " : "☐ Sand ").Style(TextStyle.Default.FontSize(10));
text.Span(_classifyID == "NS" ? " ☑ Non-sandy " : " ☐ Non-sandy ").Style(TextStyle.Default.FontSize(10));
text.Span(_classifyID == "CQ" ? "☑ Coquina" : "☐ Coquina").Style(TextStyle.Default.FontSize(10));
});
column.Item()
.PaddingBottom(10)
.Text("*tick wherever applicable")
.Italic();
// Photos Section Title
column.Item()
.PaddingBottom(5)
.Text("PHOTOGRAPHS OF SAMPLING")
.AlignCenter()
.Bold()
.FontSize(14);
// Photos Section
column.Item().Table(table =>
{
column.Spacing(0);
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
// Row 1: Photos ====================================
table.Cell().Element(CellStyle).Height(150)
.Image(LoadImage(_photoPath1) ?? null) // Loads image or uses null. if exist, fill cell. if null, cell stay empty
.FitArea();
table.Cell().Element(CellStyle).Height(150)
.Image(LoadImage(_photoPath2) ?? null)
.FitArea();
// Row 2: Captions for figure 1 & 2 =================
table.Cell().Element(CellStyle)
.Text("Figure 1: Left Side Coastal View")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle)
.Text("Figure 2: Right Side Coastal View")
.FontSize(12).AlignLeft();
// Row 3 =============================================
table.Cell().Element(CellStyle).Height(150)
.Image(LoadImage(_photoPath3) ?? null) // Just pass null if no image
.FitArea();
table.Cell().Element(CellStyle).Height(150)
.Image(LoadImage(_photoPath4) ?? null) // Just pass null if no image
.FitArea();
// Row 4: Captions for figure 3 & 4 =================
table.Cell().Element(CellStyle)
.Text("Figure 3: Drawing Vertical Lines")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle)
.Text("Figure 4: Drawing Horizontal Lines (Racking)")
.FontSize(12).AlignLeft();
});
// Page Break
column.Item().PageBreak();
column.Spacing(3);
// Additional Photos Section
column.Item().Table(table =>
{
column.Spacing(3);
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
// Helper function to safely add image cells
void AddOptionalImageCell(string imagePath)
{
table.Cell().Element(CellStyle).Height(150).Element(cell =>
{
var image = LoadImage(imagePath);
if (image != null)
{
cell.Image(image).FitArea();
}
// If null, leaves an empty cell
});
}
// Row 1: Optional images 5 & 6
AddOptionalImageCell(_photoPath5);
AddOptionalImageCell(_photoPath6);
// Row 2: Captions
table.Cell().Element(CellStyle)
.Text($"Figure 5: {(_optionalName1 ?? "")}")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle)
.Text($"Figure 6: {(_optionalName2 ?? "")}")
.FontSize(12).AlignLeft();
// Row 3: Optional images 7 & 8
AddOptionalImageCell(_photoPath7);
AddOptionalImageCell(_photoPath8);
// Row 4: Captions
table.Cell().Element(CellStyle)
.Text($"Figure 7: {(_optionalName3 ?? "")}")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle)
.Text($"Figure 8: {(_optionalName4 ?? "")}")
.FontSize(12).AlignLeft();
});
// Note Section
column.Item()
.PaddingTop(10)
.PaddingBottom(20)
.Text("* If there are any event observe at the current station it is compulsory to add optional photo with description (figure 5 to figure 8)")
.Bold()
.FontSize(10)
.AlignLeft();
// Signature Section
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
//the overall layout of the authorization section
columns.RelativeColumn(2);
columns.RelativeColumn(1);
columns.RelativeColumn(2);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
table.Cell().RowSpan(2).Element(CellStyle)
.Text(text =>
{
text.Span("REPORTED BY: ").Bold().FontSize(12);
text.Span(_firstSampler).FontSize(12);
});
table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("Date").FontSize(12);
table.Cell().Element(CellStyle).Text($"{_dateSample:dd/MM/yyyy}").FontSize(12);
table.Cell().Element(CellStyle).Text("Designation").FontSize(12);
table.Cell().ColumnSpan(3).Element(CellStyle).Text(_levelName).FontSize(12);
table.Cell().RowSpan(2).Element(CellStyle)
.Text(text =>
{
text.Span("CHECKED BY: ").Bold().FontSize(12);
text.Span("RIFAIE AZHARI").FontSize(12);
});
table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().ColumnSpan(2).Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("Designation").FontSize(12);
table.Cell().ColumnSpan(3).Element(CellStyle).Text("Executive").FontSize(12);
table.Cell().RowSpan(2).Element(CellStyle)
.Text(text =>
{
text.Span("VERIFIED BY: ").Bold().FontSize(12);
text.Span("J SOMU").FontSize(12);
});
table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().ColumnSpan(2).Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("Designation").FontSize(12);
table.Cell().ColumnSpan(3).Element(CellStyle).Text("Technical Manager").FontSize(12);
});
});
// Footer Section
page.Footer().AlignCenter().Text(text =>
{
});
static IContainer CellStyle(IContainer container) => container.Border(0.5f).Padding(5);
});
}
}
}

View File

@ -0,0 +1,6 @@
@{
ViewData["Title"] = "MMS Dashboard";
}
<h1>Welcome to the MMS Dashboard</h1>
<p>This is where youll add content for MMS later.</p>

View File

@ -0,0 +1,224 @@
@{
ViewData["Title"] = "Tarball Report";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<title>Tarball Report</title>
<style>
.container {
width: 1400px; /* Approximate width for A4 aspect ratio */
margin: 20px auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
div {
padding-top: 5px;
padding-bottom: 5px;
}
h4 {
padding-top: 15px;
padding-bottom: 5px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ccc;
padding: 10px;
}
</style>
<!-- DataTables CSS -->
<link href="~/lib/datatables/datatables.css" rel="stylesheet" />
</head>
<body>
<div id="app" class="container">
<div>
<h4>Month</h4>
<select v-model="selectedMonth" style="width: 100%; padding: 5px;">
<option value="" selected>Filter by Month</option>
<option v-for="month in months" :value="month">{{ month }}</option>
</select>
<h4>Year</h4>
<select v-model="selectedYear" style="width: 100%; padding: 5px;">
<option value="" selected>Filter by Year</option>
<option v-for="year in years" :value="year">{{ year }}</option>
</select>
<div>
<button v-on:click="clearFilters" class="btn btn-default" style="margin-top: 20px; margin-bottom: 25px;">Clear Filter</button>
</div>
</div>
<div class="datatable">
<table id="tarballTable" class="table table-bordered table-hover table-striped" style="width:100%;">
<thead>
<tr>
<th>No.</th>
<th>Date</th>
<th>Station</th>
<th>Time</th>
<th>Status</th>
<th>PDF</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</body>
</html>
@section Scripts {
<!-- DataTables JS -->
<script src="~/lib/datatables/datatables.js"></script>
<script>
new Vue({
el: '#app',
data: {
selectedMonth: '',
selectedYear: '',
months: [
'January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September',
'October', 'November', 'December'
],
dataFromServer: @Json.Serialize(Model ?? new List<object>()),
dataTable: null
},
mounted() {
if (this.dataFromServer && this.dataFromServer.length > 0) {
this.$nextTick(() => {
this.initializeDataTable();
});
} else {
console.log("No data received from server");
}
},
computed: {
years() {
if (!this.dataFromServer || this.dataFromServer.length === 0) {
return []; // Return an empty array if no data is available
}
// Extract all years from the data
const allYears = this.dataFromServer.map(data => new Date(data.date).getFullYear());
// Find the minimum and maximum years
const minYear = Math.min(...allYears);
const maxYear = Math.max(...allYears);
// Generate a range of years from minYear to maxYear
return Array.from({ length: maxYear - minYear + 1 }, (_, i) => (minYear + i).toString());
},
sortedFilteredData() {
// If no filters are applied, return all data sorted by descending date
if (!this.selectedMonth && !this.selectedYear) {
return this.dataFromServer.sort((a, b) => new Date(b.date) - new Date(a.date));
}
// Filter data by selected month and year
const filtered = this.dataFromServer.filter(data => {
const date = new Date(data.date);
const monthMatches = this.selectedMonth
? date.toLocaleString('default', { month: 'long' }) === this.selectedMonth
: true;
const yearMatches = this.selectedYear
? date.getFullYear().toString() === this.selectedYear
: true;
return monthMatches && yearMatches;
});
// Sort data by date in descending order
return filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
}
},
methods: {
clearFilters() {
this.selectedMonth = '';
this.selectedYear = '';
},
initializeDataTable() {
// Initialize DataTables
const self = this; // Store the Vue instance context
this.dataTable = $('#tarballTable').DataTable({
"pageLength": 10,
"lengthMenu": [5, 10, 15, 20],
"responsive": true,
"order": [[1, "desc"]], // Default sorting by Date column (descending)
"orderMulti": false, // Disable multi-column sorting
"columns": [
{ "data": null,"render": (data, type, row, meta) => meta.row + 1},
{ "data": "date", "render": (data) => new Date(data).toLocaleDateString('en-GB') },
{ "data": "station" },
{ "data": "time" }, // Removed the incorrect toLocaleDateString()
{
"data": null,
"render": () => `
<button class="btn btn-success" disabled>Approve</button>
<button class="btn btn-danger" disabled>Reject</button>
`
},
{
"data": null,
"render": (data) => `
<a href="/MMS/Marine/ViewPDF?id=${data.id}" class="btn btn-primary" target="_blank">View PDF</a>
<a href="/MMS/Marine/GenerateReport?id=${data.id}" class="btn btn-primary">Download PDF</a>
`
//uses ViewPDF for view, GenerateReport for downloading
}
],
"rowCallback": function(row, data, index) {
// Update the "No." column to start from 1 for the current page
const pageInfo = this.api().page.info();
$('td:first', row).html(pageInfo.start + index + 1);
}
});
// Populate the table with initial data
this.updateDataTable(this.sortedFilteredData);
//auto-filtering
$('#tarballTable_filter input').on('keyup', function () {
self.dataTable.search(this.value).draw();
});
},
updateDataTable(data) {
if (this.dataTable) {
this.dataTable.clear();
this.dataTable.rows.add(data);
this.dataTable.draw();
}
}
},
mounted() {
// Initialize DataTables after Vue has rendered the table
this.$nextTick(() => {
this.initializeDataTable();
});
},
watch: {
sortedFilteredData(newData) {
// Automatically update DataTables whenever the filtered data changes
this.updateDataTable(newData);
//trigger search function after updating data(?)
if(this.dataTable) {
this.dataTable.search('').draw();
}
}
}
});
</script>
}

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
using Mono.TextTemplating;
using Newtonsoft.Json;
using PSTW_CentralSystem.Areas.Inventory.Models;
@ -265,6 +266,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
var userRole = await _userManager.GetRolesAsync(user);
var isAdmin = userRole.Contains("SystemAdmin") || userRole.Contains("SuperAdmin") || userRole.Contains("Finance");
List<ItemModel> itemList = new List<ItemModel>();
List<ItemMovementModel> createDate = new List<ItemMovementModel>();
// Get the item list
if (isAdmin)
{
@ -296,14 +298,22 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
.ThenInclude(m => m!.FromUser)
.Where(i => i.DepartmentId == user.departmentId)
.ToListAsync();
}
// Get the departments list (DepartmentId references Departments)
var departments = await _centralDbContext.Departments.ToListAsync();
var createDates = await _centralDbContext.ItemMovements.Where(r => r.Action == "Register").ToListAsync();
// Buat dictionary agar lebih cepat dicari berdasarkan ItemID
var createDateDict = await _centralDbContext.ItemMovements.Where(r => r.Action == "Register").GroupBy(r => r.ItemId).ToDictionaryAsync(g => g.Key, g => g.Min(m => m.Date));
// Now join items with users and departments manually
var itemListWithDetails = itemList.Select(item => new
{
createDate = createDateDict.ContainsKey(item.ItemID) ? createDateDict[item.ItemID].ToString("dd/MM/yyyy HH:mm:ss") : null,
item.ItemID,
item.UniqueID,
item.CompanyId,
@ -324,6 +334,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
EndWDate = item.EndWDate.ToString("dd/MM/yyyy"),
InvoiceDate = item.InvoiceDate?.ToString("dd/MM/yyyy"),
item.Department?.DepartmentName,
RegisterDate = createDate,
CreatedBy =item.CreatedBy!.UserName,
item.Product!.ProductName,
item.Product!.ProductShortName,
@ -465,12 +476,26 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return NotFound(new { success = false, message = "Item not found" });
}
// Get related item movements
var itemMovements = await _centralDbContext.ItemMovements
.Where(i => i.ItemId == item.ItemID)
.ToListAsync();
// Remove all related item movements
if (itemMovements.Any())
{
_centralDbContext.ItemMovements.RemoveRange(itemMovements);
await _centralDbContext.SaveChangesAsync();
}
// Remove the item itself
_centralDbContext.Items.Remove(item);
await _centralDbContext.SaveChangesAsync();
return Ok(new { success = true, message = "Item deleted successfully" });
}
[HttpPost("GetItem/{id}")] // Endpoint to retrieve an item by its ID
public async Task<IActionResult> GetItem(string id)
{
@ -538,6 +563,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
item.Movement?.ToOther,
item.Movement?.LatestStatus,
item.Movement?.LastUser,
item.Movement?.LastStore,
item.Movement?.MovementComplete,
remark = item.Movement?.Remark,
QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.UniqueID}" // Generate QR String
@ -565,16 +591,18 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
.Include(i => i.NextUser)
.ToListAsync();
int itemrow = 0;
var itemMovementListWithQR = itemMovementList.Select(item => new
{
id = itemrow++,
item, // Includes all properties of the original item
QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.ItemId}", // Generate QR String
ProductName = item.Item?.Product?.ProductName,
toUserName = item.NextUser?.FullName,
lastUserName = item.FromUser?.FullName
}).ToList();
// if use qr, need to use this. else do simple return json. datatable qr will read dom and replace element with id=qr{qrstring} with qr image.
// then need dynamic numbering for qr even if item movement is repeated by the same item
//int itemrow = 0;
//var itemMovementListWithQR = itemMovementList.Select(item => new
//{
// id = itemrow++,
// item, // Includes all properties of the original item
// QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{item.ItemId}", // Generate QR String
// ProductName = item.Item?.Product?.ProductName,
// toUserName = item.NextUser?.FullName,
// lastUserName = item.FromUser?.FullName
//}).ToList();
//Console.WriteLine(Json(itemMovementListWithQR));
@ -736,6 +764,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return NotFound("Item movement record not found.");
}
updatedList.LastUser = receiveMovement.LastUser;
updatedList.LatestStatus = receiveMovement.LatestStatus;
updatedList.receiveDate = receiveMovement.receiveDate;
updatedList.Remark = receiveMovement.Remark;
@ -969,6 +998,74 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
#endregion
#region ItemRequestAdmin
[HttpPost("AddRequestMaster")]
public async Task<IActionResult> AddRequestMaster([FromBody] RequestModel request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var findUniqueCode = _centralDbContext.Products.FirstOrDefault(r => r.ProductId == request.ProductId);
var findUniqueUser = _centralDbContext.Users.FirstOrDefault(r => r.Id == request.UserId);
if (!string.IsNullOrEmpty(request.Document))
{
var bytes = Convert.FromBase64String(request.Document);
string filePath = "";
var uniqueAbjad = new string(Enumerable.Range(0, 8).Select(_ => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[new Random().Next(36)]).ToArray());
if (IsImage(bytes))
{
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", findUniqueUser.FullName + " " + findUniqueCode.ModelNo + "(" + uniqueAbjad + ") Request.jpg");
request.Document = "/media/inventory/request/" + findUniqueUser.FullName + " " + findUniqueCode.ModelNo + "(" + uniqueAbjad + ") Request.jpg";
}
else if (IsPdf(bytes))
{
filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/media/inventory/request", findUniqueUser.FullName + " " + findUniqueCode.ModelNo + "_Request.pdf");
request.Document = "/media/inventory/request/" + findUniqueUser.FullName + " " + findUniqueCode.ModelNo + "(" + uniqueAbjad + ") Request.pdf";
}
else
{
return BadRequest("Unsupported file format.");
}
await System.IO.File.WriteAllBytesAsync(filePath, bytes);
}
_centralDbContext.Requests.Add(request);
await _centralDbContext.SaveChangesAsync();
var updatedList = await _centralDbContext.Requests.ToListAsync();
return Json(updatedList.Select(i => new
{
i.ProductId,
i.UserId,
i.status,
i.StationId,
i.RequestQuantity,
i.requestDate,
i.ProductCategory,
i.Document,
i.approvalDate,
i.remarkMasterInv,
i.remarkUser,
i.fromStoreItem,
i.assignStoreItem,
}));
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpGet("ItemRequestList")]
public async Task<IActionResult> ItemRequestList()
{
@ -977,6 +1074,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return Json(itemRequestList.Select(i => new
{
i.requestID,
i.UserId,
productName = i.Product?.ProductName,
i.ProductId,
productImage = i.Product?.ImageProduct,
@ -991,6 +1089,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.approvalDate,
i.remarkMasterInv,
i.remarkUser,
i.assignStoreItem,
i.fromStoreItem,
}));
@ -1188,6 +1288,22 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
return Json(storeList);
}
[HttpPost("StoreSpecificMasterList/{userId}")]
public async Task<IActionResult> StoreSpecificMasterList(int userId)
{
var storeList = await _centralDbContext.InventoryMasters
.Where(i => i.UserId == userId)
.Select(i => i.StoreId) // Extract only StoreIds
.Distinct() // Avoid duplicate queries
.ToListAsync();
var storeSpecific = await _centralDbContext.Stores
.Where(s => storeList.Contains(s.Id)) // Fetch all relevant stores at once
.ToListAsync();
return Json(storeSpecific);
}
#endregion Store
#region AllUser
@ -1222,7 +1338,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
{
return NotFound("Item movement record not found.");
}
updatedList.MovementComplete = receiveMovement.MovementComplete;
updatedList.LatestStatus = receiveMovement.LatestStatus;
updatedList.receiveDate = receiveMovement.receiveDate;
updatedList.MovementComplete = receiveMovement.MovementComplete;

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace PSTW_CentralSystem.Controllers.API
{
[Route("api/[controller]")]
[ApiController]
public class MarineAPI : ControllerBase
{
}
}

View File

@ -5,6 +5,8 @@ using PSTW_CentralSystem.Models;
using Newtonsoft.Json;
using System.Text.Json;
using System.Data;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
namespace PSTW_CentralSystem.CustomPolicy
{
@ -32,7 +34,7 @@ namespace PSTW_CentralSystem.CustomPolicy
var userRole = await _userManager.GetRolesAsync(currentUser ?? new UserModel());
var moduleName = _httpContextAccessor.HttpContext?.GetRouteData().Values["controller"]?.ToString();
var pageName = _httpContextAccessor.HttpContext?.GetRouteData().Values["action"]?.ToString();
var registeredModule = _authDBContext.ModuleSettings.FirstOrDefault(x => x.ModuleName == moduleName);
var registeredModule = await _authDBContext.ModuleSettings.Where(x => x.ModuleName == moduleName).ToListAsync();
if (checkIfSuperAdmin())
{
@ -83,16 +85,22 @@ namespace PSTW_CentralSystem.CustomPolicy
void checkModuleActiveOrNot()
{
if (registeredModule.ModuleStatus == 0)
foreach (var module in registeredModule)
{
if (module.ModuleStatus == 0)
{
context.Fail();
return;
}
}
}
void checkModuleHaveRoleOrNot()
{
var allowedUserTypes = registeredModule?.AllowedUserType ?? "";
bool isModuleHaveRole = false;
foreach (var module in registeredModule)
{
var allowedUserTypes = module?.AllowedUserType ?? "";
if (allowedUserTypes == "Public" || userRole.Any(role => allowedUserTypes.Contains(role)))
{
context.Succeed(requirement);
@ -100,13 +108,23 @@ namespace PSTW_CentralSystem.CustomPolicy
}
else if (currentUser != null && allowedUserTypes == "Registered User")
{
checkMethodAndRole();
isModuleHaveRole = true;
}
else
{
isModuleHaveRole = false;
}
}
if (!isModuleHaveRole)
{
context.Fail();
return;
}
else
{
checkMethodAndRole();
}
}
void checkMethodAndRole()

View File

@ -0,0 +1,146 @@
//using Microsoft.AspNetCore.Authorization;
//using Microsoft.AspNetCore.Identity;
//using PSTW_CentralSystem.DBContext;
//using PSTW_CentralSystem.Models;
//using Newtonsoft.Json;
//using System.Text.Json;
//using System.Data;
//namespace PSTW_CentralSystem.CustomPolicy
//{
// public class RoleModulePolicy : IAuthorizationRequirement
// {
// }
// public class RoleModuleHandler : AuthorizationHandler<RoleModulePolicy>
// {
// private readonly CentralSystemContext _authDBContext;
// private readonly UserManager<UserModel> _userManager;
// private readonly RoleManager<RoleModel> _roleManager;
// private readonly IHttpContextAccessor _httpContextAccessor;
// public RoleModuleHandler( CentralSystemContext authDBContext, UserManager<UserModel> userManager, RoleManager<RoleModel> roleManager, IHttpContextAccessor httpContextAccessor)
// {
// _authDBContext = authDBContext;
// _userManager = userManager;
// _roleManager = roleManager;
// _httpContextAccessor = httpContextAccessor;
// }
// protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleModulePolicy requirement)
// {
// // Get the current user
// var currentUser = await _userManager.GetUserAsync(context.User);
// var userRole = await _userManager.GetRolesAsync(currentUser ?? new UserModel());
// var moduleName = _httpContextAccessor.HttpContext?.GetRouteData().Values["controller"]?.ToString();
// var pageName = _httpContextAccessor.HttpContext?.GetRouteData().Values["action"]?.ToString();
// var registeredModule = _authDBContext.ModuleSettings.FirstOrDefault(x => x.ModuleName == moduleName);
// if (checkIfSuperAdmin())
// {
// context.Succeed(requirement);
// return;
// }
// else {
// checkModuleExistOrNot();
// checkModuleHaveRoleOrNot();
// }
// bool checkIfSuperAdmin()
// {
// var superAdminRole = _authDBContext.Roles.Where(r => r.Name == "SuperAdmin").FirstOrDefault();
// var sysAdminRole = _authDBContext.Roles.Where(r => r.Name == "SystemAdmin").FirstOrDefault();
// if (userRole.ToString() != null && userRole.Contains("SuperAdmin") && superAdminRole?.Id == 1)
// {
// return true;
// }
// else if (userRole.ToString() != null && userRole.Contains("SystemAdmin") && sysAdminRole?.Id == 2)
// {
// return true;
// }
// else
// {
// return false;
// }
// }
// void checkModuleExistOrNot()
// {
// if ( moduleName == "Admin")
// {
// context.Fail();
// return;
// }
// else if (registeredModule == null)
// {
// context.Fail();
// return;
// }
// else
// {
// checkModuleActiveOrNot();
// }
// }
// void checkModuleActiveOrNot()
// {
// if (registeredModule.ModuleStatus == 0)
// {
// context.Fail();
// return;
// }
// }
// void checkModuleHaveRoleOrNot()
// {
// var allowedUserTypes = registeredModule?.AllowedUserType ?? "";
// if (allowedUserTypes == "Public" || userRole.Any(role => allowedUserTypes.Contains(role)))
// {
// context.Succeed(requirement);
// return;
// }
// else if (currentUser != null && allowedUserTypes == "Registered User" )
// {
// checkMethodAndRole();
// }
// else
// {
// context.Fail();
// return;
// }
// }
// void checkMethodAndRole()
// {
// // Load all ModuleSettings and process them in memory
// var moduleSettings = _authDBContext.ModuleSettings.AsEnumerable();
// // Check if the method exists in the module settings
// var isMethodExist = moduleSettings.FirstOrDefault(m => m.MethodAllowedUserType?.Any(mt => mt.MethodName == pageName) == true);
// if (isMethodExist != null) // Check if the method exists which means method is registered
// {
// var registeredMethod = moduleSettings.Where(m => m.MethodAllowedUserType != null && m.MethodAllowedUserType.Any(mt => mt.MethodName == pageName)).FirstOrDefault();
// var allowedUserTypes = registeredMethod?.MethodAllowedUserType?.Where(mt => mt.MethodName == pageName).Select(mt => mt.AllowedUserTypesArray).FirstOrDefault() ?? Array.Empty<string>();
// if (userRole.Any(role => allowedUserTypes.Contains(role)) || allowedUserTypes.Contains("All")) // Check if the user role is allowed, allowing only registered user to access.
// {
// context.Succeed(requirement);
// return;
// }
// else
// {
// context.Fail();
// return;
// }
// }
// else // No method is registered to allow all method to be accessed
// {
// context.Succeed(requirement);
// return;
// }
// }
// }
// }
//}

View File

@ -110,5 +110,6 @@ namespace PSTW_CentralSystem.DBContext
public DbSet<ApprovalFlowModel> Approvalflow { get; set; }
public DbSet<StaffSignModel> Staffsign { get; set; }
//testingvhjbnadgfsbgdngffdfdsdfdgfdfdg
}
}

View File

@ -0,0 +1,118 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using PSTW_CentralSystem.Models; // Add this to use the MarineTarball class
namespace PSTW_CentralSystem.DBContext
{
public class MMSSystemContext : DbContext
{
public MMSSystemContext(DbContextOptions<MMSSystemContext> options) : base(options)
{
}
// DbSet for tbl_marine_tarball
public DbSet<MarineTarball> MarineTarballs { get; set; }
public DbSet<MarineStation> MarineStations { get; set; }
public DbSet<State> States { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Level> Levels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure properties if needed
modelBuilder.Entity<MarineTarball>(entity =>
{
entity.ToTable("tbl_marine_tarball");
entity.HasKey(e => e.Id); // Primary key
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.ReportID).HasColumnName("reportID").HasMaxLength(50);
entity.Property(e => e.FirstSampler).HasColumnName("firstSampler").HasMaxLength(50);
entity.Property(e => e.SecondSampler).HasColumnName("secondSampler").HasMaxLength(50);
entity.Property(e => e.DateSample).HasColumnName("dateSample");
entity.Property(e => e.TimeSample).HasColumnName("timeSample");
entity.Property(e => e.StationID).HasColumnName("stationID").HasMaxLength(20);
entity.Property(e => e.ClassifyID).HasColumnName("classifyID").HasMaxLength(20);
entity.Property(e => e.Latitude).HasColumnName("latitude");
entity.Property(e => e.Longitude).HasColumnName("longitude");
entity.Property(e => e.GetLatitude).HasColumnName("getLatitude");
entity.Property(e => e.GetLongitude).HasColumnName("getLongitude");
entity.Property(e => e.OptionalName1).HasColumnName("optionalName1");
entity.Property(e => e.OptionalName2).HasColumnName("optionalName2");
entity.Property(e => e.OptionalName3).HasColumnName("optionalName3");
entity.Property(e => e.OptionalName4).HasColumnName("optionalName4");
entity.Property(e => e.Timestamp).HasColumnName("timestamp");
entity.HasOne(t => t.User)
.WithMany()
.HasForeignKey(t => t.FirstSampler)
.HasPrincipalKey(u => u.FullName); // Assuming User has FullName
//Configure relationship with TarballStation
entity.HasOne(m => m.MarineStation)
.WithMany()
.HasForeignKey(m => m.StationID)
.HasPrincipalKey(t => t.StationID);
});
modelBuilder.Entity<MarineStation>(entity =>
{
entity.ToTable("tbl_marine_station");
entity.HasKey(e => e.Id); // Primary key
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.StationID).HasColumnName("stationID").HasMaxLength(20);
entity.Property(e => e.StateID).HasColumnName("stateID").HasMaxLength(10);
entity.Property(e => e.CategoryID).HasColumnName("categoryID").HasMaxLength(10);
entity.Property(e => e.LocationName).HasColumnName("locationName").HasMaxLength(50);
entity.Property(e => e.Longitude).HasColumnName("longitude").HasColumnType("decimal(10,5)");
entity.Property(e => e.Latitude).HasColumnName("latitude").HasColumnType("decimal(10,5)");
// Configure relationship with State
entity.HasOne(t => t.State)
.WithMany()
.HasForeignKey(t => t.StateID)
.HasPrincipalKey(s => s.StateID);
});
modelBuilder.Entity<State>(entity =>
{
entity.ToTable("tbl_state");
entity.HasKey(e => e.Id); // Primary key
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.StateID).HasColumnName("stateID").HasMaxLength(20);
entity.Property(e => e.StateName).HasColumnName("stateName").HasMaxLength(50);
});
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("tbl_user");
entity.HasKey(e => e.Id); // Primary key
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.UserID).HasColumnName("userID").HasMaxLength(20);
entity.Property(e => e.FullName).HasColumnName("fullname").HasMaxLength(50);
entity.Property(e => e.Username).HasColumnName("username").HasMaxLength(40);
entity.Property(e => e.Password).HasColumnName("pwd").HasMaxLength(100);
entity.Property(e => e.LevelID).HasColumnName("levelID").HasMaxLength(10);
entity.Property(e => e.DeptID).HasColumnName("departID").HasMaxLength(10);
entity.HasOne(u => u.Level)
.WithMany()
.HasForeignKey(u => u.LevelID)
.HasPrincipalKey(l => l.LevelID);
});
modelBuilder.Entity<Level>().ToTable("tbl_level");
modelBuilder.Entity<Level>(entity =>
{
entity.HasKey(e => e.Id); // Primary key
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.LevelID).HasColumnName("levelID").HasMaxLength(10);
entity.Property(e => e.LevelName).HasColumnName("levelName").HasMaxLength(10);
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PSTW_CentralSystem.Migrations
{
/// <inheritdoc />
public partial class UpdateTableRequest : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "assignStoreItem",
table: "request",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "fromStoreItem",
table: "request",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "407727d8-2266-45f2-9b48-ef3a450f09c6", "AQAAAAIAAYagAAAAEDc91vi8/AJwNGigDpnzFh7Iplvlph0VGj9GfG1zI6tY/jM/4f3P0CWVQZ/0oetzVg==", "2faceaca-f491-455a-9f10-3f641a5a7e0d" });
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "8065f043-f8ed-4733-aa42-6ee6a1ebb636", "AQAAAAIAAYagAAAAEOmfi3vsFMnCUitXZqLgUaq5+Jqmigy8HrXwNqd8IELW2yvFQAMrfHLvJM5h0c+lfQ==", "46a8accc-305f-42e6-a4a2-376bfec07e84" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "assignStoreItem",
table: "request");
migrationBuilder.DropColumn(
name: "fromStoreItem",
table: "request");
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "d801514b-2c36-4df7-9bb5-1c7e351ed27e", "AQAAAAIAAYagAAAAEBSoDiGEYlobLgzVcffYwvTtm1WnXpqrBBT1yYP+kruV4OTtizW7Sel94qAfqUjGcw==", "6132b0af-6a7f-4f38-8959-d049ed486e8f" });
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "14f11e89-bb92-49dd-a8df-ec5b0d49df2d", "AQAAAAIAAYagAAAAEEvcS1SY+9pxZKH/P1l4TaodgW3SFSRfcZ+PnjB3MiMmEUSyYoo64AQtX0bOxFSX2g==", "6dca2498-5150-4369-9923-6f19a48258d4" });
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,126 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PSTW_CentralSystem.Migrations
{
/// <inheritdoc />
public partial class UpdateRequestTable2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "fromStoreItem",
table: "request",
type: "int",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<int>(
name: "assignStoreItem",
table: "request",
type: "int",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "853a1cf1-3482-47c4-b4b0-7b6a3af6696c", "AQAAAAIAAYagAAAAEBJPP1cHHZZyaGLaskNvSj8sOEizvDa1W2JgxMlYtK18+uhZWvW2RPlqBOhaKc0loQ==", "c911b03d-918a-482f-9c9e-773dc64cdd5d" });
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "9ccd914d-6310-4d4e-88c0-e842892e1831", "AQAAAAIAAYagAAAAELxkKuB4hcfQ7Pqe/XCRgygejUsY7X9ByQuS/3FMl50OSzmz9s0byWxGYWQXbyBpGA==", "5b9a67a3-8186-474b-9499-c9c40457fb54" });
migrationBuilder.CreateIndex(
name: "IX_request_assignStoreItem",
table: "request",
column: "assignStoreItem");
migrationBuilder.CreateIndex(
name: "IX_request_fromStoreItem",
table: "request",
column: "fromStoreItem");
migrationBuilder.AddForeignKey(
name: "FK_request_Stores_assignStoreItem",
table: "request",
column: "assignStoreItem",
principalTable: "Stores",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_request_Stores_fromStoreItem",
table: "request",
column: "fromStoreItem",
principalTable: "Stores",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_request_Stores_assignStoreItem",
table: "request");
migrationBuilder.DropForeignKey(
name: "FK_request_Stores_fromStoreItem",
table: "request");
migrationBuilder.DropIndex(
name: "IX_request_assignStoreItem",
table: "request");
migrationBuilder.DropIndex(
name: "IX_request_fromStoreItem",
table: "request");
migrationBuilder.AlterColumn<string>(
name: "fromStoreItem",
table: "request",
type: "longtext",
nullable: true,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "assignStoreItem",
table: "request",
type: "longtext",
nullable: true,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "407727d8-2266-45f2-9b48-ef3a450f09c6", "AQAAAAIAAYagAAAAEDc91vi8/AJwNGigDpnzFh7Iplvlph0VGj9GfG1zI6tY/jM/4f3P0CWVQZ/0oetzVg==", "2faceaca-f491-455a-9f10-3f641a5a7e0d" });
migrationBuilder.UpdateData(
table: "AspNetUsers",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" },
values: new object[] { "8065f043-f8ed-4733-aa42-6ee6a1ebb636", "AQAAAAIAAYagAAAAEOmfi3vsFMnCUitXZqLgUaq5+Jqmigy8HrXwNqd8IELW2yvFQAMrfHLvJM5h0c+lfQ==", "46a8accc-305f-42e6-a4a2-376bfec07e84" });
}
}
}

View File

@ -422,6 +422,12 @@ namespace PSTW_CentralSystem.Migrations
b.Property<DateTime?>("approvalDate")
.HasColumnType("datetime(6)");
b.Property<int?>("assignStoreItem")
.HasColumnType("int");
b.Property<int?>("fromStoreItem")
.HasColumnType("int");
b.Property<string>("remarkMasterInv")
.HasColumnType("longtext");
@ -442,6 +448,10 @@ namespace PSTW_CentralSystem.Migrations
b.HasIndex("UserId");
b.HasIndex("assignStoreItem");
b.HasIndex("fromStoreItem");
b.ToTable("request");
});
@ -754,16 +764,16 @@ namespace PSTW_CentralSystem.Migrations
{
Id = 1,
AccessFailedCount = 0,
ConcurrencyStamp = "d801514b-2c36-4df7-9bb5-1c7e351ed27e",
ConcurrencyStamp = "853a1cf1-3482-47c4-b4b0-7b6a3af6696c",
Email = "admin@pstw.com.my",
EmailConfirmed = true,
FullName = "MAAdmin",
LockoutEnabled = false,
NormalizedEmail = "ADMIN@PSTW.COM.MY",
NormalizedUserName = "ADMIN@PSTW.COM.MY",
PasswordHash = "AQAAAAIAAYagAAAAEBSoDiGEYlobLgzVcffYwvTtm1WnXpqrBBT1yYP+kruV4OTtizW7Sel94qAfqUjGcw==",
PasswordHash = "AQAAAAIAAYagAAAAEBJPP1cHHZZyaGLaskNvSj8sOEizvDa1W2JgxMlYtK18+uhZWvW2RPlqBOhaKc0loQ==",
PhoneNumberConfirmed = false,
SecurityStamp = "6132b0af-6a7f-4f38-8959-d049ed486e8f",
SecurityStamp = "c911b03d-918a-482f-9c9e-773dc64cdd5d",
TwoFactorEnabled = false,
UserInfoStatus = 1,
UserName = "admin@pstw.com.my"
@ -772,16 +782,16 @@ namespace PSTW_CentralSystem.Migrations
{
Id = 2,
AccessFailedCount = 0,
ConcurrencyStamp = "14f11e89-bb92-49dd-a8df-ec5b0d49df2d",
ConcurrencyStamp = "9ccd914d-6310-4d4e-88c0-e842892e1831",
Email = "sysadmin@pstw.com.my",
EmailConfirmed = true,
FullName = "SysAdmin",
LockoutEnabled = false,
NormalizedEmail = "SYSADMIN@PSTW.COM.MY",
NormalizedUserName = "SYSADMIN@PSTW.COM.MY",
PasswordHash = "AQAAAAIAAYagAAAAEEvcS1SY+9pxZKH/P1l4TaodgW3SFSRfcZ+PnjB3MiMmEUSyYoo64AQtX0bOxFSX2g==",
PasswordHash = "AQAAAAIAAYagAAAAELxkKuB4hcfQ7Pqe/XCRgygejUsY7X9ByQuS/3FMl50OSzmz9s0byWxGYWQXbyBpGA==",
PhoneNumberConfirmed = false,
SecurityStamp = "6dca2498-5150-4369-9923-6f19a48258d4",
SecurityStamp = "5b9a67a3-8186-474b-9499-c9c40457fb54",
TwoFactorEnabled = false,
UserInfoStatus = 1,
UserName = "sysadmin@pstw.com.my"
@ -973,10 +983,22 @@ namespace PSTW_CentralSystem.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.StoreModel", "Stores")
.WithMany()
.HasForeignKey("assignStoreItem");
b.HasOne("PSTW_CentralSystem.Areas.Inventory.Models.StoreModel", "Store")
.WithMany()
.HasForeignKey("fromStoreItem");
b.Navigation("Product");
b.Navigation("Station");
b.Navigation("Store");
b.Navigation("Stores");
b.Navigation("User");
});

83
Models/MarineTarball.cs Normal file
View File

@ -0,0 +1,83 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace PSTW_CentralSystem.Models
{
public class MarineTarball
{
public int Id { get; set; } // Maps to 'id'
public required string ReportID { get; set; } // Maps to 'reportID'
public required string FirstSampler { get; set; } // Maps to 'firstSampler'
public required string SecondSampler { get; set; } // Maps to 'secondSampler'
public DateTime DateSample { get; set; } // Maps to 'dateSample'
public TimeSpan TimeSample { get; set; } // Maps to 'timeSample'
public required string StationID { get; set; } // Maps to 'stationID'
public required string ClassifyID { get; set; } // Maps to 'classifyID'
public required string Latitude { get; set; } // Maps to 'latitude'
public required string Longitude { get; set; } // Maps to 'longitude'
public double GetLatitude { get; set; } // Maps to 'getLatitude'
public double GetLongitude { get; set; } // Maps to 'getLongitude'
public DateTime Timestamp { get; set; } // Maps to 'timestamp'
public string? OptionalName1 { get; set; }
public string? OptionalName2 { get; set; }
public string? OptionalName3 { get; set; }
public string? OptionalName4 { get; set; }
public required string PhotoPath1 { get; set; } // Left Side Coastal View
public required string PhotoPath2 { get; set; } // Right Side Coastal View
public required string PhotoPath3 { get; set; } // Vertical Lines
public required string PhotoPath4 { get; set; } // Horizontal Lines
public string? PhotoPath5 { get; set; } // optional
public string? PhotoPath6 { get; set; } // optional
public string? PhotoPath7 { get; set; } // optional
public string? PhotoPath8 { get; set; } // optional
[ForeignKey("StationID")]
public required MarineStation MarineStation { get; set; }
[ForeignKey("FirstSampler")]
public required User User { get; set; }
}
public class MarineStation
{
public int Id { get; set; } // Maps to 'id'
public required string StationID { get; set; } // Maps to 'stationID'
public required string StateID { get; set; } // Maps to 'stateID'
public required string CategoryID { get; set; } // Maps to 'categoryID'
public required string LocationName { get; set; } // Maps to 'locationName'
public decimal Longitude { get; set; } // Maps to 'longitude'
public decimal Latitude { get; set; } // Maps to 'latitude'
[ForeignKey("StateID")]
public required State State { get; set; }
}
public class State
{
public int Id { get; set; } // Maps to 'id'
public required string StateID { get; set; } // Maps to 'stateID'
public required string StateName { get; set; } // Maps to 'stateName'
}
public class User
{
public int Id { get; set; } // Maps to 'id'
public required string UserID { get; set; } // Maps to 'userID'
public required string FullName { get; set; } // Maps to 'fullname'
public required string Username { get; set; } // Maps to 'username'
public required string Password { get; set; } // Maps to 'pwd'
public required string LevelID { get; set; } // Maps to 'levelID'
public required string DeptID { get; set; } // Maps to 'deptID'
[ForeignKey("LevelID")]
public required Level Level { get; set; } // Maps to 'levelID'
}
public class Level
{
public int Id { get; set; } // Maps to 'id'
public required string LevelID { get; set; } // Maps to 'levelID'
public required string LevelName { get; set; } // Maps to 'levelName'
}
}

View File

@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.104.2" />
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
<PackageReference Include="Lemonade" Version="1.0.276" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
@ -28,11 +29,19 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="PDFsharp" Version="6.1.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="QuestPDF" Version="2025.4.0" />
<PackageReference Include="QuestPDF" Version="2025.1.7" />
<PackageReference Include="QuestPDF.HTML" Version="1.4.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="System.Drawing.Common" Version="9.0.5" />
<PackageReference Include="SkiaSharp" Version="3.116.1" />
<PackageReference Include="System.Drawing.Common" Version="9.0.3" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.3" />
<PackageReference Include="Verify.QuestPDF" Version="2.3.0" />
</ItemGroup>
<ItemGroup>

View File

@ -10,6 +10,8 @@ using QuestPDF.Infrastructure;
using PSTW_CentralSystem.Areas.OTcalculate.Services;
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
using System.Net;
internal class Program
{
private static void Main(string[] args)
@ -17,6 +19,7 @@ internal class Program
var builder = WebApplication.CreateBuilder(args);
var centralConnectionString = builder.Configuration.GetConnectionString("CentralConnnection");
Settings.License = LicenseType.Community;
//var inventoryConnectionString = builder.Configuration.GetConnectionString("InventoryConnection");
// Add services to the container.
@ -25,6 +28,9 @@ internal class Program
builder.Services.AddScoped<OvertimeExcel>();
builder.Services.AddScoped<NetworkShareAccess>(provider =>
new NetworkShareAccess(@"\\192.168.12.42\images\marine\manual_tarball", "installer", "mms@pstw"));
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
@ -56,6 +62,12 @@ internal class Program
);
});
builder.Services.AddDbContext<MMSSystemContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("MMSDatabase"),
new MySqlServerVersion(new Version(8, 0, 0))));
//builder.Services.AddDbContext<InventoryDBContext>(options =>
//{
// options.UseMySql(inventoryConnectionString, new MySqlServerVersion(new Version(8, 0, 39)),
@ -78,6 +90,7 @@ internal class Program
// Add scope
builder.Services.AddScoped<IAuthorizationHandler, RoleModuleHandler>();
var app = builder.Build();
// Configure the HTTP request pipeline.
@ -101,6 +114,12 @@ internal class Program
app.MapControllerRoute(
name: "root",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}

View File

@ -430,6 +430,7 @@
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Module Administration</span>
</a>
</li>
</ul>
</li>
<li class="sidebar-item">
@ -499,6 +500,31 @@
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Inventory Report</span>
</a>
</li>
<!--MMS-->
<li class="sidebar-item">
<a class="sidebar-link has-arrow waves-effect waves-dark"
href="javascript:void(0)" aria-expanded="false">
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">MMS</span>
</a>
<ul aria-expanded="false" class="collapse first-level">
<!-- Marine subsection -->
<li class="sidebar-item">
<a class="sidebar-link has-arrow waves-effect waves-dark"
href="javascript:void(0)" aria-expanded="false">
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Marine</span>
</a>
<ul aria-expanded="false" class="collapse second-level">
<!-- Tar Ball Sampling Form link -->
<li class="sidebar-item">
<a class="sidebar-link waves-effect waves-dark"
asp-area="MMS" asp-controller="Marine" asp-action="TarBallForm" aria-expanded="false">
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Tarball Report</span>
</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>

View File

@ -3,10 +3,19 @@
//"DefaultConnection": "Server=localhost;uid=root;Password='';Database=web_interface;"
//"DefaultConnection": "server=175.136.244.102;user id=root;password=tw_mysql_root;port=3306;database=web_interface"
//"CentralConnnection": "Server=192.168.12.12;Port=3306;uid=installer;password='pstw_mysql_installer';database=pstw_cs;", //DB_dev Local connection
"CentralConnnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs;" //DB_dev Public connection
"CentralConnnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs_prod;" //DB_dev Public connection
//"InventoryConnection": "Server=219.92.7.60;Port=3307;uid=installer;password='pstw_mysql_installer';database=pstw_cs_inventory;" //DB_dev connection
//"DefaultConnection": "Server=219.92.7.60;Port=3307;uid=intern;password='intern_mysql_acct';database=web_interface;"//DB_dev connection
"MMSDatabase": "Server=192.168.12.42;Port=3306;Uid=mmsuser;password=mms@pstw_mysql_root;database=db_mms;ConvertZeroDateTime=True;"
},
"NetworkCredentials": {
"ImageServer": {
"Username": "installer",
"Password": "mms@pstw",
"Domain": "."
}
},
"PhotoBasePath": "\\192.168.12.42\\mms\\marine\\manual_tarball",
"Logging": {
"LogLevel": {
"Default": "Information",

BIN
document.pdf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB