195 lines
7.4 KiB
JavaScript
195 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
// import_qbo_payment.js — Importiert ein spezifisches QBO Payment in die lokale DB
|
|
//
|
|
// Verwendung:
|
|
// node import_qbo_payment.js <QBO_PAYMENT_ID>
|
|
//
|
|
// Beispiel:
|
|
// node import_qbo_payment.js 20616
|
|
//
|
|
// Was passiert:
|
|
// 1. Payment aus QBO laden (inkl. LinkedTxn → verknüpfte Invoices)
|
|
// 2. Lokale Invoices anhand qbo_id finden
|
|
// 3. Payment in lokale payments-Tabelle schreiben
|
|
// 4. Invoices als bezahlt markieren (paid_date)
|
|
|
|
require('dotenv').config();
|
|
const { Pool } = require('pg');
|
|
const { makeQboApiCall, getOAuthClient } = require('./qbo_helper');
|
|
|
|
const pool = new Pool({
|
|
user: process.env.DB_USER || 'postgres',
|
|
host: process.env.DB_HOST || 'localhost',
|
|
database: process.env.DB_NAME || 'quotes_db',
|
|
password: process.env.DB_PASSWORD || 'postgres',
|
|
port: process.env.DB_PORT || 5432,
|
|
});
|
|
|
|
async function importPayment(qboPaymentId) {
|
|
const oauthClient = getOAuthClient();
|
|
const companyId = oauthClient.getToken().realmId;
|
|
const baseUrl = process.env.QBO_ENVIRONMENT === 'production'
|
|
? 'https://quickbooks.api.intuit.com'
|
|
: 'https://sandbox-quickbooks.api.intuit.com';
|
|
|
|
console.log(`\n🔍 Lade QBO Payment ${qboPaymentId}...`);
|
|
|
|
// 1. Payment aus QBO lesen
|
|
const response = await makeQboApiCall({
|
|
url: `${baseUrl}/v3/company/${companyId}/payment/${qboPaymentId}`,
|
|
method: 'GET'
|
|
});
|
|
|
|
const data = response.getJson ? response.getJson() : response.json;
|
|
const payment = data.Payment;
|
|
|
|
if (!payment) {
|
|
console.error('❌ Payment nicht gefunden in QBO.');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`✅ Payment gefunden:`);
|
|
console.log(` Datum: ${payment.TxnDate}`);
|
|
console.log(` Betrag: $${payment.TotalAmt}`);
|
|
console.log(` Referenz: ${payment.PaymentRefNum || '(keine)'}`);
|
|
console.log(` Kunde: ${payment.CustomerRef?.name || payment.CustomerRef?.value}`);
|
|
|
|
// 2. Verknüpfte Invoices aus dem Payment extrahieren
|
|
const linkedInvoices = [];
|
|
if (payment.Line) {
|
|
for (const line of payment.Line) {
|
|
if (line.LinkedTxn) {
|
|
for (const txn of line.LinkedTxn) {
|
|
if (txn.TxnType === 'Invoice') {
|
|
linkedInvoices.push({
|
|
qbo_invoice_id: txn.TxnId,
|
|
amount: line.Amount
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(` Verknüpfte Invoices: ${linkedInvoices.length}`);
|
|
linkedInvoices.forEach(li => {
|
|
console.log(` - QBO Invoice ID: ${li.qbo_invoice_id}, Amount: $${li.amount}`);
|
|
});
|
|
|
|
// 3. Lokale Invoices finden
|
|
const dbClient = await pool.connect();
|
|
try {
|
|
await dbClient.query('BEGIN');
|
|
|
|
// Kunden-ID lokal finden
|
|
const customerResult = await dbClient.query(
|
|
'SELECT id FROM customers WHERE qbo_id = $1',
|
|
[payment.CustomerRef?.value]
|
|
);
|
|
const customerId = customerResult.rows[0]?.id || null;
|
|
|
|
// PaymentMethod-Name aus QBO holen (optional)
|
|
let paymentMethodName = 'Unknown';
|
|
if (payment.PaymentMethodRef?.value) {
|
|
try {
|
|
const pmRes = await makeQboApiCall({
|
|
url: `${baseUrl}/v3/company/${companyId}/paymentmethod/${payment.PaymentMethodRef.value}`,
|
|
method: 'GET'
|
|
});
|
|
const pmData = pmRes.getJson ? pmRes.getJson() : pmRes.json;
|
|
paymentMethodName = pmData.PaymentMethod?.Name || 'Unknown';
|
|
} catch (e) {
|
|
console.log(' ⚠️ PaymentMethod konnte nicht geladen werden.');
|
|
}
|
|
}
|
|
|
|
// DepositTo-Account-Name aus QBO (optional)
|
|
let depositToName = '';
|
|
if (payment.DepositToAccountRef?.value) {
|
|
try {
|
|
const accRes = await makeQboApiCall({
|
|
url: `${baseUrl}/v3/company/${companyId}/account/${payment.DepositToAccountRef.value}`,
|
|
method: 'GET'
|
|
});
|
|
const accData = accRes.getJson ? accRes.getJson() : accRes.json;
|
|
depositToName = accData.Account?.Name || '';
|
|
} catch (e) {
|
|
console.log(' ⚠️ Account konnte nicht geladen werden.');
|
|
}
|
|
}
|
|
|
|
// Prüfen ob Payment schon importiert
|
|
const existing = await dbClient.query(
|
|
'SELECT id FROM payments WHERE qbo_payment_id = $1',
|
|
[String(qboPaymentId)]
|
|
);
|
|
if (existing.rows.length > 0) {
|
|
console.log(`\n⚠️ Payment ${qboPaymentId} wurde bereits importiert (lokale ID: ${existing.rows[0].id}).`);
|
|
console.log(' Übersprungen.');
|
|
await dbClient.query('ROLLBACK');
|
|
return;
|
|
}
|
|
|
|
// Payment lokal anlegen
|
|
const paymentResult = await dbClient.query(
|
|
`INSERT INTO payments (payment_date, reference_number, payment_method, deposit_to_account, total_amount, customer_id, qbo_payment_id)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
|
[payment.TxnDate, payment.PaymentRefNum || null, paymentMethodName, depositToName, payment.TotalAmt, customerId, String(qboPaymentId)]
|
|
);
|
|
const localPaymentId = paymentResult.rows[0].id;
|
|
console.log(`\n💾 Lokales Payment erstellt: ID ${localPaymentId}`);
|
|
|
|
// Verknüpfte Invoices lokal finden und markieren
|
|
let matchedCount = 0;
|
|
for (const li of linkedInvoices) {
|
|
const invResult = await dbClient.query(
|
|
'SELECT id, invoice_number FROM invoices WHERE qbo_id = $1',
|
|
[li.qbo_invoice_id]
|
|
);
|
|
|
|
if (invResult.rows.length > 0) {
|
|
const localInv = invResult.rows[0];
|
|
|
|
await dbClient.query(
|
|
'INSERT INTO payment_invoices (payment_id, invoice_id, amount) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
|
|
[localPaymentId, localInv.id, li.amount]
|
|
);
|
|
await dbClient.query(
|
|
'UPDATE invoices SET paid_date = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2 AND paid_date IS NULL',
|
|
[payment.TxnDate, localInv.id]
|
|
);
|
|
|
|
console.log(` ✅ Invoice #${localInv.invoice_number || localInv.id} (QBO: ${li.qbo_invoice_id}) → bezahlt`);
|
|
matchedCount++;
|
|
} else {
|
|
console.log(` ⚠️ QBO Invoice ID ${li.qbo_invoice_id} nicht in lokaler DB gefunden.`);
|
|
}
|
|
}
|
|
|
|
await dbClient.query('COMMIT');
|
|
|
|
console.log(`\n✅ Import abgeschlossen: ${matchedCount}/${linkedInvoices.length} Invoices verknüpft.`);
|
|
console.log(` Payment: Lokal ID ${localPaymentId}, QBO ID ${qboPaymentId}`);
|
|
console.log(` Methode: ${paymentMethodName}, Konto: ${depositToName}`);
|
|
|
|
} catch (error) {
|
|
await dbClient.query('ROLLBACK').catch(() => {});
|
|
console.error('❌ Fehler:', error);
|
|
} finally {
|
|
dbClient.release();
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
// --- Main ---
|
|
const qboPaymentId = process.argv[2];
|
|
if (!qboPaymentId) {
|
|
console.log('Verwendung: node import_qbo_payment.js <QBO_PAYMENT_ID>');
|
|
console.log('Beispiel: node import_qbo_payment.js 20616');
|
|
process.exit(1);
|
|
}
|
|
|
|
importPayment(qboPaymentId).catch(err => {
|
|
console.error('Fatal:', err);
|
|
process.exit(1);
|
|
}); |