update
This commit is contained in:
119
server.js
119
server.js
@@ -403,7 +403,7 @@ app.delete('/api/quotes/:id', async (req, res) => {
|
||||
app.get('/api/invoices', async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT i.*, c.name as customer_name
|
||||
SELECT i.*, c.name as customer_name, c.qbo_id as customer_qbo_id
|
||||
FROM invoices i
|
||||
LEFT JOIN customers c ON i.customer_id = c.id
|
||||
ORDER BY i.created_at DESC
|
||||
@@ -1831,8 +1831,125 @@ app.get('/api/payments', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// Neue Server Endpoints — In server.js einfügen
|
||||
// 1. Customer QBO Export
|
||||
// 2. Labor Rate aus QBO
|
||||
// =====================================================
|
||||
|
||||
|
||||
// --- 1. Kunde nach QBO exportieren ---
|
||||
app.post('/api/customers/:id/export-qbo', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const custResult = await pool.query('SELECT * FROM customers WHERE id = $1', [id]);
|
||||
if (custResult.rows.length === 0) return res.status(404).json({ error: 'Customer not found' });
|
||||
|
||||
const customer = custResult.rows[0];
|
||||
|
||||
if (customer.qbo_id) {
|
||||
return res.status(400).json({ error: `Kunde "${customer.name}" ist bereits in QBO (ID: ${customer.qbo_id}).` });
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
// QBO Customer Objekt
|
||||
const qboCustomer = {
|
||||
DisplayName: customer.name,
|
||||
CompanyName: customer.name,
|
||||
BillAddr: {},
|
||||
PrimaryEmailAddr: customer.email ? { Address: customer.email } : undefined,
|
||||
PrimaryPhone: customer.phone ? { FreeFormNumber: customer.phone } : undefined,
|
||||
// Taxable setzt man über TaxExemptionReasonId oder SalesTermRef
|
||||
Taxable: customer.taxable !== false
|
||||
};
|
||||
|
||||
// Adresse aufbauen
|
||||
const addr = qboCustomer.BillAddr;
|
||||
if (customer.line1) addr.Line1 = customer.line1;
|
||||
if (customer.line2) addr.Line2 = customer.line2;
|
||||
if (customer.line3) addr.Line3 = customer.line3;
|
||||
if (customer.line4) addr.Line4 = customer.line4;
|
||||
if (customer.city) addr.City = customer.city;
|
||||
if (customer.state) addr.CountrySubDivisionCode = customer.state;
|
||||
if (customer.zip_code) addr.PostalCode = customer.zip_code;
|
||||
|
||||
// Kein leeres BillAddr senden
|
||||
if (Object.keys(addr).length === 0) delete qboCustomer.BillAddr;
|
||||
|
||||
console.log(`📤 Exportiere Kunde "${customer.name}" nach QBO...`);
|
||||
|
||||
const response = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/customer`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(qboCustomer)
|
||||
});
|
||||
|
||||
const data = response.getJson ? response.getJson() : response.json;
|
||||
|
||||
if (data.Customer) {
|
||||
const qboId = data.Customer.Id;
|
||||
|
||||
// qbo_id lokal speichern
|
||||
await pool.query(
|
||||
'UPDATE customers SET qbo_id = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
|
||||
[qboId, id]
|
||||
);
|
||||
|
||||
console.log(`✅ Kunde "${customer.name}" in QBO erstellt: ID ${qboId}`);
|
||||
res.json({ success: true, qbo_id: qboId, name: customer.name });
|
||||
} else {
|
||||
console.error('❌ QBO Customer Fehler:', JSON.stringify(data));
|
||||
|
||||
// Spezieller Fehler: Name existiert schon in QBO
|
||||
const errMsg = data.Fault?.Error?.[0]?.Message || JSON.stringify(data);
|
||||
const errDetail = data.Fault?.Error?.[0]?.Detail || '';
|
||||
|
||||
res.status(500).json({ error: `QBO Fehler: ${errMsg}. ${errDetail}` });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Customer Export Error:', error);
|
||||
res.status(500).json({ error: 'Export fehlgeschlagen: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- 2. Labor Rate aus QBO laden ---
|
||||
// Lädt den UnitPrice des "Labor" Items (ID 5) aus QBO
|
||||
app.get('/api/qbo/labor-rate', async (req, res) => {
|
||||
try {
|
||||
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';
|
||||
|
||||
// Item ID 5 = Labor
|
||||
const response = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/item/5`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
const data = response.getJson ? response.getJson() : response.json;
|
||||
const rate = data.Item?.UnitPrice || null;
|
||||
|
||||
console.log(`💰 QBO Labor Rate: $${rate}`);
|
||||
res.json({ rate });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching labor rate:', error);
|
||||
// Nicht kritisch — Fallback auf Frontend-Default
|
||||
res.json({ rate: null });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Start server and browser
|
||||
async function startServer() {
|
||||
|
||||
Reference in New Issue
Block a user