Postkarten-Format (nur manueller Druck): Text links, Adresse rechts
- bootstrap: render_formats a6_postkarte + dinlang_postkarte (kind 'postkarte'), passende formats-Einträge (active=false → nicht in der App, nur manueller Druck), kind-Dropdown um 'postkarte' erweitert. - skrift-orders: Vorschau erkennt Postkarten (render_formats.kind) und baut EIN Dokument mit Text + Absender + Empfänger (Marker [[pk_abs]]/[[pk_adr]]). - neuauftrag: Postkarten-Formate erkannt; Absenderfeld direkt (unabhängig von Kuverts), Kuvert-Optionen ausgeblendet, Hinweis aufs Kartenlayout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -415,7 +415,7 @@ const collections = [
|
||||
{ collection: 'render_formats', meta: { icon: 'crop_free', note: 'Rendering-Formate (Seitengröße, Ränder, Zeilenlimit) – zentral fürs Backend.' }, fields: [
|
||||
f('key', 'string', { interface: 'input', unique: true, required: true, note: 'Technischer Schlüssel, z. B. a4, a6p, din_lang, oder ein eigener.' }),
|
||||
f('name', 'string', { interface: 'input', required: true }),
|
||||
dropdown('kind', ['brief', 'umschlag'], { default: 'brief' }),
|
||||
dropdown('kind', ['brief', 'umschlag', 'postkarte'], { default: 'brief' }),
|
||||
f('page_width_mm', 'float', { interface: 'input', required: true, note: 'Seitenbreite in mm.' }),
|
||||
f('page_height_mm', 'float', { interface: 'input', required: true, note: 'Seitenhöhe in mm.' }),
|
||||
f('margin_top_mm', 'float', { interface: 'input', default: 25 }),
|
||||
@@ -783,6 +783,9 @@ async function applySeed() {
|
||||
{ product: productByKey.briefe.id, key: 'a4', name: 'A4', active: true },
|
||||
{ product: productByKey.postkarten.id, key: 'a6_hoch', name: 'A6 Hochformat', active: true },
|
||||
{ product: productByKey.postkarten.id, key: 'a6_quer', name: 'A6 Querformat', active: true },
|
||||
// Postkarten-Layout (Text links, Adresse rechts) – NUR manueller Druck, nicht in der App.
|
||||
{ product: productByKey.postkarten.id, key: 'a6_postkarte', name: 'A6 Postkarte (Text + Adresse)', active: false, show_manual: true },
|
||||
{ product: productByKey.postkarten.id, key: 'dinlang_postkarte', name: 'DIN lang Postkarte (Text + Adresse)', active: false, show_manual: true },
|
||||
];
|
||||
for (const fmt of formats) await seedItem('formats', 'key', fmt);
|
||||
|
||||
@@ -934,6 +937,10 @@ async function applySeed() {
|
||||
{ key: 'a6l', name: 'A6 Querformat', kind: 'brief', page_width_mm: 148, page_height_mm: 105, margin_top_mm: 8, margin_left_mm: 8, margin_right_mm: 14, line_height_factor: 1.2, line_limit: 11, active: true },
|
||||
{ key: 'din_lang', name: 'DIN Lang Kuvert', kind: 'umschlag', page_width_mm: 220, page_height_mm: 110, margin_top_mm: 50, margin_left_mm: 10, line_height_factor: 1.2, active: true },
|
||||
{ key: 'c6', name: 'C6 Kuvert', kind: 'umschlag', page_width_mm: 162, page_height_mm: 114, margin_top_mm: 60, margin_left_mm: 10, line_height_factor: 1.2, active: true },
|
||||
// Postkarten (nur manueller Druck): Text links, Absender+Empfänger rechts,
|
||||
// Briefmarkenfeld oben rechts frei. Ein SVG je Karte (ein Plotter-Durchlauf).
|
||||
{ key: 'a6_postkarte', name: 'A6 Postkarte (Text + Adresse)', kind: 'postkarte', page_width_mm: 148, page_height_mm: 105, margin_top_mm: 8, margin_left_mm: 8, line_height_factor: 1.2, active: true },
|
||||
{ key: 'dinlang_postkarte', name: 'DIN lang Postkarte (Text + Adresse)', kind: 'postkarte', page_width_mm: 210, page_height_mm: 105, margin_top_mm: 8, margin_left_mm: 8, line_height_factor: 1.2, active: true },
|
||||
];
|
||||
// Upsert (nicht nur anlegen), damit falsche Alt-Maße korrigiert werden – z. B.
|
||||
// Postkarten (a6p/a6l), die sonst nicht aufs Kartenformat passen.
|
||||
|
||||
@@ -44,6 +44,8 @@ const NeuAuftrag = defineComponent({
|
||||
const api = useApi();
|
||||
const produkte = ref([]);
|
||||
const formate = ref([]);
|
||||
// Format-Schlüssel, die Postkarten sind (render_formats.kind === 'postkarte').
|
||||
const pkFormate = ref(new Set());
|
||||
const verbotenRe = ref(null);
|
||||
const musterTexte = ref({ text_du: '', text_sie: '' });
|
||||
// Frei anlegbare Mustertexte (Collection muster_texts). Fallback: Du/Sie aus muster_settings.
|
||||
@@ -82,25 +84,38 @@ const NeuAuftrag = defineComponent({
|
||||
importOpen: false, importText: '',
|
||||
docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', lbDoc: null,
|
||||
});
|
||||
// Ist das gewählte Format eine Postkarte (Text links, Adresse rechts, ein SVG)?
|
||||
const istPostkarte = () => pkFormate.value.has(neu.format);
|
||||
// Kuverts nur generieren, wenn angehakt (Beschriftung aus der Empfängeradresse).
|
||||
const kuvertPayload = () => (neu.kuverts
|
||||
? { needs_envelope: true, envelope_labeling: 'empfaenger', envelope_text: '',
|
||||
envelope_sender: neu.absenderAufKuvert ? (neu.envelopeSender || '').trim() : '',
|
||||
envelope_sender_scale: (Number(neu.envelopeSenderScale) || 100) / 100 }
|
||||
: { needs_envelope: false, envelope_labeling: 'keine', envelope_text: '', envelope_sender: '', envelope_sender_scale: 1 });
|
||||
// Postkarte: KEIN separater Umschlag – der Absender kommt über envelope_sender
|
||||
// aufs Kartenlayout (rechte Hälfte).
|
||||
const kuvertPayload = () => {
|
||||
if (istPostkarte()) {
|
||||
return { needs_envelope: false, envelope_labeling: 'keine', envelope_text: '',
|
||||
envelope_sender: (neu.envelopeSender || '').trim(),
|
||||
envelope_sender_scale: (Number(neu.envelopeSenderScale) || 100) / 100 };
|
||||
}
|
||||
return neu.kuverts
|
||||
? { needs_envelope: true, envelope_labeling: 'empfaenger', envelope_text: '',
|
||||
envelope_sender: neu.absenderAufKuvert ? (neu.envelopeSender || '').trim() : '',
|
||||
envelope_sender_scale: (Number(neu.envelopeSenderScale) || 100) / 100 }
|
||||
: { needs_envelope: false, envelope_labeling: 'keine', envelope_text: '', envelope_sender: '', envelope_sender_scale: 1 };
|
||||
};
|
||||
|
||||
async function laden() {
|
||||
try {
|
||||
const [p, f, ps, mt, mtx, sg] = await Promise.all([
|
||||
const [p, f, ps, mt, mtx, sg, rf] = await Promise.all([
|
||||
api.get('/items/products', { params: { limit: -1, filter: { active: { _eq: true }, type: { _in: ['letter', 'postcard'] } }, fields: 'key,name,type' } }),
|
||||
api.get('/items/formats', { params: { limit: -1, fields: '*,product.key' } }),
|
||||
api.get('/items/pricing_settings', { params: { fields: 'char_whitelist' } }).catch(() => ({ data: { data: {} } })),
|
||||
api.get('/items/muster_settings', { params: { fields: 'text_du,text_sie' } }).catch(() => ({ data: { data: {} } })),
|
||||
api.get('/items/muster_texts', { params: { limit: -1, filter: { active: { _eq: true } }, sort: 'sort', fields: 'id,label,text' } }).catch(() => ({ data: { data: [] } })),
|
||||
api.get('/items/signatures', { params: { limit: -1, sort: '-date_created', fields: 'id,label,file,marker_key,offset_x_mm,offset_y_mm,width_mm,customer.first_name,customer.last_name' } }).catch(() => ({ data: { data: [] } })),
|
||||
api.get('/items/render_formats', { params: { limit: -1, filter: { kind: { _eq: 'postkarte' } }, fields: 'key' } }).catch(() => ({ data: { data: [] } })),
|
||||
]);
|
||||
produkte.value = p.data?.data || [];
|
||||
formate.value = f.data?.data || [];
|
||||
pkFormate.value = new Set((rf.data?.data || []).map((r) => r.key));
|
||||
musterTexte.value = mt.data?.data || { text_du: '', text_sie: '' };
|
||||
musterListe.value = mtx.data?.data || [];
|
||||
signaturListe.value = sg.data?.data || [];
|
||||
@@ -286,7 +301,8 @@ const NeuAuftrag = defineComponent({
|
||||
async function preview() {
|
||||
// Vorschau ohne Empfänger erlaubt – das Backend nutzt dann eine Blankozeile.
|
||||
// Ohne Text nur erlaubt, wenn Kuverts generiert werden (reiner Kuvert-Druck).
|
||||
if (!neu.text.trim() && !neu.kuverts) { neu.err = 'Bitte Text eingeben – oder „Kuverts generieren" aktivieren.'; return; }
|
||||
// Postkarte braucht immer Text (linke Kartenhälfte).
|
||||
if (!neu.text.trim() && (istPostkarte() || !neu.kuverts)) { neu.err = 'Bitte Text eingeben' + (istPostkarte() ? '.' : ' – oder „Kuverts generieren" aktivieren.'); return; }
|
||||
neu.pLaedt = true; neu.err = ''; neu.msg = '';
|
||||
try {
|
||||
const kv = kuvertPayload();
|
||||
@@ -533,16 +549,19 @@ const NeuAuftrag = defineComponent({
|
||||
btn('+ Empfänger', addZeile),
|
||||
btn(neu.importOpen ? 'Import schließen' : '⇪ Aus Excel einfügen', () => { neu.importOpen = !neu.importOpen; }),
|
||||
]),
|
||||
h('label', { style: 'display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer;margin-top:12px;' }, [
|
||||
// Postkarte: Hinweis auf das Kartenlayout; kein separates Kuvert.
|
||||
istPostkarte() ? h('div', { style: 'font-size:12px;color:var(--foreground-subdued,#888);margin-top:12px;line-height:1.5;' },
|
||||
'Postkarte: Text auf der linken Hälfte, Absender + Empfänger rechts, Briefmarkenfeld oben rechts frei. Ein separates Kuvert wird nicht erzeugt.') : null,
|
||||
!istPostkarte() ? h('label', { style: 'display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer;margin-top:12px;' }, [
|
||||
h('input', { type: 'checkbox', checked: neu.kuverts, onChange: (e) => { neu.kuverts = e.target.checked; neu.previewSession = null; }, style: 'width:16px;height:16px;' }),
|
||||
'Kuverts generieren',
|
||||
]),
|
||||
neu.kuverts ? h('label', { style: 'display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer;margin-top:8px;' }, [
|
||||
]) : null,
|
||||
(!istPostkarte() && neu.kuverts) ? h('label', { style: 'display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer;margin-top:8px;' }, [
|
||||
h('input', { type: 'checkbox', checked: neu.absenderAufKuvert, onChange: (e) => { neu.absenderAufKuvert = e.target.checked; neu.previewSession = null; }, style: 'width:16px;height:16px;' }),
|
||||
'Absender aufs Kuvert (oben links)',
|
||||
]) : null,
|
||||
(neu.kuverts && neu.absenderAufKuvert) ? h('div', { style: 'margin-top:10px;' }, [
|
||||
label('Absender (max. 5 Zeilen)'),
|
||||
(istPostkarte() || (neu.kuverts && neu.absenderAufKuvert)) ? h('div', { style: 'margin-top:10px;' }, [
|
||||
label(istPostkarte() ? 'Absender (rechte Kartenhälfte, max. 5 Zeilen)' : 'Absender (max. 5 Zeilen)'),
|
||||
h('textarea', {
|
||||
value: neu.envelopeSender, rows: 5,
|
||||
placeholder: 'Max Mustermann\nMusterstraße 1\n12345 Musterstadt',
|
||||
@@ -556,7 +575,7 @@ const NeuAuftrag = defineComponent({
|
||||
onBlur: (e) => { const n = Math.round(Number(e.target.value)); neu.envelopeSenderScale = (Number.isFinite(n) && n) ? Math.max(50, Math.min(200, n)) : 100; } }),
|
||||
]),
|
||||
h('div', { style: 'font-size:11px;color:var(--foreground-subdued,#888);margin-top:4px;line-height:1.5;' },
|
||||
'Der Empfänger rutscht dann auf dem Kuvert automatisch nach unten rechts.'),
|
||||
istPostkarte() ? 'Empfänger steht darunter in der Anschriftzone (rechte Hälfte).' : 'Der Empfänger rutscht dann auf dem Kuvert automatisch nach unten rechts.'),
|
||||
]) : null,
|
||||
neu.importOpen ? h('div', { style: 'margin-top:12px;' }, [
|
||||
subdued('Aus Excel kopieren und einfügen – eine Zeile pro Empfänger, Spalten mit Tab/Semikolon/Komma. '
|
||||
|
||||
@@ -678,6 +678,10 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
const briefFormat = LETTER_FORMAT[body.format] || body.format || 'a4';
|
||||
// A4 → DIN Lang, A6 → C6
|
||||
const umschlagFormat = briefFormat === 'a4' ? 'din_lang' : 'c6';
|
||||
// Postkarte (Text links, Adresse rechts, ein SVG) – Erkennung über render_formats.kind.
|
||||
const istPk = (await svc('render_formats').readByQuery({
|
||||
filter: { key: { _eq: briefFormat } }, limit: 1, fields: ['kind'],
|
||||
}))?.[0]?.kind === 'postkarte';
|
||||
|
||||
const platzhalterVon = (e, i) => ({
|
||||
Anrede: clean(e.salutation || ''),
|
||||
@@ -717,6 +721,22 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
const nr = i + 1;
|
||||
// Abweichender Text je Schriftstück (#5) hat Vorrang; sonst der Standardtext.
|
||||
const briefText = (e.text && String(e.text).trim()) ? String(e.text) : String(body.text);
|
||||
|
||||
// Postkarte: EIN Dokument mit Text + Absender + Empfänger (Marker-getrennt).
|
||||
if (istPk) {
|
||||
const sender = clean(String(body.envelope_sender || '')).trim();
|
||||
const recip = adressBlock(e);
|
||||
let combined = String(clean(briefText) || '');
|
||||
if (sender) combined += `[[pk_abs]]${sender}`;
|
||||
if (recip) combined += `[[pk_adr]]${recip}`;
|
||||
letters.push({
|
||||
index: nr, type: 'postcard', text: combined, format: briefFormat, font,
|
||||
realisticHandwriting: realistisch, placeholders: platzhalterVon(e, i),
|
||||
fontScale: fsLetter, senderScale: Number(body.envelope_sender_scale) || 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Kein Schriftstück, wenn kein Text da ist (reiner Kuvert-Druck).
|
||||
if (String(briefText).trim()) {
|
||||
letters.push({
|
||||
|
||||
Reference in New Issue
Block a user