// ============================================================ // Mapping-Funktionen: Lexware Office <-> CRM // ============================================================ import { VoucherType } from '.prisma/crm-client'; import { LexwareContact, LexwareVoucherDetail, LexwareVoucherListItem, } from '../interfaces/lexware-api.interfaces'; // -------------------------------------------------------- // Typen fuer Multi-Value Email/Phone Arrays // -------------------------------------------------------- interface EmailEntry { email: string; type: 'WORK' | 'PERSONAL' | 'OTHER'; isPrimary: boolean; } interface PhoneEntry { phone: string; type: 'OFFICE' | 'MOBILE' | 'FAX'; isPrimary: boolean; } // -------------------------------------------------------- // Lexware Contact -> CRM Company // -------------------------------------------------------- export function lexwareContactToCompanyData(lc: LexwareContact): { name: string; email?: string; phone?: string; website?: string; street?: string; zip?: string; city?: string; country?: string; notes?: string; emails: EmailEntry[]; phones: PhoneEntry[]; } { const name = lc.company?.name || [lc.person?.firstName, lc.person?.lastName].filter(Boolean).join(' ') || 'Unbekannt'; const billingAddr = lc.addresses?.billing?.[0]; const email = getFirstEmail(lc); const phone = getFirstPhone(lc); return { name, email: email || undefined, phone: phone || undefined, street: billingAddr?.street || undefined, zip: billingAddr?.zip || undefined, city: billingAddr?.city || undefined, country: billingAddr?.countryCode || 'DE', notes: lc.note || undefined, emails: extractAllEmails(lc), phones: extractAllPhones(lc), }; } // -------------------------------------------------------- // Lexware Contact -> CRM Contact // -------------------------------------------------------- export function lexwareContactToContactData(lc: LexwareContact): { firstName?: string; lastName?: string; companyName?: string; position?: string; email?: string; phone?: string; mobile?: string; street?: string; zip?: string; city?: string; country?: string; notes?: string; type: 'PERSON' | 'ORGANIZATION'; emails: EmailEntry[]; phones: PhoneEntry[]; } { const isPerson = !!lc.person; const billingAddr = lc.addresses?.billing?.[0]; const email = getFirstEmail(lc); const phone = getFirstPhone(lc); const mobile = lc.phoneNumbers?.mobile?.[0]; const emails = extractAllEmails(lc); const phones = extractAllPhones(lc); if (isPerson) { return { type: 'PERSON', firstName: lc.person?.firstName || undefined, lastName: lc.person?.lastName || 'Unbekannt', companyName: lc.company?.name || undefined, position: lc.company?.contactPersons?.[0]?.primary ? 'Primaerkontakt' : undefined, email: email || undefined, phone: phone || undefined, mobile: mobile || undefined, street: billingAddr?.street || undefined, zip: billingAddr?.zip || undefined, city: billingAddr?.city || undefined, country: billingAddr?.countryCode || 'DE', notes: lc.note || undefined, emails, phones, }; } return { type: 'ORGANIZATION', firstName: lc.company?.contactPersons?.[0]?.firstName || undefined, lastName: lc.company?.contactPersons?.[0]?.lastName || lc.company?.name || 'Unbekannt', companyName: lc.company?.name || undefined, email: email || undefined, phone: phone || undefined, mobile: mobile || undefined, street: billingAddr?.street || undefined, zip: billingAddr?.zip || undefined, city: billingAddr?.city || undefined, country: billingAddr?.countryCode || 'DE', notes: lc.note || undefined, emails, phones, }; } // -------------------------------------------------------- // CRM Company -> Lexware Contact Body (fuer POST/PUT) // -------------------------------------------------------- export function companyToLexwareContactBody(company: { name: string; email?: string | null; phone?: string | null; street?: string | null; zip?: string | null; city?: string | null; country?: string | null; notes?: string | null; emails?: Array<{ email: string; type: string }>; phones?: Array<{ phone: string; type: string }>; }): Record { const body: Record = { version: 0, roles: { customer: {} }, company: { name: company.name }, }; if (company.street || company.zip || company.city) { body.addresses = { billing: [ { street: company.street || undefined, zip: company.zip || undefined, city: company.city || undefined, countryCode: company.country || 'DE', }, ], }; } // Multi-Value emails → Lexware categories if (company.emails && company.emails.length > 0) { const emailAddresses: Record = {}; for (const e of company.emails) { const category = emailTypeToLexwareCategory(e.type); if (!emailAddresses[category]) emailAddresses[category] = []; emailAddresses[category].push(e.email); } body.emailAddresses = emailAddresses; } else if (company.email) { body.emailAddresses = { business: [company.email] }; } // Multi-Value phones → Lexware categories if (company.phones && company.phones.length > 0) { const phoneNumbers: Record = {}; for (const p of company.phones) { const category = phoneTypeToLexwareCategory(p.type); if (!phoneNumbers[category]) phoneNumbers[category] = []; phoneNumbers[category].push(p.phone); } body.phoneNumbers = phoneNumbers; } else if (company.phone) { body.phoneNumbers = { business: [company.phone] }; } if (company.notes) { body.note = company.notes.substring(0, 1000); } return body; } // -------------------------------------------------------- // CRM Contact -> Lexware Contact Body (fuer POST/PUT) // -------------------------------------------------------- export function contactToLexwareContactBody(contact: { firstName?: string | null; lastName?: string | null; companyName?: string | null; email?: string | null; phone?: string | null; mobile?: string | null; street?: string | null; zip?: string | null; city?: string | null; country?: string | null; notes?: string | null; type: string; emails?: Array<{ email: string; type: string }>; phones?: Array<{ phone: string; type: string }>; }): Record { const body: Record = { version: 0, roles: { customer: {} }, }; if (contact.type === 'ORGANIZATION' && contact.companyName) { body.company = { name: contact.companyName }; } else { body.person = { firstName: contact.firstName || undefined, lastName: contact.lastName || 'Unbekannt', }; } if (contact.street || contact.zip || contact.city) { body.addresses = { billing: [ { street: contact.street || undefined, zip: contact.zip || undefined, city: contact.city || undefined, countryCode: contact.country || 'DE', }, ], }; } // Multi-Value emails → Lexware categories if (contact.emails && contact.emails.length > 0) { const emailAddresses: Record = {}; for (const e of contact.emails) { const category = emailTypeToLexwareCategory(e.type); if (!emailAddresses[category]) emailAddresses[category] = []; emailAddresses[category].push(e.email); } body.emailAddresses = emailAddresses; } else if (contact.email) { body.emailAddresses = { business: [contact.email] }; } // Multi-Value phones → Lexware categories if (contact.phones && contact.phones.length > 0) { const phoneNumbers: Record = {}; for (const p of contact.phones) { const category = phoneTypeToLexwareCategory(p.type); if (!phoneNumbers[category]) phoneNumbers[category] = []; phoneNumbers[category].push(p.phone); } body.phoneNumbers = phoneNumbers; } else { const phoneNumbers: Record = {}; if (contact.phone) phoneNumbers.business = [contact.phone]; if (contact.mobile) phoneNumbers.mobile = [contact.mobile]; if (Object.keys(phoneNumbers).length > 0) { body.phoneNumbers = phoneNumbers; } } if (contact.notes) { body.note = contact.notes.substring(0, 1000); } return body; } // -------------------------------------------------------- // Voucher Type Mapping // -------------------------------------------------------- export function voucherTypeFromLexware(lexwareType: string): VoucherType { const map: Record = { invoice: 'INVOICE', quotation: 'QUOTATION', orderconfirmation: 'ORDER_CONFIRMATION', creditnote: 'CREDIT_NOTE', }; return map[lexwareType.toLowerCase()] || 'INVOICE'; } export function voucherTypeToLexwareEndpoint(type: VoucherType): string { const map: Record = { INVOICE: 'invoices', QUOTATION: 'quotations', ORDER_CONFIRMATION: 'order-confirmations', CREDIT_NOTE: 'credit-notes', }; return map[type]; } // -------------------------------------------------------- // Deep Link Builder // -------------------------------------------------------- export function buildLexwareDeepLink( voucherType: VoucherType, voucherId: string, ): string { const typeMap: Record = { INVOICE: 'invoices', QUOTATION: 'quotations', ORDER_CONFIRMATION: 'order-confirmations', CREDIT_NOTE: 'credit-notes', }; const typePath = typeMap[voucherType]; return `https://app.lexware.de/permalink/${typePath}/view/${voucherId}`; } // -------------------------------------------------------- // Lexware Voucher Detail -> CRM Cache Daten // -------------------------------------------------------- export function voucherDetailToCacheData( detail: LexwareVoucherDetail, voucherType: VoucherType, lexwareContactId: string, ): { voucherType: VoucherType; voucherNumber: string | null; voucherDate: Date | null; voucherStatus: string | null; totalGrossAmount: number | null; totalNetAmount: number | null; totalTaxAmount: number | null; currency: string; title: string | null; lineItemsCount: number | null; lineItemsJson: string | null; lexwareContactId: string; lexwareDeepLink: string; } { const lineItems = detail.lineItems?.map((li) => ({ name: li.name, quantity: li.quantity, unitName: li.unitName, unitPrice: li.unitPrice?.grossAmount, amount: li.lineItemAmount, })); return { voucherType, voucherNumber: detail.voucherNumber || null, voucherDate: detail.voucherDate ? new Date(detail.voucherDate) : null, voucherStatus: detail.voucherStatus || null, totalGrossAmount: detail.totalPrice?.totalGrossAmount ?? null, totalNetAmount: detail.totalPrice?.totalNetAmount ?? null, totalTaxAmount: detail.totalPrice?.totalTaxAmount ?? null, currency: detail.totalPrice?.currency || 'EUR', title: detail.title || null, lineItemsCount: detail.lineItems?.length ?? null, lineItemsJson: lineItems ? JSON.stringify(lineItems) : null, lexwareContactId, lexwareDeepLink: buildLexwareDeepLink(voucherType, detail.id), }; } // -------------------------------------------------------- // Hilfsfunktionen // -------------------------------------------------------- function getFirstEmail(lc: LexwareContact): string | undefined { const emails = lc.emailAddresses; if (!emails) return undefined; return ( emails.business?.[0] || emails.office?.[0] || emails.private?.[0] || emails.other?.[0] ); } function getFirstPhone(lc: LexwareContact): string | undefined { const phones = lc.phoneNumbers; if (!phones) return undefined; return ( phones.business?.[0] || phones.office?.[0] || phones.private?.[0] || phones.other?.[0] ); } /** * Alle Lexware-Emails in Multi-Value Array konvertieren. * Mapping: business/office → WORK, private → PERSONAL, other → OTHER */ function extractAllEmails(lc: LexwareContact): EmailEntry[] { const result: EmailEntry[] = []; const emails = lc.emailAddresses; if (!emails) return result; let isFirst = true; const addEmails = (addresses: string[] | undefined, type: EmailEntry['type']) => { if (!addresses) return; for (const addr of addresses) { result.push({ email: addr, type, isPrimary: isFirst }); isFirst = false; } }; addEmails(emails.business, 'WORK'); addEmails(emails.office, 'WORK'); addEmails(emails.private, 'PERSONAL'); addEmails(emails.other, 'OTHER'); return result; } /** * Alle Lexware-Phones in Multi-Value Array konvertieren. * Mapping: business/office → OFFICE, mobile → MOBILE, fax → FAX, private/other → OFFICE */ function extractAllPhones(lc: LexwareContact): PhoneEntry[] { const result: PhoneEntry[] = []; const phones = lc.phoneNumbers; if (!phones) return result; let isFirst = true; const addPhones = (numbers: string[] | undefined, type: PhoneEntry['type']) => { if (!numbers) return; for (const num of numbers) { result.push({ phone: num, type, isPrimary: isFirst }); isFirst = false; } }; addPhones(phones.business, 'OFFICE'); addPhones(phones.office, 'OFFICE'); addPhones(phones.mobile, 'MOBILE'); addPhones(phones.fax, 'FAX'); addPhones(phones.private, 'OFFICE'); addPhones(phones.other, 'OFFICE'); return result; } /** * CRM EmailType → Lexware Kategorie */ function emailTypeToLexwareCategory(type: string): string { switch (type) { case 'WORK': return 'business'; case 'PERSONAL': return 'private'; case 'OTHER': return 'other'; default: return 'business'; } } /** * CRM PhoneType → Lexware Kategorie */ function phoneTypeToLexwareCategory(type: string): string { switch (type) { case 'OFFICE': return 'business'; case 'MOBILE': return 'mobile'; case 'FAX': return 'fax'; default: return 'business'; } } // -------------------------------------------------------- // Lexware VoucherList Item -> Voucher Type // -------------------------------------------------------- export function voucherListItemToType( item: LexwareVoucherListItem, ): VoucherType { return voucherTypeFromLexware(item.voucherType); }