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:
@@ -253,6 +253,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.' }),
|
f('order_name', 'string', { interface: 'input', note: 'Optionaler Auftragsname (z. B. „EinDollarBrille Dankeskarten"). Wird zusätzlich zur Nummer angezeigt.' }),
|
||||||
|
f('mails_senden', 'boolean', { interface: 'boolean', default: true, note: 'Kunden-Mails (Bestätigung/Status) senden? Bei manuellen Aufträgen abschaltbar.' }),
|
||||||
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'),
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export default (register, { services, getSchema, logger }) => {
|
|||||||
|
|
||||||
const order = await new ItemsService('orders', { schema, accountability: null }).readOne(orderId, {
|
const order = await new ItemsService('orders', { schema, accountability: null }).readOne(orderId, {
|
||||||
fields: [
|
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',
|
'needs_envelope', 'envelope_labeling', 'envelope_format',
|
||||||
'net_total', 'vat_amount', 'gross_total', 'billing', 'shipping_address',
|
'net_total', 'vat_amount', 'gross_total', 'billing', 'shipping_address',
|
||||||
'customer.first_name', 'customer.last_name', 'customer.company',
|
'customer.first_name', 'customer.last_name', 'customer.company',
|
||||||
@@ -178,6 +178,8 @@ export default (register, { services, getSchema, logger }) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (!order?.customer_email) return;
|
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-
|
// Datei-Produkte (Unterschriftenservice) erhalten KEINE Standard-Bestell-
|
||||||
// bestätigung – dafür geht eine eigene Vorlage aus dem Bestell-Endpunkt raus.
|
// bestätigung – dafür geht eine eigene Vorlage aus dem Bestell-Endpunkt raus.
|
||||||
if (key === 'order_created' && order.product?.input_mode === 'datei') return;
|
if (key === 'order_created' && order.product?.input_mode === 'datei') return;
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ const NeuAuftrag = defineComponent({
|
|||||||
const leereZeile = () => ({ free_text: '', placeholders: {} });
|
const leereZeile = () => ({ free_text: '', placeholders: {} });
|
||||||
const neu = reactive({
|
const neu = reactive({
|
||||||
product: '', format: '', font: 'tilda', orderName: '',
|
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.
|
// 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.
|
||||||
@@ -89,6 +93,20 @@ const NeuAuftrag = defineComponent({
|
|||||||
docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', lbDoc: null,
|
docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', lbDoc: null,
|
||||||
});
|
});
|
||||||
// Ist das gewählte Format eine Postkarte (Text links, Adresse rechts, ein SVG)?
|
// 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);
|
const istPostkarte = () => pkFormate.value.has(neu.format);
|
||||||
// Kuverts nur generieren, wenn angehakt (Beschriftung aus der Empfängeradresse).
|
// Kuverts nur generieren, wenn angehakt (Beschriftung aus der Empfängeradresse).
|
||||||
// Postkarte: KEIN separater Umschlag – der Absender kommt über envelope_sender
|
// 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.`;
|
neu.msg = `✓ Auftrag ${nr} angelegt – Dokumente werden noch generiert. Status im Produktions-Board prüfen.`;
|
||||||
}
|
}
|
||||||
async function absenden() {
|
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).
|
// Ohne Kuvert & ohne eigene Platzhalter: N identische Schriftstücke (Blankozeilen).
|
||||||
// Sonst: die erfassten Empfänger – ohne Empfänger genau ein Schriftstück.
|
// Sonst: die erfassten Empfänger – ohne Empfänger genau ein Schriftstück.
|
||||||
let payloadEntries;
|
let payloadEntries;
|
||||||
@@ -420,6 +442,11 @@ const NeuAuftrag = defineComponent({
|
|||||||
envelope_format: kv.envelope_format,
|
envelope_format: kv.envelope_format,
|
||||||
shipping_type: 'sammel',
|
shipping_type: 'sammel',
|
||||||
customer_email: neu.customer_email.trim() || undefined,
|
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,
|
text_template: neu.text, preview_session: neu.previewSession || undefined,
|
||||||
entries: payloadEntries,
|
entries: payloadEntries,
|
||||||
});
|
});
|
||||||
@@ -435,6 +462,7 @@ 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', orderName: '',
|
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,
|
fontSizeLetter: 100, fontSizeEnvelope: 100,
|
||||||
sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, sigSpacing: '', 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()],
|
||||||
@@ -697,9 +725,28 @@ const NeuAuftrag = defineComponent({
|
|||||||
]),
|
]),
|
||||||
h('div', { style: 'flex:1 1 280px;max-width:420px;' }, [
|
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; 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;' }, [
|
const aktionen = h('div', { class: 'sk-btnrow', style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
||||||
|
|||||||
@@ -2053,7 +2053,16 @@ const handler = (router, { services, getSchema, logger }) => {
|
|||||||
const role = (await new RolesService({ schema, accountability: null }).readByQuery({
|
const role = (await new RolesService({ schema, accountability: null }).readByQuery({
|
||||||
filter: { name: { _eq: 'Kunde' } }, limit: 1, fields: ['id'],
|
filter: { name: { _eq: 'Kunde' } }, limit: 1, fields: ['id'],
|
||||||
}))?.[0];
|
}))?.[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;
|
customerEmail = kundeMail;
|
||||||
} catch (e) { logger.warn(`[skrift-orders] manual-order Kunde anlegen: ${e.message}`); 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 = {
|
const orderData = {
|
||||||
customer: customerId, customer_email: customerEmail,
|
customer: customerId, customer_email: customerEmail,
|
||||||
order_name: clean(body.order_name) || null,
|
order_name: clean(body.order_name) || null,
|
||||||
|
mails_senden: body.mails_senden !== false,
|
||||||
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',
|
||||||
|
|||||||
Reference in New Issue
Block a user