Manueller Auftrag: Neukundenzwang + Mails-Checkbox

- Neue E-Mail im Neuauftrag -> Pruefung; unbekannt => Stammdaten (Vor-/Nachname
  oder Firma + Adresse) werden erzwungen und beim Anlegen am Kundenkonto
  gespeichert (statt leerem Auto-Konto).
- Checkbox "Mails an Kunden senden" je manuellem Auftrag; orders.mails_senden
  (Default true). Mailer unterdrueckt Kunden-Mails bei mails_senden=false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 11:28:08 +02:00
parent 6a6b09d49d
commit ecd6da398c
4 changed files with 63 additions and 3 deletions

View File

@@ -170,7 +170,7 @@ export default (register, { services, getSchema, logger }) => {
const order = await new ItemsService('orders', { schema, accountability: null }).readOne(orderId, {
fields: [
'order_number', 'customer_email', 'entries_count', 'shipping_type',
'order_number', 'customer_email', 'entries_count', 'shipping_type', 'mails_senden',
'needs_envelope', 'envelope_labeling', 'envelope_format',
'net_total', 'vat_amount', 'gross_total', 'billing', 'shipping_address',
'customer.first_name', 'customer.last_name', 'customer.company',
@@ -178,6 +178,8 @@ export default (register, { services, getSchema, logger }) => {
],
});
if (!order?.customer_email) return;
// Manuell deaktivierbar: „Mails senden = nein" unterdrückt Kunden-Mails.
if (order.mails_senden === false) return;
// Datei-Produkte (Unterschriftenservice) erhalten KEINE Standard-Bestell-
// bestätigung – dafür geht eine eigene Vorlage aus dem Bestell-Endpunkt raus.
if (key === 'order_created' && order.product?.input_mode === 'datei') return;

View File

@@ -73,6 +73,10 @@ const NeuAuftrag = defineComponent({
const leereZeile = () => ({ free_text: '', placeholders: {} });
const neu = reactive({
product: '', format: '', font: 'tilda', orderName: '',
// Mails an den Kunden senden (Bestätigung/Status). Pro manuellem Auftrag abschaltbar.
mailsSenden: true,
// Kunden-Check: '', 'pruefe', 'vorhanden', 'neu'. Bei 'neu' werden Stammdaten erzwungen.
kundeStatus: '', kundeVorname: '', kundeNachname: '', kundeFirma: '', kundeStr: '', kundePlz: '', kundeOrt: '',
// Relative Schriftgröße in % (getrennt Brief/Kuvert); 100 = Standard.
fontSizeLetter: 100, fontSizeEnvelope: 100,
// Unterschrift auf dem Brief ([[signatur1]]): SVG-Inhalt + Feinjustierung.
@@ -89,6 +93,20 @@ const NeuAuftrag = defineComponent({
docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', lbDoc: null,
});
// Ist das gewählte Format eine Postkarte (Text links, Adresse rechts, ein SVG)?
// Kunden-Check: existiert die E-Mail? Bei „neu" werden Stammdaten erzwungen.
let kundeTimer = null;
function pruefeKunde() {
const mail = String(neu.customer_email || '').trim();
clearTimeout(kundeTimer);
if (!mail || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(mail)) { neu.kundeStatus = ''; return; }
neu.kundeStatus = 'pruefe';
kundeTimer = setTimeout(async () => {
try {
const r = await api.get('/users', { params: { filter: { email: { _eq: mail } }, limit: 1, fields: 'id' } });
neu.kundeStatus = (r.data?.data?.[0]) ? 'vorhanden' : 'neu';
} catch { neu.kundeStatus = ''; }
}, 350);
}
const istPostkarte = () => pkFormate.value.has(neu.format);
// Kuverts nur generieren, wenn angehakt (Beschriftung aus der Empfängeradresse).
// Postkarte: KEIN separater Umschlag – der Absender kommt über envelope_sender
@@ -398,6 +416,10 @@ const NeuAuftrag = defineComponent({
neu.msg = `✓ Auftrag ${nr} angelegt – Dokumente werden noch generiert. Status im Produktions-Board prüfen.`;
}
async function absenden() {
// Neukunde: Stammdaten erzwingen, bevor der Auftrag angelegt wird.
if (neu.kundeStatus === 'neu' && !((neu.kundeVorname.trim() && neu.kundeNachname.trim()) || neu.kundeFirma.trim())) {
neu.err = 'Neuer Kunde: bitte Vor- und Nachnamen oder eine Firma angeben.'; return;
}
// Ohne Kuvert & ohne eigene Platzhalter: N identische Schriftstücke (Blankozeilen).
// Sonst: die erfassten Empfänger – ohne Empfänger genau ein Schriftstück.
let payloadEntries;
@@ -420,6 +442,11 @@ const NeuAuftrag = defineComponent({
envelope_format: kv.envelope_format,
shipping_type: 'sammel',
customer_email: neu.customer_email.trim() || undefined,
mails_senden: neu.mailsSenden,
customer_vorname: neu.kundeVorname.trim() || undefined,
customer_nachname: neu.kundeNachname.trim() || undefined,
customer_company: neu.kundeFirma.trim() || undefined,
customer_address: (neu.kundeStr || neu.kundePlz || neu.kundeOrt) ? { street: neu.kundeStr.trim(), zip: neu.kundePlz.trim(), city: neu.kundeOrt.trim(), country: 'Deutschland' } : undefined,
text_template: neu.text, preview_session: neu.previewSession || undefined,
entries: payloadEntries,
});
@@ -435,6 +462,7 @@ const NeuAuftrag = defineComponent({
function zuruecksetzen() {
Object.assign(neu, {
product: produkte.value[0]?.key || 'briefe', format: neuFormate()[0]?.key || '', font: 'tilda', orderName: '',
mailsSenden: true, kundeStatus: '', kundeVorname: '', kundeNachname: '', kundeFirma: '', kundeStr: '', kundePlz: '', kundeOrt: '',
fontSizeLetter: 100, fontSizeEnvelope: 100,
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()],
@@ -697,9 +725,28 @@ const NeuAuftrag = defineComponent({
]),
h('div', { style: 'flex:1 1 280px;max-width:420px;' }, [
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; pruefeKunde(); } }),
neu.kundeStatus === 'vorhanden' ? h('div', { style: 'font-size:12px;color:var(--success,#2e7d32);margin-top:5px;font-weight:600;' }, '✓ Bestehender Kunde wird verknüpft') : null,
neu.kundeStatus === 'neu' ? h('div', { style: 'font-size:12px;color:var(--primary,#1e50a5);margin-top:5px;font-weight:600;' }, 'Neuer Kunde – bitte unten Stammdaten erfassen.') : null,
]),
]),
// Neukundenanlage erzwingen, sobald eine unbekannte E-Mail eingetragen ist.
neu.kundeStatus === 'neu' ? h('div', { style: 'margin-top:14px;border:1px solid var(--border-normal);border-radius:10px;padding:16px;background:var(--background-subdued,#f1f4f9);' }, [
h('div', { style: 'font-weight:600;margin-bottom:10px;' }, 'Neuen Kunden anlegen'),
h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;' }, [
h('div', {}, [label('Vorname'), h('input', { value: neu.kundeVorname, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundeVorname = e.target.value; } })]),
h('div', {}, [label('Nachname'), h('input', { value: neu.kundeNachname, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundeNachname = e.target.value; } })]),
h('div', {}, [label('Firma (optional)'), h('input', { value: neu.kundeFirma, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundeFirma = e.target.value; } })]),
h('div', {}, [label('Straße + Nr.'), h('input', { value: neu.kundeStr, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundeStr = e.target.value; } })]),
h('div', {}, [label('PLZ'), h('input', { value: neu.kundePlz, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundePlz = e.target.value; } })]),
h('div', {}, [label('Ort'), h('input', { value: neu.kundeOrt, style: WFELD + 'width:100%;', onInput: (e) => { neu.kundeOrt = e.target.value; } })]),
]),
]) : null,
// Mails an Kunden senden (Bestätigung/Status) – pro Auftrag abschaltbar.
h('label', { style: 'display:flex;align-items:center;gap:8px;margin-top:14px;font-size:14px;cursor:pointer;' }, [
h('input', { type: 'checkbox', checked: neu.mailsSenden, onChange: (e) => { neu.mailsSenden = e.target.checked; }, style: 'width:16px;height:16px;' }),
'Mails an Kunden senden (Bestätigung/Status)',
]),
]);
const aktionen = h('div', { class: 'sk-btnrow', style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [

View File

@@ -2053,7 +2053,16 @@ const handler = (router, { services, getSchema, logger }) => {
const role = (await new RolesService({ schema, accountability: null }).readByQuery({
filter: { name: { _eq: 'Kunde' } }, limit: 1, fields: ['id'],
}))?.[0];
customerId = await users.createOne({ email: kundeMail, password: zufallsPasswort(), role: role?.id ?? null, status: 'active' });
// Neukunde mit den im Formular erfassten Stammdaten anlegen.
const neuKunde = { email: kundeMail, password: zufallsPasswort(), role: role?.id ?? null, status: 'active',
first_name: clean(body.customer_vorname) || null,
last_name: clean(body.customer_nachname) || null,
company: clean(body.customer_company) || null,
person_type: body.customer_company ? 'unternehmen' : 'privat',
};
const adr = body.customer_address && typeof body.customer_address === 'object' ? body.customer_address : null;
if (adr && (adr.street || adr.zip || adr.city)) neuKunde.billing_address = adr;
customerId = await users.createOne(neuKunde);
customerEmail = kundeMail;
} catch (e) { logger.warn(`[skrift-orders] manual-order Kunde anlegen: ${e.message}`); customerEmail = kundeMail; }
}
@@ -2084,6 +2093,7 @@ const handler = (router, { services, getSchema, logger }) => {
const orderData = {
customer: customerId, customer_email: customerEmail,
order_name: clean(body.order_name) || null,
mails_senden: body.mails_senden !== false,
product: product.id, format: formatId,
person_type: body.person_type === 'unternehmen' ? 'unternehmen' : 'privat',
font: ['tilda', 'alva', 'ellie'].includes(body.font) ? body.font : 'tilda',