Files
PowderCoatingLogix/src/PowderCoating.Web/Controllers/WorkOrderController.cs
T
2026-04-23 21:38:24 -04:00

240 lines
10 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PowderCoating.Application.DTOs.Company;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Interfaces;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace PowderCoating.Web.Controllers;
[Authorize]
public class WorkOrderController : Controller
{
private readonly IUnitOfWork _unitOfWork;
private readonly ITenantContext _tenantContext;
private readonly ICompanyLogoService _logoService;
public WorkOrderController(IUnitOfWork unitOfWork, ITenantContext tenantContext, ICompanyLogoService logoService)
{
_unitOfWork = unitOfWork;
_tenantContext = tenantContext;
_logoService = logoService;
}
/// <summary>
/// Streams a blank work order PDF. Uses the company logo from file storage,
/// accent color, and terms from CompanyPreferences.WoTerms.
/// </summary>
[HttpGet]
public async Task<IActionResult> Blank()
{
var companyId = _tenantContext.GetCurrentCompanyId();
if (companyId == null) return Forbid();
var company = await _unitOfWork.Companies.GetByIdAsync(companyId.Value);
var prefs = await _unitOfWork.CompanyPreferences.FirstOrDefaultAsync(
p => p.CompanyId == companyId.Value && !p.IsDeleted);
// Try file-system logo first, then fall back to legacy DB bytes
byte[]? logoBytes = null;
if (!string.IsNullOrWhiteSpace(company?.LogoFilePath))
{
var (ok, content, _, _) = await _logoService.GetCompanyLogoAsync(company.LogoFilePath);
if (ok) logoBytes = content;
}
if (logoBytes == null || logoBytes.Length == 0)
logoBytes = company?.LogoData;
var companyInfo = new CompanyInfoDto
{
CompanyName = company?.CompanyName ?? string.Empty,
Phone = company?.Phone,
Address = company?.Address,
City = company?.City,
State = company?.State,
ZipCode = company?.ZipCode,
};
var pdfBytes = GenerateBlankWorkOrderPdf(logoBytes, companyInfo, prefs?.WoAccentColor, prefs?.WoTerms);
Response.Headers["Content-Disposition"] = "inline; filename=\"Blank-Work-Order.pdf\"";
return File(pdfBytes, "application/pdf");
}
private static byte[] GenerateBlankWorkOrderPdf(
byte[]? logoData,
CompanyInfoDto companyInfo,
string? accentHex,
string? terms)
{
QuestPDF.Settings.License = LicenseType.Community;
var accent = ResolveColor(accentHex, Colors.Grey.Darken3);
const string altRow = "#F5F5F5";
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.Letter);
page.MarginHorizontal(0.6f, Unit.Inch);
page.MarginTop(0.5f, Unit.Inch);
page.MarginBottom(0.4f, Unit.Inch);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(8.5f).FontFamily("Arial"));
page.Content().Column(col =>
{
// ── HEADER: logo left, company info + drop-off date right ─
col.Item().Row(row =>
{
if (logoData != null && logoData.Length > 0)
row.ConstantItem(100).PaddingRight(10).Image(logoData).FitArea();
row.RelativeItem().AlignRight().Column(c =>
{
c.Item().Text(companyInfo.CompanyName)
.FontSize(18).Bold().FontColor(accent);
var parts = new[]
{
companyInfo.Address,
string.Join(", ", new[] { companyInfo.City, companyInfo.State }
.Where(s => !string.IsNullOrWhiteSpace(s))),
companyInfo.ZipCode,
companyInfo.Phone
}.Where(s => !string.IsNullOrWhiteSpace(s));
foreach (var p in parts)
c.Item().Text(p!).FontSize(9).FontColor(Colors.Grey.Darken1);
});
});
// ── TITLE ROW: "WORK ORDER" left, Drop Off Date field right ─
col.Item().PaddingTop(8).Row(row =>
{
row.RelativeItem().AlignBottom()
.Text("WORK ORDER")
.FontSize(24).FontColor(Colors.Grey.Lighten1).Bold();
row.ConstantItem(180).AlignBottom().Column(c =>
{
c.Item().Text("DROP OFF DATE")
.FontSize(7.5f).Bold().FontColor(accent);
c.Item().PaddingTop(2)
.LineHorizontal(1).LineColor(Colors.Grey.Darken1);
});
});
col.Item().PaddingTop(4).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
col.Item().Height(6);
// ── CLIENT INFO (no Drop Off Date column) ────────────────
col.Item().Table(table =>
{
table.ColumnsDefinition(c => c.RelativeColumn());
void HeaderCell(uint r, string label)
=> table.Cell().Row(r).Column(1)
.Background(accent).Padding(4)
.Text(label).FontSize(7.5f).Bold().FontColor(Colors.White);
void DataCell(uint r)
=> table.Cell().Row(r).Column(1)
.Border(0.5f).BorderColor(Colors.Grey.Lighten2)
.MinHeight(18).Padding(3).Text("");
HeaderCell(1, "CLIENT NAME");
DataCell(2);
HeaderCell(3, "CLIENT PHONE");
DataCell(4);
HeaderCell(5, "DUE DATE (if applicable)");
DataCell(6);
});
col.Item().Height(8);
// ── ITEMS TABLE ──────────────────────────────────────────
col.Item().Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(6);
c.RelativeColumn(2);
c.RelativeColumn(1.5f);
});
// Header
foreach (var (label, align) in new[] { ("PART DESCRIPTION", "left"), ("COLOR", "center"), ("QUOTE", "center") })
{
var cell = table.Cell().Background(accent).Padding(5);
var txt = cell.Text(label).FontSize(7.5f).Bold().FontColor(Colors.White);
if (align == "center") txt.AlignCenter();
}
// 12 data rows — tight height to fit page
for (int i = 0; i < 12; i++)
{
string bg = i % 2 == 1 ? altRow : Colors.White;
for (int c = 0; c < 3; c++)
table.Cell().Background(bg)
.Border(0.5f).BorderColor(Colors.Grey.Lighten2)
.MinHeight(16).Padding(3).Text("");
}
// Dark footer bar
table.Cell().ColumnSpan(3).Background(accent).MinHeight(6).Text("");
});
col.Item().Height(8);
// ── NOTES ────────────────────────────────────────────────
col.Item().Table(table =>
{
table.ColumnsDefinition(c => c.RelativeColumn());
table.Cell().Background(accent).Padding(5)
.Text("NOTES").FontSize(7.5f).Bold().FontColor(Colors.White);
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2)
.MinHeight(65).Padding(3).Text("");
});
col.Item().Height(8);
// ── TERMS ────────────────────────────────────────────────
if (!string.IsNullOrWhiteSpace(terms))
{
col.Item().PaddingBottom(8)
.Text(terms).FontSize(7.5f).Italic();
}
// ── SIGNATURE LINE ───────────────────────────────────────
col.Item().Row(row =>
{
row.RelativeItem(3).Column(c =>
{
c.Item().Text("Customer Signature:").FontSize(8.5f);
c.Item().PaddingTop(2).LineHorizontal(1).LineColor(Colors.Grey.Darken1);
});
row.ConstantItem(20);
row.RelativeItem(1).Column(c =>
{
c.Item().Text("Date:").FontSize(8.5f);
c.Item().PaddingTop(2).LineHorizontal(1).LineColor(Colors.Grey.Darken1);
});
});
});
});
});
return document.GeneratePdf();
}
private static string ResolveColor(string? hex, string fallback)
{
if (string.IsNullOrWhiteSpace(hex)) return fallback;
try { _ = System.Drawing.ColorTranslator.FromHtml(hex); return hex; }
catch { return fallback; }
}
}