185 lines
6.9 KiB
C#
185 lines
6.9 KiB
C#
using Lagerbestand.Models;
|
||
using QuestPDF.Fluent;
|
||
using QuestPDF.Helpers;
|
||
using QuestPDF.Infrastructure;
|
||
|
||
namespace Lagerbestand.Services;
|
||
|
||
public interface IStorageRowPdfService
|
||
{
|
||
/// <summary>
|
||
/// Generates a printable A4 inventory sheet for one shelf row (Area + Row).
|
||
/// </summary>
|
||
byte[] GenerateRowPdf(AreaType area, int row, IReadOnlyList<StorageLocation> locations);
|
||
}
|
||
|
||
public class StorageRowPdfService : IStorageRowPdfService
|
||
{
|
||
private static readonly string HeaderBackground = Colors.Grey.Lighten3;
|
||
private static readonly string BorderColor = Colors.Grey.Medium;
|
||
|
||
public byte[] GenerateRowPdf(AreaType area, int row, IReadOnlyList<StorageLocation> locations)
|
||
{
|
||
var rowLocations = locations
|
||
.Where(l => l.Area == area && l.Row == row)
|
||
.ToList();
|
||
|
||
var occupied = rowLocations.Count(l => l.IsOccupied);
|
||
var total = StorageLocation.PositionsPerSlot * StorageLocation.MaxSlot;
|
||
var printedAt = DateTime.Now;
|
||
|
||
var document = Document.Create(container =>
|
||
{
|
||
container.Page(page =>
|
||
{
|
||
page.Size(PageSizes.A4);
|
||
page.Margin(1.5f, Unit.Centimetre);
|
||
page.DefaultTextStyle(x => x.FontSize(9));
|
||
|
||
page.Header()
|
||
.PaddingBottom(12)
|
||
.AlignCenter()
|
||
.Text($"{area.GetDisplayName()} – Regal {row}")
|
||
.Bold()
|
||
.FontSize(20);
|
||
|
||
page.Content().Row(r =>
|
||
{
|
||
r.RelativeItem().Column(col =>
|
||
{
|
||
col.Spacing(14);
|
||
col.Item().Element(c => RenderPositionTable(c, area, row, ShelfPosition.LinksOben, rowLocations));
|
||
col.Item().Element(c => RenderPositionTable(c, area, row, ShelfPosition.LinksUnten, rowLocations));
|
||
});
|
||
|
||
r.ConstantItem(14);
|
||
|
||
r.RelativeItem().Column(col =>
|
||
{
|
||
col.Spacing(14);
|
||
col.Item().Element(c => RenderPositionTable(c, area, row, ShelfPosition.RechtsOben, rowLocations));
|
||
col.Item().Element(c => RenderPositionTable(c, area, row, ShelfPosition.RechtsUnten, rowLocations));
|
||
});
|
||
});
|
||
|
||
page.Footer()
|
||
.PaddingTop(10)
|
||
.Row(f =>
|
||
{
|
||
f.RelativeItem().Text($"Gedruckt am {printedAt:dd.MM.yyyy HH:mm}").FontSize(8).FontColor(Colors.Grey.Darken1);
|
||
f.RelativeItem().AlignRight().Text($"{occupied} / {total} belegt").FontSize(8).FontColor(Colors.Grey.Darken1);
|
||
});
|
||
});
|
||
});
|
||
|
||
return document.GeneratePdf();
|
||
}
|
||
|
||
private static void RenderPositionTable(IContainer container, AreaType area, int row, ShelfPosition position, List<StorageLocation> rowLocations)
|
||
{
|
||
var bySlot = rowLocations
|
||
.Where(l => l.Position == position)
|
||
.GroupBy(l => l.SlotNumber)
|
||
.ToDictionary(g => g.Key, g => g.First());
|
||
|
||
container.Column(col =>
|
||
{
|
||
col.Spacing(4);
|
||
col.Item().Text(position.GetDisplayName()).Bold().FontSize(11);
|
||
|
||
col.Item().Table(table =>
|
||
{
|
||
table.ColumnsDefinition(columns =>
|
||
{
|
||
columns.ConstantColumn(28);
|
||
columns.ConstantColumn(34);
|
||
columns.RelativeColumn(3);
|
||
columns.RelativeColumn(2);
|
||
columns.ConstantColumn(44);
|
||
});
|
||
|
||
table.Header(header =>
|
||
{
|
||
header.Cell().Element(HeaderCell).Text("Platz");
|
||
header.Cell().Element(HeaderCell).Text("Code");
|
||
header.Cell().Element(HeaderCell).Text("Kunde/Inhalt");
|
||
header.Cell().Element(HeaderCell).Text("Fahrzeug");
|
||
header.Cell().Element(HeaderCell).Text("Saison");
|
||
});
|
||
|
||
for (var slot = StorageLocation.MinSlot; slot <= StorageLocation.MaxSlot; slot++)
|
||
{
|
||
bySlot.TryGetValue(slot, out var location);
|
||
var code = location?.Code ?? $"{(int)area}{row}{(int)position}{slot}";
|
||
|
||
string content, vehicle, season;
|
||
var muted = false;
|
||
|
||
if (location is { IsOccupied: true, OccupiedTireStack: not null })
|
||
{
|
||
var stack = location.OccupiedTireStack;
|
||
content = stack.Customer?.FullName ?? stack.CatalogItem?.Name ?? "Lager";
|
||
vehicle = stack.Vehicle?.ShortName ?? "–";
|
||
season = SeasonText(stack.Season);
|
||
}
|
||
else if (location is { IsOccupied: true })
|
||
{
|
||
content = "Belegt";
|
||
vehicle = "–";
|
||
season = "–";
|
||
}
|
||
else if (location is { IsAvailable: false })
|
||
{
|
||
content = "Gesperrt";
|
||
vehicle = "–";
|
||
season = "–";
|
||
muted = true;
|
||
}
|
||
else
|
||
{
|
||
content = "Frei";
|
||
vehicle = "–";
|
||
season = "–";
|
||
muted = true;
|
||
}
|
||
|
||
table.Cell().Element(BodyCell).Text(slot.ToString());
|
||
table.Cell().Element(BodyCell).Text(code);
|
||
table.Cell().Element(BodyCell).Text(t => Styled(t.Span(content), muted));
|
||
table.Cell().Element(BodyCell).Text(t => Styled(t.Span(vehicle), muted));
|
||
table.Cell().Element(BodyCell).Text(t => Styled(t.Span(season), muted));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
private static void Styled(TextSpanDescriptor span, bool muted)
|
||
{
|
||
if (muted)
|
||
span.FontColor(Colors.Grey.Medium).Italic();
|
||
}
|
||
|
||
private static string SeasonText(TireSeason season) => season switch
|
||
{
|
||
TireSeason.Sommer => "Sommer",
|
||
TireSeason.Winter => "Winter",
|
||
TireSeason.Allrad => "Ganzjahr",
|
||
_ => season.ToString()
|
||
};
|
||
|
||
private static IContainer HeaderCell(IContainer container) => container
|
||
.Background(HeaderBackground)
|
||
.Border(0.5f)
|
||
.BorderColor(BorderColor)
|
||
.PaddingVertical(3)
|
||
.PaddingHorizontal(4)
|
||
.DefaultTextStyle(x => x.Bold().FontSize(8));
|
||
|
||
private static IContainer BodyCell(IContainer container) => container
|
||
.Border(0.5f)
|
||
.BorderColor(BorderColor)
|
||
.PaddingVertical(3)
|
||
.PaddingHorizontal(4)
|
||
.DefaultTextStyle(x => x.FontSize(8));
|
||
}
|