Compare commits
2 Commits
8a39714a25
...
c0c2c59ea3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c2c59ea3 | ||
|
|
90e56547ba |
@ -20,5 +20,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult OtSTatus()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
Areas/OTcalculate/Models/OtStatusModel.cs
Normal file
36
Areas/OTcalculate/Models/OtStatusModel.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PSTW_CentralSystem.Areas.OTcalculate.Models
|
||||
{
|
||||
[Table("otstatus")]
|
||||
public class OtStatusModel
|
||||
{
|
||||
[Key]
|
||||
public int StatusId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Month { get; set; }
|
||||
|
||||
[Required]
|
||||
public int Year { get; set; }
|
||||
|
||||
public DateTime SubmitDate { get; set; }
|
||||
|
||||
public string HodStatus { get; set; } = "Pending";
|
||||
|
||||
// JSON array of ApprovalUpdateLog
|
||||
public string? HodUpdate { get; set; }
|
||||
|
||||
public string HrStatus { get; set; } = "Pending";
|
||||
|
||||
// JSON array of ApprovalUpdateLog
|
||||
public string? HrUpdate { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@ -78,30 +78,35 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
columns.RelativeColumn(2.7f); // Description
|
||||
});
|
||||
|
||||
// Header Row
|
||||
table.Header(header =>
|
||||
{
|
||||
void AddHeaderCell(string text, string bgColor)
|
||||
{
|
||||
header.Cell().Background(bgColor).Border(0.25f).Padding(5).Text(text).FontSize(9).Bold().AlignCenter();
|
||||
}
|
||||
// Row 1 — grouped headers
|
||||
header.Cell().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(5).Text("Date").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().ColumnSpan(3).Background("#dceefb").Border(0.25f).Padding(5).Text("Office Hours\n(8:30 - 17:30)").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().ColumnSpan(3).Background("#edf2f7").Border(0.25f).Padding(5).Text("After Office Hours\n(17:30 - 8:30)").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Net OT Hours").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
AddHeaderCell("Date", "#d0ead2");
|
||||
AddHeaderCell("From\n(Office)", "#dceefb");
|
||||
AddHeaderCell("To\n(Office)", "#dceefb");
|
||||
AddHeaderCell("Break\n(Office)", "#dceefb");
|
||||
AddHeaderCell("From\n(After)", "#edf2f7");
|
||||
AddHeaderCell("To\n(After)", "#edf2f7");
|
||||
AddHeaderCell("Break\n(After)", "#edf2f7");
|
||||
AddHeaderCell("Total OT\nHours", "#fdebd0");
|
||||
AddHeaderCell("Break Hours\n(min)", "#fdebd0");
|
||||
AddHeaderCell("Net OT Hours", "#fdebd0");
|
||||
if (departmentId == 2)
|
||||
AddHeaderCell("Station", "#d0f0ef");
|
||||
AddHeaderCell("Days", "#e0f7da");
|
||||
AddHeaderCell("Description", "#e3f2fd");
|
||||
header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(5).Text("Station").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(5).Text("Days").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(5).Text("Description").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
// Row 2 — subheaders only for grouped columns
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("Break").FontSize(9).Bold().AlignCenter();
|
||||
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter();
|
||||
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("Break").FontSize(9).Bold().AlignCenter();
|
||||
});
|
||||
|
||||
|
||||
// Data Rows
|
||||
double totalOTSum = 0;
|
||||
int totalBreakSum = 0;
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
@{
|
||||
@{
|
||||
ViewData["Title"] = "Rate Update";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
<th class="header-orange" rowspan="2" v-if="isPSTWAIR">Station</th>
|
||||
<th class="header-green" rowspan="2">Days</th>
|
||||
<th class="header-blue" rowspan="2">Description</th>
|
||||
<th class="header-green" rowspan="2">Action</th>
|
||||
<th class="header-green" rowspan="2" v-if="!isAlreadySubmitted">Action</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="header-blue">From</th>
|
||||
@ -134,7 +134,7 @@
|
||||
{{ record.otDescription }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<td v-if="!isAlreadySubmitted">
|
||||
<button class="btn btn-light border rounded-circle me-1" title="Edit Record" v-on:click="editRecord(index)">
|
||||
<i class="bi bi-pencil-fill text-warning fs-5"></i>
|
||||
</button>
|
||||
@ -167,10 +167,33 @@
|
||||
<button class="btn btn-dark btn-sm" v-on:click="downloadPdf">
|
||||
<i class="bi bi-download"></i> Save
|
||||
</button>
|
||||
<button class="btn btn-success btn-sm" v-on:click="submitRecords">
|
||||
<i class="bi bi-send"></i> Submit
|
||||
<button class="btn btn-success btn-sm"
|
||||
v-on:click="openSubmitModal"
|
||||
:disabled="isSubmitting || isAlreadySubmitted">
|
||||
<i class="bi bi-send"></i> {{ isAlreadySubmitted ? 'Submitted' : 'Submit' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Submit Modal -->
|
||||
<div v-if="showSubmitModal" class="modal show d-block" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content p-3">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Submit OT Records</h5>
|
||||
<button type="button" class="btn-close" v-on:click="showSubmitModal = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="file" class="form-control" v-on:change="handleFileChange" accept=".pdf" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success" v-on:click="submitToHod">Submit</button>
|
||||
<button class="btn btn-secondary" v-on:click="showSubmitModal = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -189,7 +212,10 @@
|
||||
selectedYear: currentYear,
|
||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i),
|
||||
expandedDescriptions: {}
|
||||
expandedDescriptions: {},
|
||||
showSubmitModal: false,
|
||||
submitFile: null,
|
||||
submittedStatus: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -217,6 +243,10 @@
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
};
|
||||
},
|
||||
isAlreadySubmitted() {
|
||||
const key = `${this.selectedYear}-${String(this.selectedMonth).padStart(2, '0')}`;
|
||||
return !!this.submittedStatus[key];
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@ -225,8 +255,21 @@
|
||||
methods: {
|
||||
async initUserAndRecords() {
|
||||
await this.fetchUser();
|
||||
if (this.userId) await this.fetchOtRecords();
|
||||
if (this.userId) {
|
||||
await this.fetchOtRecords();
|
||||
await this.fetchSubmissionStatus();
|
||||
}
|
||||
},
|
||||
async fetchSubmissionStatus() {
|
||||
try {
|
||||
const res = await fetch(`/OvertimeAPI/GetSubmissionStatus/${this.userId}`);
|
||||
const data = await res.json();
|
||||
this.submittedStatus = data; // expect format like { '2025-04': true }
|
||||
} catch (err) {
|
||||
console.error("Submission status fetch error:", err);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchUser() {
|
||||
try {
|
||||
const res = await fetch('/IdentityAPI/GetUserInformation', { method: 'POST' });
|
||||
@ -342,40 +385,54 @@
|
||||
alert("An error occurred while generating the PDF.");
|
||||
}
|
||||
},
|
||||
async submitRecords() {
|
||||
try {
|
||||
const recordsToSubmit = this.filteredRecords.map(record => ({
|
||||
overtimeId: record.overtimeId, // Make sure to include the ID for updates
|
||||
otDate: record.otDate,
|
||||
officeFrom: record.officeFrom,
|
||||
officeTo: record.officeTo,
|
||||
officeBreak: record.officeBreak,
|
||||
afterFrom: record.afterFrom,
|
||||
afterTo: record.afterTo,
|
||||
afterBreak: record.afterBreak,
|
||||
stationId: record.stationId,
|
||||
otDescription: record.otDescription,
|
||||
otDays: record.otDays,
|
||||
filePath: record.filePath, // Include existing file path
|
||||
userId: this.userId
|
||||
// Add other relevant fields if necessary
|
||||
}));
|
||||
|
||||
const res = await fetch('/OvertimeAPI/SubmitOvertimeRecords', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(recordsToSubmit)
|
||||
});
|
||||
if (res.ok) {
|
||||
alert("Overtime records submitted for review.");
|
||||
} else {
|
||||
alert("Submission failed: " + await res.text());
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Submission error:", err);
|
||||
alert("An error occurred during submission.");
|
||||
}
|
||||
openSubmitModal() {
|
||||
this.showSubmitModal = true;
|
||||
},
|
||||
handleFileChange(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file && file.type !== 'application/pdf') {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
this.submitFile = file;
|
||||
},
|
||||
|
||||
async submitToHod() {
|
||||
this.isSubmitting = true;
|
||||
try {
|
||||
if (!this.submitFile) {
|
||||
alert("Please upload a PDF file.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.submitFile);
|
||||
|
||||
// Add month & year selection logic if needed
|
||||
formData.append("month", new Date().getMonth() + 1);
|
||||
formData.append("year", new Date().getFullYear());
|
||||
|
||||
try {
|
||||
const response = await fetch("/OvertimeAPI/SubmitOvertimeRecords", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Submission failed");
|
||||
alert("Submission successful!");
|
||||
this.showSubmitModal = false;
|
||||
|
||||
const key = `${this.selectedYear}-${String(this.selectedMonth).padStart(2, '0')}`;
|
||||
this.submittedStatus[key] = true;
|
||||
this.showSubmitModal = false;
|
||||
|
||||
} catch (err) {
|
||||
alert("Error: " + err.message);
|
||||
}
|
||||
} finally {
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
123
Areas/OTcalculate/Views/Overtime/OtStatus.cshtml
Normal file
123
Areas/OTcalculate/Views/Overtime/OtStatus.cshtml
Normal file
@ -0,0 +1,123 @@
|
||||
@{
|
||||
ViewData["Title"] = "Overtime Status";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<style>
|
||||
.hodstatus, .hrstatus {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 500px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="otStatusApp" class="container mt-4">
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month</th>
|
||||
<th>Year</th>
|
||||
<th>Submitted On</th>
|
||||
<th>HOD Status</th>
|
||||
<th>HR Status</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="status in statusList" :key="status.statusId">
|
||||
<td>{{ getMonthName(status.month) }}</td>
|
||||
<td>{{ status.year }}</td>
|
||||
<td>{{ formatDate(status.submitDate) }}</td>
|
||||
<td :class="status.hodStatus.toLowerCase()">{{ status.hodStatus }}</td>
|
||||
<td :class="status.hrStatus.toLowerCase()">{{ status.hrStatus }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" v-on:click ="viewUpdates(status)">View Updates</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Modal -->
|
||||
<div v-if="selectedStatus" class="modal-mask">
|
||||
<div class="modal-container">
|
||||
<h5>Status History</h5>
|
||||
<p><strong>HOD Updates:</strong></p>
|
||||
<ul>
|
||||
<li v-for="update in parseJson(selectedStatus.hodUpdate)">
|
||||
{{ formatUpdate(update) }}
|
||||
</li>
|
||||
</ul>
|
||||
<p><strong>HR Updates:</strong></p>
|
||||
<ul>
|
||||
<li v-for="update in parseJson(selectedStatus.hrUpdate)">
|
||||
{{ formatUpdate(update) }}
|
||||
</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary" v-on:click ="selectedStatus = null">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
statusList: [],
|
||||
selectedStatus: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
fetch('/OvertimeAPI/GetUserOtStatus')
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
.then(data => this.statusList = data)
|
||||
.catch(err => console.error("Fetch error:", err));
|
||||
},
|
||||
methods: {
|
||||
formatDate(dateStr) {
|
||||
return new Date(dateStr).toLocaleDateString();
|
||||
},
|
||||
getMonthName(month) {
|
||||
return new Date(2000, month - 1, 1).toLocaleString('default', { month: 'long' });
|
||||
},
|
||||
parseJson(jsonStr) {
|
||||
try {
|
||||
return jsonStr ? JSON.parse(jsonStr) : [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
formatUpdate(update) {
|
||||
return `${update.timestamp} - ${update.updatedBy} changed ${update.field} from '${update.oldValue}' to '${update.newValue}'`;
|
||||
},
|
||||
viewUpdates(status) {
|
||||
this.selectedStatus = status;
|
||||
}
|
||||
}
|
||||
}).mount('#otStatusApp');
|
||||
</script>
|
||||
}
|
||||
@ -554,6 +554,87 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
|
||||
}
|
||||
|
||||
[HttpPost("SubmitOvertimeRecords")]
|
||||
public async Task<IActionResult> SubmitOvertimeRecords([FromForm] IFormFile file, [FromForm] int month, [FromForm] int year)
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
if (file == null || file.Length == 0)
|
||||
return BadRequest("No file uploaded.");
|
||||
|
||||
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Media", "Overtime");
|
||||
|
||||
if (!Directory.Exists(uploadsFolder))
|
||||
Directory.CreateDirectory(uploadsFolder);
|
||||
|
||||
var fileName = $"OT_{userId}_{year}_{month}_{DateTime.Now.Ticks}.pdf";
|
||||
var filePath = Path.Combine(uploadsFolder, fileName);
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var statusRecord = new OtStatusModel
|
||||
{
|
||||
UserId = userId,
|
||||
Month = month,
|
||||
Year = year,
|
||||
SubmitDate = DateTime.Now,
|
||||
HodStatus = "Pending",
|
||||
HrStatus = "Pending",
|
||||
FilePath = $"/Media/Overtime/{fileName}"
|
||||
};
|
||||
|
||||
_centralDbContext.OtStatus.Add(statusRecord);
|
||||
await _centralDbContext.SaveChangesAsync();
|
||||
|
||||
return Ok(new { message = "Overtime records submitted successfully." });
|
||||
}
|
||||
|
||||
[HttpGet("CheckSubmissionStatus")]
|
||||
public IActionResult CheckSubmissionStatus(int month, int year)
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
var isSubmitted = _centralDbContext.OtStatus
|
||||
.Any(s => s.UserId == userId && s.Month == month && s.Year == year);
|
||||
|
||||
return Ok(new { isSubmitted });
|
||||
}
|
||||
|
||||
[HttpGet("GetSubmissionStatus/{userId}")]
|
||||
public IActionResult GetSubmissionStatus(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statuses = _centralDbContext.OtStatus
|
||||
.Where(s => s.UserId == userId)
|
||||
.OrderByDescending(s => s.SubmitDate)
|
||||
.Select(s => new
|
||||
{
|
||||
s.UserId,
|
||||
s.Month,
|
||||
s.Year,
|
||||
s.HodStatus,
|
||||
s.HrStatus,
|
||||
s.SubmitDate
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Ok(statuses);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch submission statuses.");
|
||||
return StatusCode(500, "Error retrieving submission statuses.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ot Edit
|
||||
@ -615,6 +696,23 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OtStatus
|
||||
[HttpGet("GetUserOtStatus")]
|
||||
public IActionResult GetUserOtStatus()
|
||||
{
|
||||
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||
return Unauthorized();
|
||||
|
||||
var records = _centralDbContext.OtStatus
|
||||
.Where(s => s.UserId == userId)
|
||||
.OrderByDescending(s => s.Year).ThenByDescending(s => s.Month)
|
||||
.ToList();
|
||||
|
||||
return Ok(records);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -104,5 +104,7 @@ namespace PSTW_CentralSystem.DBContext
|
||||
public DbSet<StateModel> States { get; set; }
|
||||
public DbSet<WeekendModel> Weekends { get; set; }
|
||||
public DbSet<OtRegisterModel> Otregisters { get; set; }
|
||||
public DbSet<OtStatusModel> OtStatus { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,6 +539,11 @@
|
||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Records</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidebar-item">
|
||||
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="Overtime" asp-action="OtStatus" aria-expanded="false">
|
||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Status</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809276784755591.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809276784755591.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809289066407421.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809289066407421.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809289682209687.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809289682209687.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809323916424471.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809323916424471.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809389078270198.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809389078270198.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809392040377761.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_15_2025_4_638809392040377761.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_1_2025_4_638809164154988965.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_1_2025_4_638809164154988965.pdf
Normal file
Binary file not shown.
BIN
wwwroot/Media/Overtime/OT_1_2025_4_638809168067665769.pdf
Normal file
BIN
wwwroot/Media/Overtime/OT_1_2025_4_638809168067665769.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user