Initial commit
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user