import { prisma } from "@/lib/prisma"; import { MAX_PAGE_SIZE } from "@/lib/erp-export"; /** * Nur lesender Export fuer das CRM in ERPNext (collab.vycept.com): * * - /api/erp/kunden: Kundenkonten des Shops. ERPNext fuehrt sie als * Privatkunden (Kundengruppe "Privat") mit Kontakt und Adresse. * - /api/erp/anfragen: Firmenanfragen von der Geschaeftskunden-Seite. * ERPNext fuehrt die Firma als B2B-Kunden (Kundengruppe "Gewerblich") mit * Ansprechpartner und je Anfrage eine Chance (Opportunity). * * ERPNext holt bei jedem Lauf die VOLLSTAENDIGE Liste (seitenweise ueber den * Cursor "nach"). Nur so erkennt es auch geloeschte Konten: ein Kunde, der im * Shop sein Konto geloescht hat und keine Rechnung hat, wird dort ebenfalls * entfernt (Art. 17 DSGVO); mit Rechnung bleibt er wegen der * Aufbewahrungspflicht stehen. * * Bewusst nicht im Export: Passwort-Hash, 2FA-Daten, Sitzungen, Wunschliste, * Bewertungen. Admin-Konten sind keine Kundschaft und fehlen ebenfalls. * Zugang wie beim Rechnungsexport nur mit ERP_EXPORT_TOKEN. */ /** `nach` (letzte id der vorigen Seite, leer = von vorn) und `limit` (1 bis MAX_PAGE_SIZE). */ export function parseErpCursor(params: URLSearchParams): { after: string | null; limit: number } | null { const after = params.get("nach")?.trim() || null; const limit = Number(params.get("limit") ?? MAX_PAGE_SIZE); if (!Number.isInteger(limit) || limit < 1 || limit > MAX_PAGE_SIZE) return null; if (after !== null && !/^[a-z0-9]{1,64}$/i.test(after)) return null; return { after, limit }; } function page(rows: T[], limit: number): string | null { return rows.length === limit ? rows[rows.length - 1].id : null; } export async function listErpCustomers(after: string | null, limit: number) { const customers = await prisma.customer.findMany({ where: { role: "KUNDE", ...(after ? { id: { gt: after } } : {}) }, orderBy: { id: "asc" }, take: limit, select: { id: true, email: true, firstName: true, lastName: true, phone: true, createdAt: true, emailVerifiedAt: true, blockedAt: true, // Standardadresse, sonst die zuletzt geaenderte; Packstationen sind // keine Anschrift fuer das CRM. addresses: { where: { packstationNumber: null }, orderBy: [{ isDefault: "desc" }, { updatedAt: "desc" }], take: 1, select: { street: true, zip: true, city: true, country: true }, }, }, }); return { customers: customers.map((customer) => ({ shopCustomerId: customer.id, email: customer.email, firstName: customer.firstName, lastName: customer.lastName, phone: customer.phone, createdAt: customer.createdAt.toISOString(), emailVerified: customer.emailVerifiedAt !== null, blocked: customer.blockedAt !== null, address: customer.addresses[0] ?? null, })), next: page(customers, limit), }; } export type ErpCustomer = Awaited>["customers"][number]; export async function listErpInquiries(after: string | null, limit: number) { const inquiries = await prisma.businessInquiry.findMany({ where: after ? { id: { gt: after } } : {}, orderBy: { id: "asc" }, take: limit, }); return { inquiries: inquiries.map((inquiry) => ({ inquiryId: inquiry.id, companyName: inquiry.companyName, contactPerson: inquiry.contactPerson, email: inquiry.email, phone: inquiry.phone, desiredQuantity: inquiry.desiredQuantity, desiredDeliveryDate: inquiry.desiredDeliveryDate?.toISOString() ?? null, package: inquiry.package, message: inquiry.message, status: inquiry.status, createdAt: inquiry.createdAt.toISOString(), updatedAt: inquiry.updatedAt.toISOString(), })), next: page(inquiries, limit), }; } export type ErpInquiry = Awaited>["inquiries"][number];