This commit is contained in:
seb
2026-11-01 04:33:01 +01:00
parent cb30352b57
commit e8a5322775
4 changed files with 253 additions and 7 deletions

View File

@@ -1,7 +1,17 @@
import { validateObjects } from 'react-canvas-label-editor';
import fs from 'fs/promises';
import path from 'path';
import { renderLabel, validateObjects } from 'react-canvas-label-editor';
import * as db from '../../db/labeltemplate.js';
import * as printerDb from '../../db/printer.js';
import * as agentprinterDb from '../../db/agentprinter.js';
import * as printerjobDb from '../../db/printerjob.js';
import * as articleDb from '../../db/article.js';
import { renderThumb } from '../../services/labelThumb.js';
import { pngToZpl } from '../../services/zplService.js';
import { notifyAgentJob } from '../agent/index.js';
import { emit } from '../sse.js';
import { applyZplCopies } from './zpljob.js';
import { interpolateString } from './article/shared.js';
// #region Helpers
@@ -482,6 +492,222 @@ export function register(app, basePath = '') {
});
// #endregion
/**
* @openapi
* /labeltemplate/testprint:
* post:
* tags: [LabelTemplate]
* summary: Test print label template
* description: |
* Dispatches a test print job for a label template or unsaved canvas objects to a specified ZPL printer.
* Placeholders in text and barcodes are interpolated using a sample article or the specified article SKU.
* security:
* - cookieAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* printerId:
* type: integer
* agentprinterId:
* type: integer
* templateId:
* type: integer
* objects:
* type: array
* width:
* type: integer
* height:
* type: integer
* dpi:
* type: integer
* sku:
* type: string
* quantity:
* type: integer
* responses:
* 200:
* description: Print job created
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* 400:
* description: Invalid parameters
* 404:
* description: Printer or template not found
* 500:
* description: Server error
*/
// #region POST /labeltemplate/testprint
async function handleTestPrint(req, res) {
const selected = requireSelectedCompany(req);
if (selected.error) {
return res.status(400).json({ error: selected.error });
}
const companyId = selected.companyId;
const rawPrinterId = req.body?.printerId ?? req.body?.agentprinterId;
const parsedPrinterId = parsePositiveInt(rawPrinterId, 'printerId');
if (parsedPrinterId.error) {
return res.status(400).json({ error: parsedPrinterId.error });
}
try {
let agentPrinter = null;
let printer = null;
if (req.body?.agentprinterId != null) {
const parsedApId = parsePositiveInt(req.body.agentprinterId, 'agentprinterId');
if (!parsedApId.error) {
agentPrinter = await agentprinterDb.getById(req.user.id, parsedApId.id);
if (agentPrinter && agentPrinter.company_id === companyId) {
printer = agentPrinter;
} else {
agentPrinter = null;
}
}
}
if (!agentPrinter) {
printer = await printerDb.getById(req.user.id, parsedPrinterId.id);
if (!printer || printer.company_id !== companyId) {
agentPrinter = await agentprinterDb.getById(req.user.id, parsedPrinterId.id);
if (agentPrinter && agentPrinter.company_id === companyId) {
printer = agentPrinter;
} else {
return res.status(404).json({ error: 'Printer not found' });
}
} else if (printer.printeragentId != null) {
agentPrinter = printer;
}
}
if (printer.type && printer.type.toLowerCase() !== 'zpl') {
return res.status(400).json({ error: 'Printer is not a ZPL printer' });
}
const rawTemplateId = req.params?.id ?? req.body?.templateId;
let template = null;
if (rawTemplateId != null) {
const parsedTmplId = parsePositiveInt(rawTemplateId, 'templateId');
if (!parsedTmplId.error) {
template = await db.getById(req.user.id, parsedTmplId.id);
if (!template || template.company_id !== companyId) {
return res.status(404).json({ error: 'Label template not found' });
}
}
}
const rawQuantity = req.body?.quantity ?? 1;
const quantity = Number(rawQuantity) > 0 ? Number(rawQuantity) : 1;
let objects = req.body?.objects;
let width = req.body?.width != null ? Number(req.body.width) : template?.width;
let height = req.body?.height != null ? Number(req.body.height) : template?.height;
let content = req.body?.content ?? template?.content;
// Resolve sample article for placeholder interpolation
let article = null;
if (req.body?.sku) {
try {
const result = await articleDb.getBySku(req.user.id, req.body.sku);
article = result?.article;
} catch {
// ignore
}
}
if (!article) {
article = {
name: req.body?.name || template?.name || 'Musterartikel',
sku: 'TEST-12345',
prices: [{ net: 19.99, taxRate: 19 }],
eans: ['4012345678901'],
};
}
let zplData;
if (typeof content === 'string' && content.includes('^XA')) {
let interpolated = interpolateString(content, article);
if (quantity > 1) {
interpolated = applyZplCopies(interpolated, quantity);
}
zplData = interpolated;
} else {
if (!objects && content) {
objects = Array.isArray(content?.objects)
? content.objects
: Array.isArray(content)
? content
: [];
}
if (!Array.isArray(objects)) {
objects = [];
}
const interpolatedObjects = objects.map((obj) => {
const cloned = { ...obj };
if (typeof cloned.text === 'string') {
cloned.text = interpolateString(cloned.text, article);
}
if (typeof cloned.code === 'string') {
cloned.code = interpolateString(cloned.code, article);
}
return cloned;
});
const printPayload = await renderLabel({
width: width || 448,
height: height || 200,
objects: interpolatedObjects,
});
zplData = await pngToZpl(printPayload, quantity);
}
if (agentPrinter) {
const job = await printerjobDb.create(req.user.id, {
agentprinterId: agentPrinter.id,
type: 'zpl',
data: zplData,
});
if (job.printeragentId) {
notifyAgentJob(job.printeragentId, 'created', job);
}
return res.status(200).json({ success: true, printerjob: job });
}
// Fallback for legacy network printers with static IP / address
const spoolKey = printer.address ? `${printer.address}_${printer.type}` : `printer_${printer.id}_${printer.type || 'zpl'}`;
const printDir = path.join(process.cwd(), 'print_spool', spoolKey);
await fs.mkdir(printDir, { recursive: true });
const timestamp = Date.now();
const filename = `${timestamp}.zpl`;
const filePath = path.join(printDir, filename);
await fs.writeFile(filePath, zplData, 'utf8');
return res.status(200).json({ success: true });
} catch (error) {
console.error('Testprint label template failed:', error);
return res.status(500).json({ error: error.message });
}
}
app.post(`${basePath}/labeltemplate/testprint`, handleTestPrint);
app.post(`${basePath}/labeltemplate/:id/testprint`, handleTestPrint);
// #endregion
}
/**