Files
Atlas/lib/lexoffice.ts
2025-11-12 20:21:32 +01:00

301 lines
8.2 KiB
TypeScript

interface LexOfficeContact {
id?: string;
organizationId: string;
version: number;
roles: {
customer?: {
number?: number;
};
};
company?: {
name: string;
taxNumber?: string;
vatRegistrationId?: string;
allowTaxFreeInvoices?: boolean;
contactPersons?: Array<{
salutation?: string;
firstName?: string;
lastName?: string;
primary?: boolean;
emailAddress?: string;
phoneNumber?: string;
}>;
};
person?: {
salutation?: string;
firstName?: string;
lastName?: string;
};
addresses?: {
billing?: Array<{
supplement?: string;
street?: string;
zip?: string;
city?: string;
countryCode?: string;
}>;
};
emailAddresses?: {
business?: Array<string>;
office?: Array<string>;
private?: Array<string>;
other?: Array<string>;
};
phoneNumbers?: {
business?: Array<string>;
office?: Array<string>;
mobile?: Array<string>;
private?: Array<string>;
fax?: Array<string>;
other?: Array<string>;
};
note?: string;
}
interface LexOfficeQuotation {
id?: string;
organizationId?: string;
createdDate?: string;
updatedDate?: string;
voucherNumber?: string;
voucherDate: string;
address: {
contactId?: string;
name?: string;
supplement?: string;
street?: string;
city?: string;
zip?: string;
countryCode: string;
};
lineItems: Array<{
type: 'custom' | 'text';
name?: string;
description?: string;
quantity?: number;
unitName?: string;
unitPrice?: {
currency: string;
netAmount: number;
taxRatePercentage: number;
};
}>;
totalPrice?: {
currency: string;
};
taxConditions?: {
taxType: 'net' | 'gross' | 'vatfree';
};
title?: string;
introduction?: string;
remark?: string;
}
interface LexOfficeInvoice extends Omit<LexOfficeQuotation, 'id'> {
voucherStatus?: string;
shippingConditions?: {
shippingDate?: string;
shippingType?: string;
};
paymentConditions?: {
paymentTermLabel?: string;
paymentTermDuration?: number;
};
}
export class LexOfficeService {
private apiKey: string;
private baseUrl = 'https://api.lexoffice.io/v1';
constructor() {
this.apiKey = process.env.LEXOFFICE_API_KEY || '';
if (!this.apiKey) {
console.warn('LexOffice API Key nicht konfiguriert');
}
}
private async request(method: string, endpoint: string, data?: any) {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: data ? JSON.stringify(data) : undefined,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LexOffice API Error: ${response.status} - ${errorText}`);
}
return await response.json();
} catch (error) {
console.error('LexOffice API Request failed:', error);
throw error;
}
}
async createContact(contact: Partial<LexOfficeContact>): Promise<{ id: string; resourceUri: string }> {
return this.request('POST', '/contacts', contact);
}
async getContact(contactId: string): Promise<LexOfficeContact> {
return this.request('GET', `/contacts/${contactId}`);
}
async createQuotation(quotation: LexOfficeQuotation): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; voucherNumber: string }> {
return this.request('POST', '/quotations', quotation);
}
async getQuotation(quotationId: string): Promise<LexOfficeQuotation> {
return this.request('GET', `/quotations/${quotationId}`);
}
async createInvoice(invoice: LexOfficeInvoice): Promise<{ id: string; resourceUri: string; createdDate: string; updatedDate: string; voucherNumber: string }> {
return this.request('POST', '/invoices', invoice);
}
async getInvoice(invoiceId: string): Promise<LexOfficeInvoice> {
return this.request('GET', `/invoices/${invoiceId}`);
}
async finalizeInvoice(invoiceId: string): Promise<{ id: string; resourceUri: string }> {
return this.request('PUT', `/invoices/${invoiceId}/pursue`, {
precedingSalesVoucherId: null,
preserveVoucherNumber: false,
});
}
async createContactFromBooking(booking: any): Promise<string> {
const contact: Partial<LexOfficeContact> = {
roles: {
customer: {},
},
addresses: {
billing: [{
street: booking.customerAddress,
zip: booking.customerZip,
city: booking.customerCity,
countryCode: 'DE',
}],
},
emailAddresses: {
business: [booking.customerEmail],
},
phoneNumbers: {
business: [booking.customerPhone],
},
};
if (booking.invoiceType === 'BUSINESS' && booking.companyName) {
contact.company = {
name: booking.companyName,
contactPersons: [{
firstName: booking.customerName.split(' ')[0],
lastName: booking.customerName.split(' ').slice(1).join(' '),
primary: true,
emailAddress: booking.customerEmail,
phoneNumber: booking.customerPhone,
}],
};
} else {
const [firstName, ...lastNameParts] = booking.customerName.split(' ');
contact.person = {
firstName: firstName,
lastName: lastNameParts.join(' '),
};
}
const result = await this.createContact(contact);
return result.id;
}
async createQuotationFromBooking(booking: any, contactId: string): Promise<string> {
const quotation: LexOfficeQuotation = {
voucherDate: new Date().toISOString().split('T')[0],
address: {
contactId: contactId,
countryCode: 'DE',
},
lineItems: [
{
type: 'custom',
name: 'Fotobox-Vermietung',
description: `Event am ${new Date(booking.eventDate).toLocaleDateString('de-DE')} in ${booking.eventCity}`,
quantity: 1,
unitName: 'Stück',
unitPrice: {
currency: 'EUR',
netAmount: booking.calculatedPrice || 0,
taxRatePercentage: 19,
},
},
],
totalPrice: {
currency: 'EUR',
},
taxConditions: {
taxType: 'net',
},
title: `Angebot Fotobox-Vermietung - ${booking.bookingNumber}`,
introduction: 'Vielen Dank für Ihre Anfrage! Gerne erstellen wir Ihnen folgendes Angebot:',
remark: 'Wir freuen uns auf Ihre Bestellung!',
};
const result = await this.createQuotation(quotation);
return result.id;
}
async createConfirmationFromBooking(booking: any, contactId: string): Promise<string> {
const invoice: LexOfficeInvoice = {
voucherDate: new Date().toISOString().split('T')[0],
address: {
contactId: contactId,
countryCode: 'DE',
},
lineItems: [
{
type: 'custom',
name: 'Fotobox-Vermietung',
description: `Event am ${new Date(booking.eventDate).toLocaleDateString('de-DE')} in ${booking.eventCity}\nAufbau: ${new Date(booking.setupTimeStart).toLocaleString('de-DE')}\nOrt: ${booking.eventAddress}, ${booking.eventZip} ${booking.eventCity}`,
quantity: 1,
unitName: 'Stück',
unitPrice: {
currency: 'EUR',
netAmount: booking.calculatedPrice || 0,
taxRatePercentage: 19,
},
},
],
totalPrice: {
currency: 'EUR',
},
taxConditions: {
taxType: 'net',
},
title: `Auftragsbestätigung - ${booking.bookingNumber}`,
introduction: 'Vielen Dank für Ihre Bestellung! Hiermit bestätigen wir Ihren Auftrag:',
remark: 'Wir freuen uns auf Ihre Veranstaltung!',
shippingConditions: {
shippingDate: new Date(booking.eventDate).toISOString().split('T')[0],
shippingType: 'service',
},
paymentConditions: {
paymentTermLabel: 'Zahlung bei Lieferung',
paymentTermDuration: 0,
},
};
const result = await this.createInvoice(invoice);
await this.finalizeInvoice(result.id);
return result.id;
}
}
export const lexofficeService = new LexOfficeService();