feat: Equipment-System, Buchungsbearbeitung, Kundenadresse, LexOffice-Fix
- Vintage Modell hinzugefuegt - Equipment Multi-Select (Neue Buchung + Bearbeitung) - Kundenadresse in Formularen - Bearbeiten-Seite fuer Buchungen - Abbau-Zeiten in Formularen und Uebersicht - Vertrag PDF nur bei Privatkunden - LexOffice Kontakt-Erstellung Fix (BUSINESS) - Zurueck-Pfeil auf Touren-Seite
This commit is contained in:
73
scripts/check-email-sync.ts
Normal file
73
scripts/check-email-sync.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function checkEmailSync() {
|
||||
console.log('🔍 Prüfe E-Mail-Sync Status...\n');
|
||||
|
||||
const luebeck = await prisma.location.findUnique({
|
||||
where: { slug: 'luebeck' },
|
||||
select: {
|
||||
name: true,
|
||||
emailSyncEnabled: true,
|
||||
lastEmailSync: true,
|
||||
imapHost: true,
|
||||
imapUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!luebeck) {
|
||||
console.log('❌ Lübeck Location nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📍 Lübeck:');
|
||||
console.log(` E-Mail-Sync: ${luebeck.emailSyncEnabled ? '✅ Aktiviert' : '❌ Deaktiviert'}`);
|
||||
console.log(` IMAP konfiguriert: ${luebeck.imapHost ? '✅ Ja' : '❌ Nein'}`);
|
||||
console.log(` Letzter Sync: ${luebeck.lastEmailSync ? new Date(luebeck.lastEmailSync).toLocaleString('de-DE') : 'Noch nie'}`);
|
||||
|
||||
if (!luebeck.emailSyncEnabled) {
|
||||
console.log('\n⚠️ E-Mail-Sync ist deaktiviert!');
|
||||
console.log(' Buchungen werden NICHT automatisch erfasst.');
|
||||
console.log(' → Entweder manuell im Dashboard anlegen');
|
||||
console.log(' → Oder E-Mail-Sync aktivieren\n');
|
||||
} else {
|
||||
console.log('\n✅ E-Mail-Sync ist aktiviert!');
|
||||
console.log(' → E-Mails werden automatisch abgerufen (Cron-Job oder manuell)');
|
||||
console.log(' → Buchungen erscheinen im Dashboard\n');
|
||||
}
|
||||
|
||||
// Prüfe ob es neue Buchungen gibt
|
||||
const recentBookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
locationId: luebeck ? undefined : undefined,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
bookingNumber: true,
|
||||
customerName: true,
|
||||
createdAt: true,
|
||||
location: {
|
||||
select: { name: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (recentBookings.length > 0) {
|
||||
console.log('📊 Letzte 5 Buchungen:');
|
||||
recentBookings.forEach((booking, idx) => {
|
||||
console.log(` ${idx + 1}. ${booking.bookingNumber} - ${booking.customerName} (${booking.location.name})`);
|
||||
console.log(` Erstellt: ${new Date(booking.createdAt).toLocaleString('de-DE')}`);
|
||||
});
|
||||
} else {
|
||||
console.log('📊 Keine Buchungen gefunden.');
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
checkEmailSync();
|
||||
31
scripts/check-equipment.ts
Normal file
31
scripts/check-equipment.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { prisma } from '../lib/prisma';
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Prüfe Equipment-Einträge...\n');
|
||||
|
||||
const count = await prisma.equipment.count();
|
||||
console.log(`📊 Anzahl Equipment-Einträge: ${count}`);
|
||||
|
||||
if (count > 0) {
|
||||
const equipment = await prisma.equipment.findMany();
|
||||
console.log('\n📦 Vorhandene Equipment:');
|
||||
console.table(equipment.map(e => ({
|
||||
id: e.id.slice(0, 8) + '...',
|
||||
name: e.name,
|
||||
price: e.price,
|
||||
lexofficeId: e.lexofficeArticleId?.slice(0, 8) + '...' || 'null',
|
||||
})));
|
||||
} else {
|
||||
console.log('\n⚠️ Keine Equipment-Einträge in der Datenbank!');
|
||||
console.log('ℹ️ Du musst zuerst Equipment-Artikel anlegen.');
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
56
scripts/check-quotation-status.ts
Normal file
56
scripts/check-quotation-status.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { prisma } from '../lib/prisma';
|
||||
import { lexofficeService } from '../lib/lexoffice';
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Prüfe LexOffice Quotation Status...\n');
|
||||
|
||||
// Hole die Test-Buchung
|
||||
const booking = await prisma.booking.findFirst({
|
||||
where: { bookingNumber: 'STM-2511-9237' },
|
||||
select: {
|
||||
id: true,
|
||||
bookingNumber: true,
|
||||
lexofficeOfferId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking) {
|
||||
console.log('❌ Buchung nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📋 Buchung:', booking.bookingNumber);
|
||||
console.log('🆔 LexOffice Offer ID:', booking.lexofficeOfferId);
|
||||
|
||||
if (!booking.lexofficeOfferId) {
|
||||
console.log('❌ Keine LexOffice Angebots-ID vorhanden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('\n🔍 Lade Quotation Details von LexOffice...');
|
||||
const quotation = await lexofficeService.getQuotation(booking.lexofficeOfferId);
|
||||
|
||||
console.log('\n📊 Quotation Details:');
|
||||
console.log(' Voucher Number:', quotation.voucherNumber);
|
||||
console.log(' Created:', quotation.createdDate);
|
||||
console.log(' Updated:', quotation.updatedDate);
|
||||
console.log('\n Full Response:', JSON.stringify(quotation, null, 2));
|
||||
|
||||
console.log('\n📄 Versuche PDF Download...');
|
||||
const pdf = await lexofficeService.getQuotationPDF(booking.lexofficeOfferId);
|
||||
console.log('✅ PDF erfolgreich heruntergeladen! Größe:', pdf.length, 'bytes');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('\n❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
40
scripts/clear-lexoffice-article-ids.ts
Normal file
40
scripts/clear-lexoffice-article-ids.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🧹 Entferne LexOffice-Artikel-IDs...');
|
||||
|
||||
const result = await prisma.priceConfig.updateMany({
|
||||
data: {
|
||||
lexofficeArticleId: null,
|
||||
lexofficeArticleIdWithFlat: null,
|
||||
lexofficeKmFlatArticleId: null,
|
||||
lexofficeKmExtraArticleId: null,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ ${result.count} PriceConfigs aktualisiert`);
|
||||
|
||||
const configs = await prisma.priceConfig.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
model: true,
|
||||
basePrice: true,
|
||||
lexofficeArticleId: true,
|
||||
lexofficeArticleIdWithFlat: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('\n📊 Aktuelle PriceConfigs:');
|
||||
console.table(configs);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
77
scripts/configure-luebeck-km.ts
Normal file
77
scripts/configure-luebeck-km.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function configureLuebeckLocation() {
|
||||
console.log('🔧 Konfiguriere Lübeck Location & Kilometerpauschalen...\n');
|
||||
|
||||
try {
|
||||
const location = await prisma.location.findUnique({
|
||||
where: { slug: 'luebeck' },
|
||||
});
|
||||
|
||||
if (!location) {
|
||||
console.error('❌ Lübeck Location nicht gefunden!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await prisma.location.update({
|
||||
where: { id: location.id },
|
||||
data: {
|
||||
warehouseAddress: 'Wahmstraße 83',
|
||||
warehouseZip: '23552',
|
||||
warehouseCity: 'Lübeck',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('✅ Lager-Adresse gesetzt: Wahmstraße 83, 23552 Lübeck\n');
|
||||
|
||||
const models = ['VINTAGE_SMILE', 'VINTAGE_PHOTOS', 'NOSTALGIE', 'MAGIC_MIRROR'];
|
||||
|
||||
for (const model of models) {
|
||||
const existing = await prisma.priceConfig.findUnique({
|
||||
where: {
|
||||
locationId_model: {
|
||||
locationId: location.id,
|
||||
model: model as any,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
await prisma.priceConfig.update({
|
||||
where: {
|
||||
locationId_model: {
|
||||
locationId: location.id,
|
||||
model: model as any,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
kmFlatRate: 60.0,
|
||||
kmFlatRateUpTo: 15,
|
||||
pricePerKm: 0.40,
|
||||
kmMultiplier: 4,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ ${model}: Kilometerpauschale aktualisiert`);
|
||||
} else {
|
||||
console.log(`⚠️ ${model}: Keine PriceConfig gefunden, überspringe...`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n🎉 Lübeck Location erfolgreich konfiguriert!');
|
||||
console.log('\nKonfiguration:');
|
||||
console.log(' 📍 Lager: Wahmstraße 83, 23552 Lübeck');
|
||||
console.log(' 💰 Pauschale bis 15km: 60,00€ brutto');
|
||||
console.log(' 💰 Darüber: 0,40€ netto/km × 4 Strecken');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
configureLuebeckLocation();
|
||||
35
scripts/finalize-existing-quotation.ts
Normal file
35
scripts/finalize-existing-quotation.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { prisma } from '../lib/prisma';
|
||||
import { lexofficeService } from '../lib/lexoffice';
|
||||
|
||||
async function main() {
|
||||
console.log('🔄 Finalisiere bestehende Quotation...\n');
|
||||
|
||||
const quotationId = 'c66b3347-4411-449c-897f-e0d84cb42601';
|
||||
|
||||
try {
|
||||
console.log('📤 Rufe PUT /quotations/{id}/pursue auf...');
|
||||
const result = await lexofficeService.finalizeQuotation(quotationId);
|
||||
console.log('✅ Erfolgreich!', result);
|
||||
|
||||
console.log('\n🔍 Prüfe neuen Status...');
|
||||
const quotation = await lexofficeService.getQuotation(quotationId);
|
||||
console.log('Status:', quotation.voucherStatus);
|
||||
|
||||
console.log('\n📄 Versuche PDF Download...');
|
||||
const pdf = await lexofficeService.getQuotationPDF(quotationId);
|
||||
console.log('✅ PDF erfolgreich! Größe:', pdf.length, 'bytes');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
console.error('Details:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
89
scripts/list-lexoffice-articles.js
Normal file
89
scripts/list-lexoffice-articles.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function loadEnv() {
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
const envContent = fs.readFileSync(envPath, 'utf-8');
|
||||
const env = {};
|
||||
|
||||
envContent.split('\n').forEach(line => {
|
||||
const match = line.match(/^([^=:#]+)=(.*)$/);
|
||||
if (match) {
|
||||
const key = match[1].trim();
|
||||
let value = match[2].trim();
|
||||
|
||||
if (value.startsWith('"') && value.endsWith('"')) {
|
||||
value = value.slice(1, -1);
|
||||
} else if (value.startsWith("'") && value.endsWith("'")) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
env[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async function listLexOfficeArticles() {
|
||||
console.log('🔍 Lade LexOffice Artikel...\n');
|
||||
|
||||
const env = loadEnv();
|
||||
const apiKey = env.LEXOFFICE_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('❌ LEXOFFICE_API_KEY nicht in .env gefunden!');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.lexoffice.io/v1/articles?page=0&size=100', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`LexOffice API Error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.content || data.content.length === 0) {
|
||||
console.log('⚠️ Keine Artikel gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Gefunden: ${data.content.length} Artikel\n`);
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
data.content.forEach((article, index) => {
|
||||
console.log(`\n${index + 1}. ${article.title || article.articleNumber || 'Unbekannt'}`);
|
||||
console.log(` ID: ${article.id}`);
|
||||
console.log(` Artikelnummer: ${article.articleNumber || 'N/A'}`);
|
||||
console.log(` Typ: ${article.type || 'N/A'}`);
|
||||
|
||||
if (article.price) {
|
||||
const netAmount = article.price.netAmount || 0;
|
||||
console.log(` Preis: ${netAmount.toFixed(2)}€ netto`);
|
||||
}
|
||||
|
||||
if (article.description) {
|
||||
const desc = article.description.substring(0, 60);
|
||||
console.log(` Beschreibung: ${desc}${article.description.length > 60 ? '...' : ''}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n' + '─'.repeat(80));
|
||||
console.log('\n💡 Kopieren Sie die IDs für Ihr Produkt-Mapping!');
|
||||
console.log('📝 Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
listLexOfficeArticles();
|
||||
53
scripts/list-lexoffice-articles.ts
Normal file
53
scripts/list-lexoffice-articles.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
|
||||
async function listLexOfficeArticles() {
|
||||
console.log('🔍 Lade LexOffice Artikel...\n');
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.lexoffice.io/v1/articles?page=0&size=100', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.LEXOFFICE_API_KEY}`,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`LexOffice API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.content || data.content.length === 0) {
|
||||
console.log('⚠️ Keine Artikel gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Gefunden: ${data.content.length} Artikel\n`);
|
||||
console.log('─'.repeat(80));
|
||||
|
||||
data.content.forEach((article: any, index: number) => {
|
||||
console.log(`\n${index + 1}. ${article.title || article.articleNumber || 'Unbekannt'}`);
|
||||
console.log(` ID: ${article.id}`);
|
||||
console.log(` Artikelnummer: ${article.articleNumber || 'N/A'}`);
|
||||
console.log(` Typ: ${article.type || 'N/A'}`);
|
||||
|
||||
if (article.price) {
|
||||
console.log(` Preis: ${article.price.netAmount || 0}€ netto`);
|
||||
}
|
||||
|
||||
if (article.description) {
|
||||
console.log(` Beschreibung: ${article.description.substring(0, 60)}...`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n' + '─'.repeat(80));
|
||||
console.log('\n💡 Kopieren Sie die IDs für Ihr Produkt-Mapping!');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
listLexOfficeArticles();
|
||||
69
scripts/manual-email-sync.ts
Normal file
69
scripts/manual-email-sync.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { emailSyncService } from '../lib/email-sync';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function manualEmailSync() {
|
||||
console.log('🔄 Manueller E-Mail-Sync gestartet...\n');
|
||||
|
||||
try {
|
||||
const locations = await prisma.location.findMany({
|
||||
where: { emailSyncEnabled: true },
|
||||
select: { id: true, name: true, slug: true },
|
||||
});
|
||||
|
||||
if (locations.length === 0) {
|
||||
console.log('⚠️ Keine Locations mit aktiviertem E-Mail-Sync gefunden.\n');
|
||||
console.log('💡 Bitte aktivieren Sie E-Mail-Sync in den Location-Einstellungen.\n');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📍 Gefunden: ${locations.length} Location(s) mit E-Mail-Sync\n`);
|
||||
|
||||
let totalNewEmails = 0;
|
||||
let totalNewBookings = 0;
|
||||
const results = [];
|
||||
|
||||
for (const location of locations) {
|
||||
console.log(`🔄 ${location.name} (${location.slug})...`);
|
||||
|
||||
const result = await emailSyncService.syncLocationEmails(location.id);
|
||||
|
||||
if (result.success) {
|
||||
console.log(` ✅ ${result.newEmails} neue E-Mails`);
|
||||
console.log(` ✅ ${result.newBookings} neue Buchungen\n`);
|
||||
totalNewEmails += result.newEmails;
|
||||
totalNewBookings += result.newBookings;
|
||||
} else {
|
||||
console.log(` ❌ Fehler:`);
|
||||
result.errors.forEach(err => console.log(` - ${err}`));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
results.push({
|
||||
location: location.name,
|
||||
...result,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('─'.repeat(60));
|
||||
console.log('📊 Zusammenfassung:');
|
||||
console.log(` Locations: ${locations.length}`);
|
||||
console.log(` Neue E-Mails: ${totalNewEmails}`);
|
||||
console.log(` Neue Buchungen: ${totalNewBookings}`);
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
if (totalNewBookings > 0) {
|
||||
console.log('\n✅ Neue Buchungen im Dashboard verfügbar!');
|
||||
console.log(' → http://localhost:3000/dashboard/bookings\n');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler beim E-Mail-Sync:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
manualEmailSync();
|
||||
42
scripts/reset-lexoffice-ids.ts
Normal file
42
scripts/reset-lexoffice-ids.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { prisma } from '../lib/prisma';
|
||||
|
||||
async function main() {
|
||||
console.log('🗑️ Lösche alte LexOffice IDs für erneuten Test...\n');
|
||||
|
||||
const booking = await prisma.booking.findFirst({
|
||||
where: { bookingNumber: 'STM-2511-9237' },
|
||||
});
|
||||
|
||||
if (!booking) {
|
||||
console.log('❌ Buchung nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📋 Buchung:', booking.bookingNumber);
|
||||
console.log('🆔 Aktuelle LexOffice Offer ID:', booking.lexofficeOfferId);
|
||||
|
||||
if (booking.lexofficeOfferId) {
|
||||
await prisma.booking.update({
|
||||
where: { id: booking.id },
|
||||
data: {
|
||||
lexofficeOfferId: null,
|
||||
lexofficeContactId: null,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('\n✅ LexOffice IDs gelöscht!');
|
||||
console.log('ℹ️ Der "Automation starten" Button sollte jetzt wieder erscheinen.');
|
||||
console.log('⚠️ WICHTIG: Lösche die alte Quotation in LexOffice manuell (AN-221646)');
|
||||
} else {
|
||||
console.log('\n✅ Keine LexOffice IDs vorhanden - nichts zu löschen.');
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
68
scripts/restore-lexoffice-article-ids.ts
Normal file
68
scripts/restore-lexoffice-article-ids.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🔄 Stelle LexOffice-Artikel-IDs wieder her...\n');
|
||||
|
||||
// 1. Fotobox-Artikel-IDs für ALLE PriceConfigs setzen
|
||||
const photoboxUpdate = await prisma.priceConfig.updateMany({
|
||||
data: {
|
||||
lexofficeArticleId: '5d9d3716-f81e-4e46-b5cf-13988f489cc2', // ohne Druckflatrate
|
||||
lexofficeArticleIdWithFlat: '3f26c02c-d705-41a6-9b49-2e2e96e77036', // mit Druckflatrate
|
||||
},
|
||||
});
|
||||
console.log(`✅ ${photoboxUpdate.count} PriceConfigs mit Fotobox-Artikel-IDs aktualisiert`);
|
||||
|
||||
// 2. Kilometerpauschale für Lübeck
|
||||
const luebeckLocation = await prisma.location.findFirst({
|
||||
where: { city: 'Lübeck' },
|
||||
});
|
||||
|
||||
if (luebeckLocation) {
|
||||
const kmUpdate = await prisma.priceConfig.updateMany({
|
||||
where: { locationId: luebeckLocation.id },
|
||||
data: {
|
||||
lexofficeKmFlatArticleId: 'd3e2b21b-e899-412d-b53e-c82a0a94fcfa',
|
||||
},
|
||||
});
|
||||
console.log(`✅ ${kmUpdate.count} PriceConfigs für Lübeck mit Kilometerpauschale-ID aktualisiert`);
|
||||
}
|
||||
|
||||
// 3. Equipment-IDs prüfen
|
||||
const equipment = await prisma.equipment.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
lexofficeArticleId: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('\n📦 Vorhandene Equipment-Artikel:');
|
||||
console.table(equipment);
|
||||
|
||||
// 4. Finale Übersicht
|
||||
console.log('\n📊 Finale PriceConfig-Übersicht:');
|
||||
const configs = await prisma.priceConfig.findMany({
|
||||
include: {
|
||||
location: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.table(configs.map(c => ({
|
||||
model: c.model,
|
||||
city: c.location?.city,
|
||||
articleId: c.lexofficeArticleId?.slice(0, 8) + '...',
|
||||
articleIdWithFlat: c.lexofficeArticleIdWithFlat?.slice(0, 8) + '...',
|
||||
kmFlatArticleId: c.lexofficeKmFlatArticleId?.slice(0, 8) + '...' || 'null',
|
||||
})));
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
114
scripts/set-correct-article-ids.ts
Normal file
114
scripts/set-correct-article-ids.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🔄 Setze korrekte LexOffice-Artikel-IDs pro Modell...\n');
|
||||
|
||||
// Mapping: Modell -> Artikel-IDs
|
||||
const modelArticleIds: Record<string, { withoutFlat: string; withFlat: string }> = {
|
||||
'VINTAGE_SMILE': {
|
||||
withoutFlat: '5d9d3716-f81e-4e46-b5cf-13988f489cc2',
|
||||
withFlat: '3f26c02c-d705-41a6-9b49-2e2e96e77036',
|
||||
},
|
||||
'VINTAGE_PHOTOS': {
|
||||
withoutFlat: '5d9d3716-f81e-4e46-b5cf-13988f489cc2',
|
||||
withFlat: '3f26c02c-d705-41a6-9b49-2e2e96e77036',
|
||||
},
|
||||
'NOSTALGIE': {
|
||||
withoutFlat: '8954bd20-570c-4a7d-9ac3-8ee756652a89',
|
||||
withFlat: '701bd150-48c0-4937-b628-f4a754d86264',
|
||||
},
|
||||
'MAGIC_MIRROR': {
|
||||
withoutFlat: '72bbe51b-c0bb-437a-963b-248cb105553a',
|
||||
withFlat: '39ec59e7-57a6-4d6d-80f6-645e589c4b2c',
|
||||
},
|
||||
};
|
||||
|
||||
// Update pro Modell
|
||||
for (const [model, ids] of Object.entries(modelArticleIds)) {
|
||||
const result = await prisma.priceConfig.updateMany({
|
||||
where: { model },
|
||||
data: {
|
||||
lexofficeArticleId: ids.withoutFlat,
|
||||
lexofficeArticleIdWithFlat: ids.withFlat,
|
||||
},
|
||||
});
|
||||
console.log(`✅ ${model}: ${result.count} PriceConfigs aktualisiert`);
|
||||
}
|
||||
|
||||
// Kilometerpauschale für Lübeck
|
||||
const luebeck = await prisma.location.findFirst({ where: { city: 'Lübeck' } });
|
||||
if (luebeck) {
|
||||
const kmResult = await prisma.priceConfig.updateMany({
|
||||
where: { locationId: luebeck.id },
|
||||
data: { lexofficeKmFlatArticleId: 'd3e2b21b-e899-412d-b53e-c82a0a94fcfa' },
|
||||
});
|
||||
console.log(`✅ Lübeck Kilometerpauschale: ${kmResult.count} PriceConfigs aktualisiert`);
|
||||
}
|
||||
|
||||
console.log('\n📦 Equipment-Artikel-IDs aktualisieren...\n');
|
||||
|
||||
// Equipment-Mapping: Name -> Artikel-ID
|
||||
const equipmentArticleIds: Record<string, string> = {
|
||||
'Accessoires': 'c62d4dad-4f04-4330-9019-f9804bb43ddc',
|
||||
'VIP-Bänder': 'e3942394-94d7-45ea-a31b-a1b035f6f34e',
|
||||
'Erweiterte Druck-Flat DIY': '774aba7f-2c20-4d65-b8d0-4793f27d2d71',
|
||||
'Kartenspiel Fotoboxaufgaben': '17d563fe-5f00-4591-a414-d013d7ce68e0',
|
||||
'Roter Teppich': 'd138da65-4e23-4a88-813a-1f3725a75a15',
|
||||
'Service-Techniker vor Ort': 'ec972616-8bc5-4334-876d-b442838a8bbf',
|
||||
};
|
||||
|
||||
for (const [name, articleId] of Object.entries(equipmentArticleIds)) {
|
||||
try {
|
||||
const equipment = await prisma.equipment.findFirst({
|
||||
where: { name: { contains: name, mode: 'insensitive' } },
|
||||
});
|
||||
|
||||
if (equipment) {
|
||||
await prisma.equipment.update({
|
||||
where: { id: equipment.id },
|
||||
data: { lexofficeArticleId: articleId },
|
||||
});
|
||||
console.log(`✅ ${name}: Artikel-ID gesetzt`);
|
||||
} else {
|
||||
console.log(`⚠️ ${name}: Equipment nicht gefunden - übersprungen`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`⚠️ ${name}: Fehler - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n📊 Finale Übersicht:');
|
||||
const configs = await prisma.priceConfig.findMany({
|
||||
include: { location: true },
|
||||
orderBy: [{ location: { city: 'asc' } }, { model: 'asc' }],
|
||||
});
|
||||
|
||||
console.table(configs.map(c => ({
|
||||
city: c.location?.city,
|
||||
model: c.model,
|
||||
withoutFlat: c.lexofficeArticleId?.slice(0, 8) + '...',
|
||||
withFlat: c.lexofficeArticleIdWithFlat?.slice(0, 8) + '...',
|
||||
kmFlat: c.lexofficeKmFlatArticleId?.slice(0, 8) + '...' || 'null',
|
||||
})));
|
||||
|
||||
const equipment = await prisma.equipment.findMany({
|
||||
select: { name: true, lexofficeArticleId: true },
|
||||
});
|
||||
|
||||
console.log('\n📦 Equipment mit Artikel-IDs:');
|
||||
console.table(equipment.map(e => ({
|
||||
name: e.name,
|
||||
articleId: e.lexofficeArticleId?.slice(0, 8) + '...' || 'null',
|
||||
})));
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
113
scripts/setup-lexoffice-mapping.ts
Normal file
113
scripts/setup-lexoffice-mapping.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// LexOffice Artikel-IDs (von Dennis bereitgestellt)
|
||||
const LEXOFFICE_ARTICLES = {
|
||||
// Fotoboxen
|
||||
VINTAGE: {
|
||||
withFlat: '3f26c02c-d705-41a6-9b49-2e2e96e77036', // Fotobox Vintage Flat
|
||||
withoutFlat: '5d9d3716-f81e-4e46-b5cf-13988f489cc2', // Fotobox Vintage
|
||||
},
|
||||
NOSTALGIE: {
|
||||
withFlat: '701bd150-48c0-4937-b628-f4a754d86264', // Fotobox Nostalgie Flat
|
||||
withoutFlat: '8954bd20-570c-4a7d-9ac3-8ee756652a89', // Fotobox Nostalgie
|
||||
},
|
||||
MAGIC_MIRROR: {
|
||||
withFlat: '39ec59e7-57a6-4d6d-80f6-645e589c4b2c', // Fotobox Magic Mirror Flat
|
||||
withoutFlat: '72bbe51b-c0bb-437a-963b-248cb105553a', // Fotobox Magic Mirror
|
||||
},
|
||||
|
||||
// Equipment/Extras
|
||||
ACCESSORIES: 'c62d4dad-4f04-4330-9019-f9804bb43ddc',
|
||||
VIP_BARRIER: 'e3942394-94d7-45ea-a31b-a1b035f6f34e',
|
||||
PRINT_FLAT_DIY: '774aba7f-2c20-4d65-b8d0-4793f27d2d71',
|
||||
CARD_GAME: '17d563fe-5f00-4591-a414-d013d7ce68e0',
|
||||
RED_CARPET: 'd138da65-4e23-4a88-813a-1f3725a75a15',
|
||||
SERVICE_TECH: 'ec972616-8bc5-4334-876d-b442838a8bbf',
|
||||
};
|
||||
|
||||
async function setupLexOfficeMapping() {
|
||||
console.log('🔧 Konfiguriere LexOffice Artikel-IDs...\n');
|
||||
|
||||
try {
|
||||
const locations = await prisma.location.findMany();
|
||||
|
||||
for (const location of locations) {
|
||||
console.log(`📍 ${location.name}:`);
|
||||
|
||||
// VINTAGE_SMILE / VINTAGE_PHOTOS (gleiche Box)
|
||||
const vintageConfigs = await prisma.priceConfig.findMany({
|
||||
where: {
|
||||
locationId: location.id,
|
||||
model: {
|
||||
in: ['VINTAGE_SMILE', 'VINTAGE_PHOTOS'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const config of vintageConfigs) {
|
||||
await prisma.priceConfig.update({
|
||||
where: { id: config.id },
|
||||
data: {
|
||||
lexofficeArticleId: LEXOFFICE_ARTICLES.VINTAGE.withoutFlat,
|
||||
lexofficeArticleIdWithFlat: LEXOFFICE_ARTICLES.VINTAGE.withFlat,
|
||||
},
|
||||
});
|
||||
console.log(` ✅ ${config.model}: LexOffice IDs gesetzt`);
|
||||
}
|
||||
|
||||
// NOSTALGIE
|
||||
const nostalgieConfig = await prisma.priceConfig.findFirst({
|
||||
where: {
|
||||
locationId: location.id,
|
||||
model: 'NOSTALGIE',
|
||||
},
|
||||
});
|
||||
|
||||
if (nostalgieConfig) {
|
||||
await prisma.priceConfig.update({
|
||||
where: { id: nostalgieConfig.id },
|
||||
data: {
|
||||
lexofficeArticleId: LEXOFFICE_ARTICLES.NOSTALGIE.withoutFlat,
|
||||
lexofficeArticleIdWithFlat: LEXOFFICE_ARTICLES.NOSTALGIE.withFlat,
|
||||
},
|
||||
});
|
||||
console.log(` ✅ NOSTALGIE: LexOffice IDs gesetzt`);
|
||||
}
|
||||
|
||||
// MAGIC_MIRROR
|
||||
const magicMirrorConfig = await prisma.priceConfig.findFirst({
|
||||
where: {
|
||||
locationId: location.id,
|
||||
model: 'MAGIC_MIRROR',
|
||||
},
|
||||
});
|
||||
|
||||
if (magicMirrorConfig) {
|
||||
await prisma.priceConfig.update({
|
||||
where: { id: magicMirrorConfig.id },
|
||||
data: {
|
||||
lexofficeArticleId: LEXOFFICE_ARTICLES.MAGIC_MIRROR.withoutFlat,
|
||||
lexofficeArticleIdWithFlat: LEXOFFICE_ARTICLES.MAGIC_MIRROR.withFlat,
|
||||
},
|
||||
});
|
||||
console.log(` ✅ MAGIC_MIRROR: LexOffice IDs gesetzt`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n🎉 LexOffice Artikel-Mapping erfolgreich konfiguriert!');
|
||||
console.log('\n📝 Nächste Schritte:');
|
||||
console.log(' 1. Testbuchung erstellen (mit/ohne Druckflatrate)');
|
||||
console.log(' 2. LexOffice Angebot generieren');
|
||||
console.log(' 3. Prüfen ob korrekte Artikel-IDs verwendet werden\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
setupLexOfficeMapping();
|
||||
73
scripts/sync-nextcloud-bookings.ts
Normal file
73
scripts/sync-nextcloud-bookings.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { config } from 'dotenv';
|
||||
config(); // Load .env file
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { nextcloudCalendar } from '../lib/nextcloud-calendar.ts';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function syncExistingBookings() {
|
||||
console.log('🔄 Synchronisiere bestehende Buchungen mit Nextcloud...\n');
|
||||
|
||||
try {
|
||||
// Hole alle bestätigten Buchungen
|
||||
const bookings = await prisma.booking.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: ['RESERVED', 'CONFIRMED'],
|
||||
},
|
||||
},
|
||||
include: {
|
||||
location: true,
|
||||
photobox: true,
|
||||
},
|
||||
orderBy: {
|
||||
eventDate: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`📊 Gefunden: ${bookings.length} Buchungen\n`);
|
||||
|
||||
if (bookings.length === 0) {
|
||||
console.log('ℹ️ Keine Buchungen zum Synchronisieren gefunden.');
|
||||
return;
|
||||
}
|
||||
|
||||
let synced = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const booking of bookings) {
|
||||
try {
|
||||
console.log(`📅 Synchronisiere: ${booking.bookingNumber} - ${booking.customerName}`);
|
||||
console.log(` Event: ${new Date(booking.eventDate).toLocaleDateString('de-DE')}`);
|
||||
console.log(` Standort: ${booking.location?.name || 'Unbekannt'}`);
|
||||
|
||||
await nextcloudCalendar.syncBookingToCalendar(booking);
|
||||
synced++;
|
||||
console.log(` ✅ Erfolgreich!\n`);
|
||||
} catch (error: any) {
|
||||
failed++;
|
||||
console.error(` ❌ Fehler: ${error.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('─'.repeat(50));
|
||||
console.log(`✅ Erfolgreich synchronisiert: ${synced}`);
|
||||
console.log(`❌ Fehlgeschlagen: ${failed}`);
|
||||
console.log(`📊 Gesamt: ${bookings.length}`);
|
||||
console.log('\n🎉 Synchronisation abgeschlossen!');
|
||||
console.log(' Prüfen Sie Nextcloud → Kalender "Buchungen (Dennis Forte)"');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler beim Synchronisieren:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
syncExistingBookings()
|
||||
.catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
85
scripts/test-article-access.ts
Normal file
85
scripts/test-article-access.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { lexofficeService } from '../lib/lexoffice';
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Teste LexOffice Artikel-Zugriff...\n');
|
||||
|
||||
const testArticleIds = [
|
||||
'5d9d3716-f81e-4e46-b5cf-13988f489cc2', // Vintage ohne Flat
|
||||
'3f26c02c-d705-41a6-9b49-2e2e96e77036', // Vintage mit Flat
|
||||
'8954bd20-570c-4a7d-9ac3-8ee756652a89', // Nostalgie ohne Flat
|
||||
'701bd150-48c0-4937-b628-f4a754d86264', // Nostalgie mit Flat
|
||||
];
|
||||
|
||||
for (const articleId of testArticleIds) {
|
||||
try {
|
||||
console.log(`\n📦 Teste Artikel-ID: ${articleId}`);
|
||||
|
||||
// Versuche den Artikel abzurufen
|
||||
const response = await fetch(`https://api.lexoffice.io/v1/articles/${articleId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.LEXOFFICE_API_KEY}`,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const article = await response.json();
|
||||
console.log('✅ Artikel gefunden:', article.title || article.name);
|
||||
console.log(' Preis:', article.price?.netAmount, 'EUR');
|
||||
} else {
|
||||
const error = await response.text();
|
||||
console.log('❌ Artikel nicht gefunden:', response.status, error);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n\n🔍 Teste Quotation mit custom lineItem (ohne Artikel-ID)...\n');
|
||||
|
||||
try {
|
||||
const testQuotation = {
|
||||
voucherDate: new Date().toISOString().split('T')[0] + 'T00:00:00.000+01:00',
|
||||
expirationDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + 'T00:00:00.000+01:00',
|
||||
address: {
|
||||
name: 'Test Kunde',
|
||||
countryCode: 'DE',
|
||||
},
|
||||
lineItems: [
|
||||
{
|
||||
type: 'custom' as const,
|
||||
name: 'Test Fotobox VINTAGE_PHOTOS mit Druckflatrate',
|
||||
description: 'Test Beschreibung',
|
||||
quantity: 1,
|
||||
unitName: 'Stück',
|
||||
unitPrice: {
|
||||
currency: 'EUR',
|
||||
netAmount: 449,
|
||||
taxRatePercentage: 19,
|
||||
},
|
||||
},
|
||||
],
|
||||
totalPrice: {
|
||||
currency: 'EUR',
|
||||
},
|
||||
taxConditions: {
|
||||
taxType: 'net' as const,
|
||||
},
|
||||
};
|
||||
|
||||
console.log('📤 Erstelle Test-Quotation mit custom lineItem...');
|
||||
const result = await lexofficeService.createQuotation(testQuotation, true);
|
||||
console.log('✅ Erfolgreich erstellt:', result.id);
|
||||
console.log(' Voucher Number:', result.voucherNumber);
|
||||
|
||||
console.log('\n📄 Teste PDF Download...');
|
||||
const pdf = await lexofficeService.getQuotationPDF(result.id);
|
||||
console.log('✅ PDF erfolgreich heruntergeladen! Größe:', pdf.length, 'bytes');
|
||||
|
||||
} catch (error: any) {
|
||||
console.log('❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
53
scripts/test-booking-automation.ts
Normal file
53
scripts/test-booking-automation.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { bookingAutomationService } from '../lib/booking-automation.ts';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function testAutomation() {
|
||||
console.log('🧪 Teste Booking Automation...\n');
|
||||
|
||||
try {
|
||||
// Hole neueste Buchung
|
||||
const latestBooking = await prisma.booking.findFirst({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
bookingNumber: true,
|
||||
customerName: true,
|
||||
eventDate: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!latestBooking) {
|
||||
console.log('❌ Keine Buchung gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📋 Buchung: ${latestBooking.bookingNumber} - ${latestBooking.customerName}`);
|
||||
console.log(` Event: ${new Date(latestBooking.eventDate).toLocaleDateString('de-DE')}\n`);
|
||||
|
||||
console.log('🤖 Starte automatische Aktionen...\n');
|
||||
|
||||
const result = await bookingAutomationService.runPostBookingActions(latestBooking.id);
|
||||
|
||||
console.log('\n' + '─'.repeat(60));
|
||||
console.log('📊 Ergebnis:');
|
||||
console.log(` ✅ E-Mail gesendet: ${result.emailSent ? 'Ja' : 'Nein'}`);
|
||||
console.log(` ✅ Kalender synchronisiert: ${result.calendarSynced ? 'Ja' : 'Nein'}`);
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.log(`\n❌ Fehler (${result.errors.length}):`);
|
||||
result.errors.forEach(err => console.log(` - ${err}`));
|
||||
} else {
|
||||
console.log('\n✅ Alle Aktionen erfolgreich!');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('\n❌ Fehler:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
testAutomation();
|
||||
54
scripts/test-lexoffice-finalize.ts
Normal file
54
scripts/test-lexoffice-finalize.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { LexOfficeService } from '../lib/lexoffice';
|
||||
|
||||
async function main() {
|
||||
const lexoffice = new LexOfficeService();
|
||||
|
||||
console.log('🧪 Teste LexOffice Quotation finalize Parameter...\n');
|
||||
|
||||
// Einfaches Test-Angebot erstellen
|
||||
const testQuotation = {
|
||||
voucherDate: new Date().toISOString().split('T')[0] + 'T00:00:00.000+01:00',
|
||||
expirationDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + 'T00:00:00.000+01:00',
|
||||
address: {
|
||||
name: 'Test Kunde',
|
||||
countryCode: 'DE',
|
||||
},
|
||||
lineItems: [
|
||||
{
|
||||
type: 'custom' as const,
|
||||
name: 'Test Artikel',
|
||||
quantity: 1,
|
||||
unitName: 'Stück',
|
||||
unitPrice: {
|
||||
currency: 'EUR',
|
||||
netAmount: 100,
|
||||
taxRatePercentage: 19,
|
||||
},
|
||||
},
|
||||
],
|
||||
totalPrice: {
|
||||
currency: 'EUR',
|
||||
},
|
||||
taxConditions: {
|
||||
taxType: 'net' as const,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('📤 Erstelle Quotation MIT finalize=true...');
|
||||
const result = await lexoffice.createQuotation(testQuotation, true);
|
||||
console.log('✅ Quotation erstellt:', result);
|
||||
|
||||
console.log('\n🔍 Lade Quotation Details...');
|
||||
const details = await lexoffice.getQuotation(result.id);
|
||||
console.log('📊 Quotation Status:', JSON.stringify(details, null, 2));
|
||||
|
||||
console.log('\n📄 Versuche PDF Download...');
|
||||
const pdf = await lexoffice.getQuotationPDF(result.id);
|
||||
console.log('✅ PDF erfolgreich heruntergeladen! Größe:', pdf.length, 'bytes');
|
||||
} catch (error: any) {
|
||||
console.error('❌ Fehler:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user