This commit is contained in:
Naz 2025-04-11 17:21:55 +08:00
parent 2c9d8bc4da
commit ffdc93a4b7
8 changed files with 393 additions and 73 deletions

View File

@ -0,0 +1,163 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using System.IO;
using System.Collections.Generic;
using PSTW_CentralSystem.Areas.OTcalculate.Models;
using System;
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
{
public class OvertimePdfService
{
public MemoryStream GenerateOvertimeTablePdf(
List<OtRegisterModel> records,
int departmentId,
string userFullName,
string departmentName,
byte[]? logoImage = null // Optional logo image
)
{
var stream = new MemoryStream();
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4.Landscape());
page.Margin(30);
// Header section with logo and user info
page.Content().Column(column =>
{
column.Item().Row(row =>
{
row.RelativeItem(2).Column(col =>
{
if (logoImage != null)
{
col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea);
col.Spacing(10);
}
col.Item().Text($"Name: {userFullName}").FontSize(9).SemiBold();
col.Item().Text($"Department: {departmentName}").FontSize(9).Italic();
});
row.RelativeItem(1).AlignRight().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}")
.FontSize(9).FontColor(Colors.Grey.Medium);
});
column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2);
// Table section
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(); // Date
columns.RelativeColumn(1); // Office From
columns.RelativeColumn(1); // Office To
columns.RelativeColumn(); // Office Break
columns.RelativeColumn(1); // Outside From
columns.RelativeColumn(1); // Outside To
columns.RelativeColumn(); // Outside Break
columns.RelativeColumn(); // Total OT
columns.RelativeColumn(1); // Break Hours
columns.RelativeColumn(); // Net OT
if (departmentId == 2)
columns.RelativeColumn(); // Station
columns.RelativeColumn(1); // Day Type
columns.RelativeColumn(3); // Description
});
// Header Row
table.Header(header =>
{
header.Cell().Background("#d0ead2").Padding(5).Text("Date").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#dceefb").Padding(5).Text("From\n(Office)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#dceefb").Padding(5).Text("To\n(Office)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#dceefb").Padding(5).Text("Break\n(Office)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#edf2f7").Padding(5).Text("From\n(Outside)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#edf2f7").Padding(5).Text("To\n(Outside)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#edf2f7").Padding(5).Text("Break\n(Outside)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#fdebd0").Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#fdebd0").Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#fdebd0").Padding(5).Text("Net OT").FontSize(9).Bold().AlignCenter();
if (departmentId == 2)
header.Cell().Background("#d0f0ef").Padding(5).Text("Station").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#e0f7da").Padding(5).Text("Days").FontSize(9).Bold().AlignCenter();
header.Cell().Background("#e3f2fd").Padding(5).Text("Description").FontSize(9).Bold().AlignCenter();
});
// Data Rows
double totalOTSum = 0;
int totalBreakSum = 0;
TimeSpan totalNetOt = TimeSpan.Zero;
foreach (var r in records)
{
var totalOT = CalculateTotalOT(r);
var totalBreak = (r.OfficeBreak ?? 0) + (r.OutsideBreak ?? 0);
var netOT = totalOT - TimeSpan.FromMinutes(totalBreak);
totalOTSum += totalOT.TotalHours;
totalBreakSum += totalBreak;
totalNetOt += netOT;
table.Cell().Padding(5).Text(r.OtDate.ToString("dd/MM/yyyy")).FontSize(9);
table.Cell().Padding(5).Text(FormatTime(r.OfficeFrom)).FontSize(9);
table.Cell().Padding(5).Text(FormatTime(r.OfficeTo)).FontSize(9);
table.Cell().Padding(5).Text($"{r.OfficeBreak ?? 0} min").FontSize(9);
table.Cell().Padding(5).Text(FormatTime(r.OutsideFrom)).FontSize(9);
table.Cell().Padding(5).Text(FormatTime(r.OutsideTo)).FontSize(9);
table.Cell().Padding(5).Text($"{r.OutsideBreak ?? 0} min").FontSize(9);
table.Cell().Padding(5).Text($"{totalOT.TotalHours:F2}").FontSize(9);
table.Cell().Padding(5).Text($"{totalBreak}").FontSize(9);
table.Cell().Padding(5).Text($"{netOT.Hours} hr {netOT.Minutes} min").FontSize(9);
if (departmentId == 2)
table.Cell().Padding(5).Text(r.Stations?.StationName ?? "N/A").FontSize(9);
table.Cell().Padding(5).Text(r.OtDays).FontSize(9);
table.Cell().Padding(5).Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f);
}
// Totals Row
table.Cell().ColumnSpan((uint)(departmentId == 2 ? 7 : 6)).Background("#d8d1f5").Padding(5).Text("TOTAL").Bold().FontSize(9);
table.Cell().Background("#d8d1f5").Padding(5).Text($"{totalOTSum:F2}").Bold().FontSize(9);
table.Cell().Background("#d8d1f5").Padding(5).Text($"{totalBreakSum}").Bold().FontSize(9);
table.Cell().Background("#d8d1f5").Padding(5).Text($"{(int)totalNetOt.TotalHours} hr {totalNetOt.Minutes} min").Bold().FontSize(9);
if (departmentId == 2)
table.Cell().Background("#d8d1f5");
table.Cell().Background("#d8d1f5");
table.Cell().Background("#d8d1f5");
});
});
});
}).GeneratePdf(stream);
stream.Position = 0;
return stream;
}
private TimeSpan CalculateTotalOT(OtRegisterModel r)
{
TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero);
TimeSpan outside = (r.OutsideTo ?? TimeSpan.Zero) - (r.OutsideFrom ?? TimeSpan.Zero);
if (outside < TimeSpan.Zero)
outside += TimeSpan.FromHours(24);
return office + outside;
}
private string FormatTime(TimeSpan? time)
{
return time?.ToString(@"hh\:mm") ?? "-";
}
}
}

View File

@ -1,5 +1,5 @@
@{
ViewData["Title"] = "Records Overtime";
ViewData["Title"] = "Edit Overtime";
Layout = "~/Views/Shared/_Layout.cshtml";
}

View File

@ -1,5 +1,5 @@
@{
ViewData["Title"] = "My Overtime Records";
ViewData["Title"] = "Overtime Records";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -51,6 +51,14 @@
overflow-x: auto;
}
td.wrap-text {
white-space: pre-wrap; /* Keep line breaks and wrap text */
word-wrap: break-word; /* Break long words if necessary */
max-width: 300px; /* Adjust as needed */
text-align: left; /* Optional: left-align description */
}
</style>
@ -80,7 +88,8 @@
<th class="header-orange" rowspan="2">Total OT Hours</th>
<th class="header-orange" rowspan="2">Break Hours (min)</th>
<th class="header-orange" rowspan="2">Net OT</th>
<th class="header-orange" rowspan="2">Station</th>
<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-blue" rowspan="2">File</th>
<th class="header-green" rowspan="2">Action</th>
@ -106,8 +115,9 @@
<td>{{ calcTotalHours(record).toFixed(2) }}</td>
<td>{{ calcBreakTotal(record) }}</td>
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
<td>{{ record.stationName || 'N/A' }}</td>
<td>{{ record.otDescription }}</td>
<td v-if="isPSTWAIR">{{ record.stationName || 'N/A' }}</td>
<td>{{ record.otDays}}</td>
<td class="wrap-text">{{ record.otDescription }}</td>
<td>
<span v-if="record.pdfBase64">
<button class="btn btn-light border rounded-circle" title="View PDF" v-on:click="viewPdf(record.pdfBase64)">
@ -127,7 +137,7 @@
</tr>
<tr v-if="filteredRecords.length === 0">
<td colspan="14">No records found for selected month and year.</td>
<td :colspan="isPSTWAIR ? 14 : 13">No records found for selected month and year.</td>
</tr>
<tr class="table-primary fw-bold">
<td>TOTAL</td>
@ -135,6 +145,7 @@
<td>{{ totalHours.toFixed(2) }}</td>
<td>{{ totalBreak }}</td>
<td>{{ formatHourMinute(totalNetTime) }}</td>
<td v-if="isPSTWAIR"></td>
<td colspan="4"></td>
</tr>
</tbody>
@ -157,27 +168,25 @@
@section Scripts {
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.min.js"></script>
<script>
const app = Vue.createApp({
data() {
const currentYear = new Date().getFullYear();
return {
otRecords: [],
userId: null,
isPSTWAIR: false,
selectedMonth: new Date().getMonth() + 1,
selectedYear: new Date().getFullYear(),
selectedYear: currentYear,
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
years: Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - 5 + i)
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i)
};
},
computed: {
filteredRecords() {
return this.otRecords
.filter(record => {
const date = new Date(record.otDate);
return date.getMonth() + 1 === this.selectedMonth && date.getFullYear() === this.selectedYear;
})
.filter(r => new Date(r.otDate).getMonth() + 1 === this.selectedMonth && new Date(r.otDate).getFullYear() === this.selectedYear)
.sort((a, b) => new Date(a.otDate) - new Date(b.otDate));
},
totalHours() {
@ -186,38 +195,41 @@
totalBreak() {
return this.filteredRecords.reduce((sum, r) => sum + this.calcBreakTotal(r), 0);
},
netHours() {
return this.totalHours - (this.totalBreak / 60);
},
totalNetTime() {
const totalMinutes = (this.totalHours * 60) - this.totalBreak;
const hours = Math.floor(totalMinutes / 60);
const minutes = Math.round(totalMinutes % 60);
return { hours, minutes };
return {
hours: Math.floor(totalMinutes / 60),
minutes: Math.round(totalMinutes % 60)
};
}
},
async mounted() {
await this.initUserAndRecords();
},
await this.initUserAndRecords();
},
methods: {
async initUserAndRecords() {
await this.fetchUser();
if (this.userId) {
await this.fetchOtRecords();
}
if (this.userId) await this.fetchOtRecords();
},
async fetchUser() {
try {
const res = await fetch('/IdentityAPI/GetUserInformation', { method: 'POST' });
const data = await res.json();
const department = data.userInfo?.department;
this.userId = data.userInfo?.id;
} catch (err) { console.error("User fetch error", err); }
const deptName = department?.departmentName?.toUpperCase?.() || '';
this.isPSTWAIR = deptName.includes("PSTW") && deptName.includes("AIR") && department?.departmentId === 2;
} catch (err) {
console.error("User fetch error:", err);
}
},
async fetchOtRecords() {
try {
const res = await fetch(`/OvertimeAPI/GetUserOvertimeRecords/${this.userId}`);
this.otRecords = await res.json();
} catch (err) { console.error("Records fetch error", err); }
} catch (err) {
console.error("Records fetch error:", err);
}
},
formatDate(d) {
return new Date(d).toLocaleDateString();
@ -225,6 +237,12 @@
formatTime(t) {
return t ? t.slice(0, 5) : "-";
},
getTimeDiff(from, to) {
if (!from || !to) return 0;
const [fh, fm] = from.split(":").map(Number);
const [th, tm] = to.split(":").map(Number);
return ((th * 60 + tm) - (fh * 60 + fm)) / 60;
},
calcTotalHours(r) {
return this.getTimeDiff(r.officeFrom, r.officeTo) + this.getTimeDiff(r.outsideFrom, r.outsideTo);
},
@ -232,67 +250,73 @@
return (r.officeBreak || 0) + (r.outsideBreak || 0);
},
calcNetHours(r) {
const totalHours = this.calcTotalHours(r);
const breakMinutes = this.calcBreakTotal(r);
const netMinutes = (totalHours * 60) - breakMinutes;
const hours = Math.floor(netMinutes / 60);
const minutes = Math.round(netMinutes % 60);
return { hours, minutes };
const totalMinutes = (this.calcTotalHours(r) * 60) - this.calcBreakTotal(r);
return {
hours: Math.floor(totalMinutes / 60),
minutes: Math.round(totalMinutes % 60)
};
},
formatHourMinute(timeObj) {
if (!timeObj) return '-';
return `${timeObj.hours} hr ${timeObj.minutes} min`;
},
getTimeDiff(from, to) {
if (!from || !to) return 0;
const [fh, fm] = from.split(":").map(Number);
const [th, tm] = to.split(":").map(Number);
return ((th * 60 + tm) - (fh * 60 + fm)) / 60;
return timeObj ? `${timeObj.hours} hr ${timeObj.minutes} min` : '-';
},
editRecord(index) {
const record = this.filteredRecords[index];
window.location.href = `/OTcalculate/Overtime/EditOvertime?id=${record.overtimeId}`;
},
async deleteRecord(index) {
const record = this.filteredRecords[index];
const confirmed = confirm("Are you sure you want to delete this record?");
if (!confirmed) return;
if (!confirm("Are you sure you want to delete this record?")) return;
try {
const res = await fetch(`/OvertimeAPI/DeleteOvertimeRecord/${record.overtimeId}`, {
method: 'DELETE'
});
if (res.ok) {
// Remove from local state after successful backend deletion
this.otRecords.splice(this.otRecords.indexOf(record), 1);
} else {
const errorText = await res.text();
alert("Failed to delete: " + errorText);
}
const res = await fetch(`/OvertimeAPI/DeleteOvertimeRecord/${record.overtimeId}`, { method: 'DELETE' });
if (res.ok) this.otRecords.splice(this.otRecords.indexOf(record), 1);
else alert("Failed to delete: " + await res.text());
} catch (err) {
console.error("Delete failed", err);
alert("Error deleting record.");
}
},
printTable() {
window.print();
},
downloadPdf() {
const element = document.getElementById("print-section");
html2pdf().from(element).save("OT_Records.pdf");
async downloadPdf() {
try {
const res = await fetch(`/OvertimeAPI/GenerateOvertimePdf?month=${this.selectedMonth}&year=${this.selectedYear}`);
if (res.ok) {
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `OvertimeRecords_${this.selectedYear}_${this.selectedMonth}.pdf`;
a.click();
URL.revokeObjectURL(url);
} else {
alert("Failed to generate PDF: " + await res.text());
}
} catch (err) {
console.error("PDF download error:", err);
alert("An error occurred while generating the PDF.");
}
},
submitRecords() {
alert("Submitting records...");
async submitRecords() {
try {
const res = await fetch('/OvertimeAPI/SaveOvertimeRecordsWithPdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.otRecords)
});
if (res.ok) alert("Overtime records submitted successfully.");
else alert("Submission failed.");
} catch (err) {
console.error("Submission error:", err);
alert("An error occurred during submission.");
}
},
viewPdf(base64) {
const byteArray = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
const blob = new Blob([byteArray], { type: 'application/pdf' });
window.open(URL.createObjectURL(blob));
const pdfWindow = window.open("");
pdfWindow.document.write(`<iframe width='100%' height='100%' src='data:application/pdf;base64,${base64}'></iframe>`);
}
}
});
app.mount("#app");
</script>
}
}

View File

@ -54,7 +54,7 @@
</div>
</div>
<div class="mb-3">
<div class="mb-3" v-if="isPSTWAIR">
<label for="airstationDropdown">Air Station</label>
<select id="airstationDropdown" class="form-control" v-model="selectedAirStation">
<option value="" disabled selected>Select Station</option>
@ -65,11 +65,18 @@
<small class="text-danger">*Only for PSTW AIR</small>
</div>
<div class="mb-3">
<label for="otDescription">Work Brief Description</label>
<textarea id="otDescription" class="form-control" v-model="otDescription"
<textarea id="otDescription" class="form-control"
v-model="otDescription"
v-on:input="limitCharCount"
placeholder="Describe the work done..."></textarea>
<small class="text-muted">
{{ charCount }} / 150 characters
</small>
</div>
</div>
<div class="col-md-5">
@ -143,11 +150,20 @@
uploadedFile: null,
currentUser: null,
userId: null,
isPSTWAIR: false,
};
},
computed: {
charCount() {
return this.otDescription.length;
}
},
async mounted() {
this.fetchStations();
await this.fetchUser();
if (this.isPSTWAIR) {
this.fetchStations();
}
},
methods: {
async fetchStations() {
@ -167,6 +183,14 @@
const data = await response.json();
this.currentUser = data?.userInfo || null;
this.userId = this.currentUser?.id || null;
console.log("Fetched User:", this.currentUser);
console.log("Dept ID:", this.currentUser?.departmentId);
if (this.currentUser?.department?.departmentId === 2) {
this.isPSTWAIR = true;
console.log("User is PSTW AIR");
}
} else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
@ -174,6 +198,12 @@
console.error("Error fetching user:", error);
}
},
limitCharCount(event) {
if (this.otDescription.length > 150) {
this.otDescription = this.otDescription.substring(0, 150);
event.preventDefault();
}
},
calculateOTAndBreak() {
let officeOT = this.calculateTimeDifference(this.officeFrom, this.officeTo, this.officeBreak);
let outsideOT = this.calculateTimeDifference(this.outsideFrom, this.outsideTo, this.outsideBreak);
@ -223,7 +253,7 @@
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // Ensure valid HH:mm:ss format
},
async addOvertime() {
if (!this.selectedDate || !this.selectedDayType) {
if (this.isPSTWAIR && !this.selectedAirStation) {
alert("Please fill in all required fields.");
return;
}
@ -253,13 +283,14 @@
outsideFrom: this.formatTime(this.outsideFrom) || null,
outsideTo: this.formatTime(this.outsideTo) || null,
outsideBreak: this.outsideBreak || 0,
stationId: this.selectedAirStation || null,
otDescription: this.otDescription,
stationId: this.isPSTWAIR ? parseInt(this.selectedAirStation) : null,
otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '),
otDays: this.selectedDayType,
pdfBase64: base64String,
userId: this.userId,
};
try {
const response = await fetch(`${window.location.origin}/OvertimeAPI/AddOvertime`, {
method: "POST",

View File

@ -19,6 +19,11 @@ using System.Reflection;
using static System.Collections.Specialized.BitVector32;
using System.Security.Claims;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
using Microsoft.AspNetCore.Mvc.Rendering;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using PSTW_CentralSystem.Areas.OTcalculate.Services;
namespace PSTW_CentralSystem.Controllers.API
@ -30,12 +35,15 @@ namespace PSTW_CentralSystem.Controllers.API
private readonly ILogger<OvertimeAPI> _logger;
private readonly CentralSystemContext _centralDbContext;
private readonly UserManager<UserModel> _userManager;
private readonly OvertimePdfService _pdfService;
public OvertimeAPI(ILogger<OvertimeAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> userManager)
public OvertimeAPI(ILogger<OvertimeAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> userManager, OvertimePdfService pdfService)
{
_logger = logger;
_centralDbContext = centralDbContext;
_userManager = userManager;
_pdfService = pdfService;
}
#region Settings
@ -485,5 +493,90 @@ namespace PSTW_CentralSystem.Controllers.API
}
#endregion
[HttpPost("SaveOvertimeRecordsWithPdf")]
public async Task<IActionResult> SaveOvertimeRecordsWithPdf([FromBody] List<OtRegisterModel> records)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
_logger.LogInformation("SaveOvertimeRecordsWithPdf called with {RecordCount} records", records.Count);
foreach (var record in records)
{
_logger.LogDebug("Processing record with OvertimeId: {OvertimeId}", record.OvertimeId);
var existingRecord = await _centralDbContext.Otregisters.FindAsync(record.OvertimeId);
if (existingRecord != null)
{
_logger.LogDebug("Updating existing record with OvertimeId: {OvertimeId}", record.OvertimeId);
existingRecord.PDFBase64 = record.PDFBase64;
_centralDbContext.Otregisters.Update(existingRecord);
}
else
{
_logger.LogWarning("Record with OvertimeId: {OvertimeId} not found, adding new", record.OvertimeId);
_centralDbContext.Otregisters.Add(record);
}
}
await _centralDbContext.SaveChangesAsync();
_logger.LogInformation("Successfully saved {RecordCount} overtime records with PDFs", records.Count);
return Ok("Overtime records updated with PDFs.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving overtime records with PDFs");
return StatusCode(500, "An error occurred while saving records.");
}
}
[HttpGet("GenerateOvertimePdf")]
public IActionResult GenerateOvertimePdf(int month, int year)
{
var userIdString = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(userIdString) || !int.TryParse(userIdString, out int userId))
{
return Unauthorized();
}
// Get user and department info
var user = _centralDbContext.Users
.Include(u => u.Department)
.FirstOrDefault(u => u.Id == userId);
if (user == null)
return NotFound("User not found");
var userFullName = $"{user.FullName}";
var departmentId = user.departmentId ?? 0;
var departmentName = user.Department?.DepartmentName ?? "N/A";
var records = _centralDbContext.Otregisters
.Include(o => o.Stations)
.Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year)
.ToList();
// Optional: load logo image as byte array
byte[]? logoImage = null;
var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg");
if (System.IO.File.Exists(logoPath))
{
logoImage = System.IO.File.ReadAllBytes(logoPath);
}
var stream = _pdfService.GenerateOvertimeTablePdf(records, departmentId, userFullName, departmentName, logoImage);
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
}
}
}

View File

@ -28,6 +28,7 @@
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="QuestPDF" Version="2025.4.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
</ItemGroup>
@ -37,6 +38,7 @@
<Folder Include="Areas\Report\Models\" />
<Folder Include="Controllers\API\Reporting\" />
<Folder Include="Logs\" />
<Folder Include="wwwroot\NewFolder\" />
</ItemGroup>
</Project>

View File

@ -5,6 +5,9 @@ using PSTW_CentralSystem.CustomPolicy;
using PSTW_CentralSystem.DBContext;
using PSTW_CentralSystem.Models;
using Serilog;
using QuestPDF;
using QuestPDF.Infrastructure;
using PSTW_CentralSystem.Areas.OTcalculate.Services;
internal class Program
{
@ -12,11 +15,14 @@ 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.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddScoped<OvertimePdfService>();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
@ -94,4 +100,5 @@ internal class Program
app.Run();
}
}

BIN
wwwroot/images/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB