feat: Etiketten-Seitenlayout auf dem A4-Bogen frei waehlbar
Backend Image / build-and-push (push) Successful in 2m16s
Frontend Image / build-and-push (push) Successful in 2m0s

- LabelTemplate bekommt columns/rows (optional, leer = automatisch
  passend), plus konfigurierbare Raender/Abstaende - damit ein Etikett
  exakt auf das vorgedruckte Raster eines physischen Etikettenbogens
  (z.B. Avery) gelegt werden kann statt nur automatisch berechnet
- Startposition beim Drucken waehlbar, fuer bereits angebrochene Boegen
  (erste Seite startet ab der gewaehlten Position, Folgeseiten wieder
  von vorne)
This commit is contained in:
Nick Szaunig committed 2026-09-01 19:43:01 +02:00
1 parent 9659b8d266
commit c6941a797f
11 files changed
+357 -27

No files matched your search

@@ -64,6 +64,12 @@ describe('LabelTemplatesService', () => {
elements,
widthMm: 90,
heightMm: 35,
columns: null,
rows: null,
marginTopMm: 5,
marginLeftMm: 5,
gapXMm: 2,
gapYMm: 2,
isDefault: false,
active: true,
};
@@ -213,5 +219,56 @@ describe('LabelTemplatesService', () => {
// A PDF with an embedded raster image contains an XObject /Image entry.
expect(pdf.toString('latin1')).toContain('/Image');
});
function countPages(pdf: Buffer): number {
const matches = pdf.toString('latin1').match(/\/Type\s*\/Page[^s]/g);
return matches ? matches.length : 0;
}
it('uses an explicit columns/rows override instead of auto-fitting', async () => {
prisma.labelTemplate.findUnique.mockResolvedValue({
...template,
columns: 1,
rows: 1,
});
prisma.bottlingBatch.findUnique.mockResolvedValue({
id: 'batch-1',
jarSize: 500,
jarCount: 3,
lotNumber: 'L-001',
date: new Date('2026-06-01T00:00:00.000Z'),
bestBeforeDate: new Date('2028-06-01T00:00:00.000Z'),
honeyBatch: { batchNumber: 'HB-001', honeyType: 'Bluetenhonig' },
product: { name: 'Glas 500g' },
});
const pdf = await service.renderLabelsPdf('template-1', 'batch-1');
expect(countPages(pdf)).toBe(3);
});
it('skips already-used slots on the first sheet via startPosition, but not later sheets', async () => {
prisma.labelTemplate.findUnique.mockResolvedValue({
...template,
columns: 2,
rows: 1,
});
prisma.bottlingBatch.findUnique.mockResolvedValue({
id: 'batch-1',
jarSize: 500,
jarCount: 3,
lotNumber: 'L-001',
date: new Date('2026-06-01T00:00:00.000Z'),
bestBeforeDate: new Date('2028-06-01T00:00:00.000Z'),
honeyBatch: { batchNumber: 'HB-001', honeyType: 'Bluetenhonig' },
product: { name: 'Glas 500g' },
});
const pdf = await service.renderLabelsPdf('template-1', 'batch-1', 2);
// perPage=2, skip=1: label 1 fills the remaining slot on page 1,
// labels 2-3 fill both slots of page 2.
expect(countPages(pdf)).toBe(2);
});
});
});