Auftragsname (Zusatz) + Signatur-Abstand-Feld im Neuauftrag

- orders.order_name (optionaler Auftragsname); im Neuauftrag setzbar, auf der
  Board-Karte als Eyebrow ueber dem Namen angezeigt; in manual-order gespeichert.
- Neuauftrag: Signatur-Feld "Abstand (mm)" (leer = Standard halber Zeilenabstand),
  via signature_offsets.spacing durchgereicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 10:36:12 +02:00
parent c6f73c2d34
commit a01da17221
4 changed files with 25 additions and 11 deletions

View File

@@ -252,6 +252,7 @@ const collections = [
{ collection: 'orders', meta: { icon: 'receipt_long', note: 'Auftrag.' }, fields: [ { collection: 'orders', meta: { icon: 'receipt_long', note: 'Auftrag.' }, fields: [
f('order_number', 'string', { interface: 'input', unique: true, required: true }), f('order_number', 'string', { interface: 'input', unique: true, required: true }),
f('order_name', 'string', { interface: 'input', note: 'Optionaler Auftragsname (z. B. „EinDollarBrille Dankeskarten"). Wird zusätzlich zur Nummer angezeigt.' }),
m2o('customer', 'directus_users', { note: 'Kunde = Login-Konto (Zuordnung per E-Mail).' }), m2o('customer', 'directus_users', { note: 'Kunde = Login-Konto (Zuordnung per E-Mail).' }),
f('customer_email', 'string', { interface: 'input', required: true, note: 'Zuordnungsschlüssel.' }), f('customer_email', 'string', { interface: 'input', required: true, note: 'Zuordnungsschlüssel.' }),
m2o('product', 'products'), m2o('product', 'products'),

View File

@@ -72,11 +72,11 @@ const NeuAuftrag = defineComponent({
const leereZeile = () => ({ free_text: '', placeholders: {} }); const leereZeile = () => ({ free_text: '', placeholders: {} });
const neu = reactive({ const neu = reactive({
product: '', format: '', font: 'tilda', product: '', format: '', font: 'tilda', orderName: '',
// Relative Schriftgröße in % (getrennt Brief/Kuvert); 100 = Standard. // Relative Schriftgröße in % (getrennt Brief/Kuvert); 100 = Standard.
fontSizeLetter: 100, fontSizeEnvelope: 100, fontSizeLetter: 100, fontSizeEnvelope: 100,
// Unterschrift auf dem Brief ([[signatur1]]): SVG-Inhalt + Feinjustierung. // Unterschrift auf dem Brief ([[signatur1]]): SVG-Inhalt + Feinjustierung.
sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, sigSpacing: '',
// Mehrseitiges Schriftstück (Auto-Fluss über mehrere A4-Seiten). // Mehrseitiges Schriftstück (Auto-Fluss über mehrere A4-Seiten).
mehrseitig: false, seitenInfo: '', mehrseitig: false, seitenInfo: '',
// Kuvert-Adresse je Empfänger: '5zeilen' (5 Zeilen) oder 'individuell' (einzeilig). // Kuvert-Adresse je Empfänger: '5zeilen' (5 Zeilen) oder 'individuell' (einzeilig).
@@ -311,7 +311,11 @@ const NeuAuftrag = defineComponent({
// bereits ein sigPayload() für den Unterschriften-Service (PDF) gibt. // bereits ein sigPayload() für den Unterschriften-Service (PDF) gibt.
const briefSigPayload = () => (neu.sigSvg const briefSigPayload = () => (neu.sigSvg
? { signatures: { signatur1: neu.sigSvg }, ? { signatures: { signatur1: neu.sigSvg },
signature_offsets: { signatur1: { x: Number(neu.sigX) || 0, y: Number(neu.sigY) || 0, scale: (Number(neu.sigScale) || 100) / 100 } } } signature_offsets: { signatur1: {
x: Number(neu.sigX) || 0, y: Number(neu.sigY) || 0, scale: (Number(neu.sigScale) || 100) / 100,
// Abstand in mm (leer = Backend-Default = halber Zeilenabstand).
...(String(neu.sigSpacing).trim() !== '' ? { spacing: Number(neu.sigSpacing) || 0 } : {}),
} } }
: {}); : {});
async function preview() { async function preview() {
@@ -407,7 +411,7 @@ const NeuAuftrag = defineComponent({
try { try {
const kv = kuvertPayload(); const kv = kuvertPayload();
const res = await api.post('/skrift-orders/manual-order', { const res = await api.post('/skrift-orders/manual-order', {
product: neu.product, format: neu.format, font: neu.font, product: neu.product, format: neu.format, font: neu.font, order_name: neu.orderName.trim() || undefined,
realistic: neu.realistic, realistic: neu.realistic,
font_scale_letter: neu.fontSizeLetter / 100, font_scale_envelope: neu.fontSizeEnvelope / 100, font_scale_letter: neu.fontSizeLetter / 100, font_scale_envelope: neu.fontSizeEnvelope / 100,
multi_page: neu.mehrseitig, multi_page: neu.mehrseitig,
@@ -430,9 +434,9 @@ const NeuAuftrag = defineComponent({
} }
function zuruecksetzen() { function zuruecksetzen() {
Object.assign(neu, { Object.assign(neu, {
product: produkte.value[0]?.key || 'briefe', format: neuFormate()[0]?.key || '', font: 'tilda', product: produkte.value[0]?.key || 'briefe', format: neuFormate()[0]?.key || '', font: 'tilda', orderName: '',
fontSizeLetter: 100, fontSizeEnvelope: 100, fontSizeLetter: 100, fontSizeEnvelope: 100,
sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, mehrseitig: false, seitenInfo: '', sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, sigSpacing: '', mehrseitig: false, seitenInfo: '',
adressModus: '5zeilen', kuverts: true, absenderAufKuvert: false, envelopeSender: '', envelopeSenderScale: 100, envFormat: '', realistic: true, customer_email: '', text: '', rows: [leereZeile()], adressModus: '5zeilen', kuverts: true, absenderAufKuvert: false, envelopeSender: '', envelopeSenderScale: 100, envFormat: '', realistic: true, customer_email: '', text: '', rows: [leereZeile()],
anzahl: 1, anzahl: 1,
importOpen: false, importText: '', importOpen: false, importText: '',
@@ -672,10 +676,11 @@ const NeuAuftrag = defineComponent({
h('span', { style: `color:${ACCENT};font-size:13px;font-weight:600;` }, `✓ ${neu.sigLabel} – als [[signatur1]] im Text`), h('span', { style: `color:${ACCENT};font-size:13px;font-weight:600;` }, `✓ ${neu.sigLabel} – als [[signatur1]] im Text`),
btn('Entfernen', sigEntfernen), btn('Entfernen', sigEntfernen),
]), ]),
h('div', { style: 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;' }, [ h('div', { style: 'display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;' }, [
h('div', {}, [label('Versatz X (mm)'), h('input', { type: 'number', step: 1, value: neu.sigX, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigX = e.target.value; neu.previewSession = null; } })]), h('div', {}, [label('Versatz X (mm)'), h('input', { type: 'number', step: 1, value: neu.sigX, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigX = e.target.value; neu.previewSession = null; } })]),
h('div', {}, [label('Versatz Y (mm)'), h('input', { type: 'number', step: 1, value: neu.sigY, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigY = e.target.value; neu.previewSession = null; } })]), h('div', {}, [label('Versatz Y (mm)'), h('input', { type: 'number', step: 1, value: neu.sigY, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigY = e.target.value; neu.previewSession = null; } })]),
h('div', {}, [label('Größe (%)'), h('input', { type: 'number', step: 5, value: neu.sigScale, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigScale = e.target.value; neu.previewSession = null; } })]), h('div', {}, [label('Größe (%)'), h('input', { type: 'number', step: 5, value: neu.sigScale, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigScale = e.target.value; neu.previewSession = null; } })]),
h('div', {}, [label('Abstand (mm)'), h('input', { type: 'number', step: 1, min: 0, placeholder: 'Standard', value: neu.sigSpacing, style: WFELD + 'width:100%;', onInput: (e) => { neu.sigSpacing = e.target.value; neu.previewSession = null; } })]),
]), ]),
subdued('Positiv X = nach rechts, positiv Y = nach unten. Nach dem Anpassen „Aktualisieren" in der Vorschau klicken.', 'margin-top:8px;'), subdued('Positiv X = nach rechts, positiv Y = nach unten. Nach dem Anpassen „Aktualisieren" in der Vorschau klicken.', 'margin-top:8px;'),
]) : null, ]) : null,
@@ -685,10 +690,16 @@ const NeuAuftrag = defineComponent({
// Bereich 4 – Kunde // Bereich 4 – Kunde
const b4 = h('div', { style: BEREICH }, [ const b4 = h('div', { style: BEREICH }, [
bereichKopf('👤 Kunde'), bereichKopf('👤 Kunde'),
h('div', { style: 'max-width:420px;' }, [ h('div', { style: 'display:flex;gap:16px;flex-wrap:wrap;' }, [
h('div', { style: 'flex:1 1 280px;max-width:420px;' }, [
label('Auftragsname (optional)'),
h('input', { type: 'text', value: neu.orderName, placeholder: 'z. B. EinDollarBrille Dankeskarten', style: WFELD + 'width:100%;', onInput: (e) => { neu.orderName = e.target.value; } }),
]),
h('div', { style: 'flex:1 1 280px;max-width:420px;' }, [
label('Kunde verknüpfen (E-Mail, optional)'), label('Kunde verknüpfen (E-Mail, optional)'),
h('input', { type: 'email', value: neu.customer_email, placeholder: 'kunde@example.de', style: WFELD + 'width:100%;', onInput: (e) => { neu.customer_email = e.target.value; } }), h('input', { type: 'email', value: neu.customer_email, placeholder: 'kunde@example.de', style: WFELD + 'width:100%;', onInput: (e) => { neu.customer_email = e.target.value; } }),
]), ]),
]),
]); ]);
const aktionen = h('div', { class: 'sk-btnrow', style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [ const aktionen = h('div', { class: 'sk-btnrow', style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [

View File

@@ -2083,6 +2083,7 @@ const handler = (router, { services, getSchema, logger }) => {
const orderData = { const orderData = {
customer: customerId, customer_email: customerEmail, customer: customerId, customer_email: customerEmail,
order_name: clean(body.order_name) || null,
product: product.id, format: formatId, product: product.id, format: formatId,
person_type: body.person_type === 'unternehmen' ? 'unternehmen' : 'privat', person_type: body.person_type === 'unternehmen' ? 'unternehmen' : 'privat',
font: ['tilda', 'alva', 'ellie'].includes(body.font) ? body.font : 'tilda', font: ['tilda', 'alva', 'ellie'].includes(body.font) ? body.font : 'tilda',

View File

@@ -252,7 +252,7 @@ const Board = defineComponent({
// ── Laden ── // ── Laden ──
// Basis-Felder ohne die evtl. noch nicht gebootstrapten Felder – diese // Basis-Felder ohne die evtl. noch nicht gebootstrapten Felder – diese
// werden separat versucht, damit ein fehlendes Feld nicht das ganze Board sperrt. // werden separat versucht, damit ein fehlendes Feld nicht das ganze Board sperrt.
const ORDER_FELDER = 'id,order_number,status,payment_status,production_status,needs_envelope,envelope_format,' const ORDER_FELDER = 'id,order_number,order_name,status,payment_status,production_status,needs_envelope,envelope_format,'
+ 'envelope_labeling,shipping_type,shipping_day,keep_contacts,entries_count,count_inland,count_ausland,brief_gedruckt,kuvert_gedruckt,' + 'envelope_labeling,shipping_type,shipping_day,keep_contacts,entries_count,count_inland,count_ausland,brief_gedruckt,kuvert_gedruckt,'
+ 'date_updated,date_created,format.key,format.name,product.name,product.type,product.input_mode,' + 'date_updated,date_created,format.key,format.name,product.name,product.type,product.input_mode,'
+ 'source,api_client.id,api_client.name,motif_mode,motif.name,shipping_address,' + 'source,api_client.id,api_client.name,motif_mode,motif.name,shipping_address,'
@@ -1430,6 +1430,7 @@ const Board = defineComponent({
: h('input', { type: 'checkbox', checked: istGewaehlt(o.id), onClick: (e) => toggleAuswahl(o.id, e), : h('input', { type: 'checkbox', checked: istGewaehlt(o.id), onClick: (e) => toggleAuswahl(o.id, e),
title: 'Für Sammel-Druck auswählen', style: 'width:18px;height:18px;margin-top:5px;cursor:pointer;flex-shrink:0;' }), title: 'Für Sammel-Druck auswählen', style: 'width:18px;height:18px;margin-top:5px;cursor:pointer;flex-shrink:0;' }),
h('div', { style: 'min-width:0;' }, [ h('div', { style: 'min-width:0;' }, [
o.order_name ? h('div', { style: 'font-size:12px;font-weight:500;color:var(--primary,#1e50a5);text-transform:uppercase;letter-spacing:.03em;margin-bottom:1px;' }, o.order_name) : null,
h('div', { style: 'font-size:19px;font-weight:500;letter-spacing:-.01em;line-height:1.25;' }, kundeName(o) || `#${o.order_number}`), h('div', { style: 'font-size:19px;font-weight:500;letter-spacing:-.01em;line-height:1.25;' }, kundeName(o) || `#${o.order_number}`),
h('div', { style: 'display:flex;align-items:baseline;gap:8px;margin-top:4px;flex-wrap:wrap;' }, [ h('div', { style: 'display:flex;align-items:baseline;gap:8px;margin-top:4px;flex-wrap:wrap;' }, [
h('span', { style: `font-size:22px;font-weight:500;color:${ACCENT};line-height:1;` }, String(o.entries_count || 0)), h('span', { style: `font-size:22px;font-weight:500;color:${ACCENT};line-height:1;` }, String(o.entries_count || 0)),