QR-Scanner: Kamera-Fallbacks und Foto-Upload; weitere Aenderungen

This commit is contained in:
spaethf committed 2026-09-29 18:08:01 +02:00
1 parent 7a0406eaa2
commit e2aef67099
13 files changed
+965 -58

No files matched your search

@@ -0,0 +1,184 @@
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));
}