This commit is contained in:
seb
2026-10-31 21:19:13 +01:00
parent 6719cf9d51
commit f6eb31b70d
4 changed files with 169 additions and 21 deletions

View File

@@ -17,6 +17,7 @@ export { posterminal } from './posterminal.js';
export { printer } from './printer.js'; export { printer } from './printer.js';
export { printeragent } from './printeragent.js'; export { printeragent } from './printeragent.js';
export { agentprinter } from './agentprinter.js'; export { agentprinter } from './agentprinter.js';
export { printerjob } from './printerjob.js';
export { propertyname } from './propertyname.js'; export { propertyname } from './propertyname.js';
export { quote } from './quote.js'; export { quote } from './quote.js';
export { retoure } from './retoure.js'; export { retoure } from './retoure.js';

100
src/api/printerjob.js Normal file
View File

@@ -0,0 +1,100 @@
import { http } from './http.js';
export const printerjob = {
/**
* POST /api/zpljob
* @param {{ agentprinterId: number, data: string, copiescount?: number }} payload
* → { printerjob: PrinterJob }
*/
createZplJob({ agentprinterId, data, copiescount = 1 }) {
return http('/api/zpljob', {
method: 'POST',
body: {
agentprinterId: Number(agentprinterId),
data: String(data),
copiescount: Number(copiescount) || 1,
},
});
},
/**
* Sends a minimal ZPL test label to a specific agent printer.
* @param {number} agentprinterId
* @param {{ model?: string }} [options]
* → { printerjob: PrinterJob }
*/
sendTestZpl(agentprinterId, { model = 'Zebra' } = {}) {
const timeStr = new Date().toLocaleTimeString('de-DE');
const safeModel = String(model || 'Drucker').replace(/[\^~]/g, '');
const zpl = [
'^XA',
'^FO40,40^ADN,36,20^FDTestdruck OK^FS',
`^FO40,85^ADN,18,10^FD${safeModel}^FS`,
`^FO40,115^ADN,18,10^FDID: ${agentprinterId} - ${timeStr}^FS`,
'^XZ',
].join('\n');
return this.createZplJob({
agentprinterId,
data: zpl,
copiescount: 1,
});
},
/**
* GET /api/printerjob
* @param {{ agentprinterId?: number, state?: string }} [query]
* → { printerjobs: PrinterJob[] }
*/
list(query = {}) {
const params = new URLSearchParams();
if (query.agentprinterId != null) params.set('agentprinterId', String(query.agentprinterId));
if (query.state != null) params.set('state', String(query.state));
const qs = params.toString();
return http(qs ? `/api/printerjob?${qs}` : '/api/printerjob');
},
/**
* GET /api/printerjob/:id
* → { printerjob: PrinterJob }
*/
get(id) {
return http(`/api/printerjob/${id}`);
},
/**
* POST /api/printerjob
* @param {{ agentprinterId: number, type?: 'zpl'|'pdf'|'epos', data: string }} payload
* → { printerjob: PrinterJob }
*/
create({ agentprinterId, type = 'zpl', data }) {
return http('/api/printerjob', {
method: 'POST',
body: {
agentprinterId: Number(agentprinterId),
type,
data,
},
});
},
/**
* POST /api/printerjob/:id/cancel
* → { success: true, printerjob: PrinterJob }
*/
cancel(id) {
return http(`/api/printerjob/${id}/cancel`, {
method: 'POST',
});
},
/**
* DELETE /api/printerjob/:id
* → { success: true, deletedJob: PrinterJob }
*/
delete(id) {
return http(`/api/printerjob/${id}`, {
method: 'DELETE',
});
},
};

View File

@@ -28,6 +28,7 @@ import {
printer as printerApi, printer as printerApi,
printeragent as printeragentApi, printeragent as printeragentApi,
agentprinter as agentprinterApi, agentprinter as agentprinterApi,
printerjob as printerjobApi,
supplier as supplierApi, supplier as supplierApi,
unit as unitApi, unit as unitApi,
warehouse as warehouseApi, warehouse as warehouseApi,
@@ -876,6 +877,14 @@ const actions = {
}); });
}, },
async sendTestPrint(agentprinterId, options = {}) {
return printerjobApi.sendTestZpl(agentprinterId, options);
},
async createZplJob(payload) {
return printerjobApi.createZplJob(payload);
},
async loadLivePrinters() { async loadLivePrinters() {
const companyId = const companyId =
authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id; authAutomat.state.user?.selectedCompanyId ?? companyAutomat.state.current?.id;

View File

@@ -120,7 +120,14 @@ class AgentQrCode extends PureComponent {
} }
function renderPrinterDiagnostics(printer, live) { function renderPrinterDiagnostics(printer, live) {
if (!live) { const isOnline = printer?.online ?? (live ? !live.unreachable && live.status !== 'offline' : false);
const isOpen = printer?.open ?? (live?.errors?.headOpen || live?.headOpen || false);
const isPaused = printer?.pause ?? (live?.errors?.pause || live?.pause || false);
const isError = printer?.error ?? (live?.errors?.other || live?.error || false);
const isUnreachable = !isOnline || (live?.errors?.unreachable || false);
const labelLength = printer?.labelLength ?? live?.labelLength;
if (!isOnline) {
return ( return (
<Chip <Chip
label="Offline" label="Offline"
@@ -132,9 +139,8 @@ function renderPrinterDiagnostics(printer, live) {
); );
} }
const errors = live.errors || {};
const errorChips = []; const errorChips = [];
if (errors.headOpen) { if (isOpen) {
errorChips.push( errorChips.push(
<Chip <Chip
key="headOpen" key="headOpen"
@@ -145,7 +151,7 @@ function renderPrinterDiagnostics(printer, live) {
/>, />,
); );
} }
if (errors.pause) { if (isPaused) {
errorChips.push( errorChips.push(
<Chip <Chip
key="pause" key="pause"
@@ -156,7 +162,7 @@ function renderPrinterDiagnostics(printer, live) {
/>, />,
); );
} }
if (errors.unreachable) { if (isUnreachable) {
errorChips.push( errorChips.push(
<Chip <Chip
key="unreachable" key="unreachable"
@@ -167,7 +173,7 @@ function renderPrinterDiagnostics(printer, live) {
/>, />,
); );
} }
if (errors.other) { if (isError) {
errorChips.push( errorChips.push(
<Chip <Chip
key="other" key="other"
@@ -192,9 +198,9 @@ function renderPrinterDiagnostics(printer, live) {
variant="outlined" variant="outlined"
sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }} sx={{ fontSize: '0.7rem', height: 20, fontWeight: 600 }}
/> />
{live.labelLength != null ? ( {labelLength != null ? (
<Chip <Chip
label={`Etikett: ${live.labelLength} px`} label={`Etikett: ${labelLength} px`}
size="small" size="small"
variant="outlined" variant="outlined"
sx={{ fontSize: '0.7rem', height: 20 }} sx={{ fontSize: '0.7rem', height: 20 }}
@@ -227,6 +233,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
editingId: null, editingId: null,
editNameDraft: '', editNameDraft: '',
sortDrafts: {}, sortDrafts: {},
testingPrinterId: null,
}; };
this.handleRetry = this.handleRetry.bind(this); this.handleRetry = this.handleRetry.bind(this);
this.handleCreateAgent = this.handleCreateAgent.bind(this); this.handleCreateAgent = this.handleCreateAgent.bind(this);
@@ -241,6 +248,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
this.handleTogglePrinterEnabled = this.handleTogglePrinterEnabled.bind(this); this.handleTogglePrinterEnabled = this.handleTogglePrinterEnabled.bind(this);
this.handleMovePrinter = this.handleMovePrinter.bind(this); this.handleMovePrinter = this.handleMovePrinter.bind(this);
this.handleDeletePrinter = this.handleDeletePrinter.bind(this); this.handleDeletePrinter = this.handleDeletePrinter.bind(this);
this.handleTestPrint = this.handleTestPrint.bind(this);
} }
componentDidMount() { componentDidMount() {
@@ -467,6 +475,24 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
} }
} }
async handleTestPrint(printer) {
if (!printer?.id) return;
this.setState({ testingPrinterId: printer.id });
try {
await companyAutomat.actions.sendTestPrint(printer.id, {
model: printer.model || printer.type || 'Zebra',
});
enqueueSnackbar(
`Testdruck für ${printer.model || 'Drucker'} (#${printer.id}) an Drucker-Warteschlange gesendet.`,
{ variant: 'success' },
);
} catch (err) {
notifyError(err);
} finally {
this.setState({ testingPrinterId: null });
}
}
render() { render() {
const { const {
error, error,
@@ -479,6 +505,7 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
patchingAgentId, patchingAgentId,
editingId, editingId,
editNameDraft, editNameDraft,
testingPrinterId,
} = this.state; } = this.state;
if (error && printeragents.length === 0) { if (error && printeragents.length === 0) {
@@ -564,8 +591,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
const isEditing = editingId === agent.id; const isEditing = editingId === agent.id;
const isPaired = agent.pairedAt != null; const isPaired = agent.pairedAt != null;
const isEnabled = agent.enabled !== false; const isEnabled = agent.enabled !== false;
const clientState = agent.clientstate;
const isOnline = clientState === 'online';
const pairedDateText = formatDateTime(agent.pairedAt); const pairedDateText = formatDateTime(agent.pairedAt);
const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie'; const lastseenText = formatDateTime(agent.lastseenAt) ?? 'Nie';
const updatedText = formatDateTime(agent.updatedAt) ?? '—'; const updatedText = formatDateTime(agent.updatedAt) ?? '—';
@@ -700,17 +725,6 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
/> />
)} )}
{/* Live connection state */}
{clientState ? (
<Chip
label={isOnline ? 'Online' : 'Offline'}
size="small"
variant="outlined"
color={isOnline ? 'success' : 'default'}
sx={{ fontSize: '0.75rem', fontWeight: 600 }}
/>
) : null}
{/* Agent version */} {/* Agent version */}
{agent.agentversion ? ( {agent.agentversion ? (
<Tooltip title="Agent Software-Version"> <Tooltip title="Agent Software-Version">
@@ -931,6 +945,30 @@ export default class PrinterAgentSettingsPanel extends PureComponent {
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{/* Test print button */}
<Tooltip title="Test-Etikett an Drucker senden (ZPL)">
<span>
<Button
size="small"
variant="outlined"
color="primary"
startIcon={<PrintOutlinedIcon sx={{ fontSize: 15 }} />}
disabled={testingPrinterId === p.id}
onClick={() => void this.handleTestPrint(p)}
sx={{
textTransform: 'none',
fontSize: '0.75rem',
py: 0.25,
px: 1,
minHeight: 26,
fontWeight: 600,
}}
>
{testingPrinterId === p.id ? 'Druckt…' : 'Testdruck'}
</Button>
</span>
</Tooltip>
{/* Enabled toggle switch */} {/* Enabled toggle switch */}
<FormControlLabel <FormControlLabel
control={ control={