update delete endpoints
This commit is contained in:
parent
c44fc7f63e
commit
5e63adfee8
181
server.js
181
server.js
|
|
@ -193,12 +193,13 @@ app.post('/api/customers', async (req, res) => {
|
|||
// PUT /api/customers/:id
|
||||
app.put('/api/customers/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
name, line1, line2, line3, line4, city, state, zip_code,
|
||||
account_number, email, phone, phone2, taxable
|
||||
const {
|
||||
name, line1, line2, line3, line4, city, state, zip_code,
|
||||
account_number, email, phone, phone2, taxable
|
||||
} = req.body;
|
||||
|
||||
|
||||
try {
|
||||
// Lokal updaten
|
||||
const result = await pool.query(
|
||||
`UPDATE customers
|
||||
SET name = $1, line1 = $2, line2 = $3, line3 = $4, line4 = $5,
|
||||
|
|
@ -206,12 +207,73 @@ app.put('/api/customers/:id', async (req, res) => {
|
|||
phone = $11, phone2 = $12, taxable = $13, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $14
|
||||
RETURNING *`,
|
||||
[name, line1 || null, line2 || null, line3 || null, line4 || null,
|
||||
city || null, state || null, zip_code || null,
|
||||
[name, line1 || null, line2 || null, line3 || null, line4 || null,
|
||||
city || null, state || null, zip_code || null,
|
||||
account_number || null, email || null, phone || null, phone2 || null,
|
||||
taxable !== undefined ? taxable : true, id]
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
|
||||
const customer = result.rows[0];
|
||||
|
||||
// In QBO updaten falls vorhanden
|
||||
if (customer.qbo_id) {
|
||||
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';
|
||||
|
||||
// Aktuellen SyncToken holen
|
||||
const qboRes = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/customer/${customer.qbo_id}`,
|
||||
method: 'GET'
|
||||
});
|
||||
const qboData = qboRes.getJson ? qboRes.getJson() : qboRes.json;
|
||||
const syncToken = qboData.Customer?.SyncToken;
|
||||
|
||||
if (syncToken !== undefined) {
|
||||
// Sparse update
|
||||
const updatePayload = {
|
||||
Id: customer.qbo_id,
|
||||
SyncToken: syncToken,
|
||||
sparse: true,
|
||||
DisplayName: name,
|
||||
CompanyName: name,
|
||||
PrimaryEmailAddr: email ? { Address: email } : undefined,
|
||||
PrimaryPhone: phone ? { FreeFormNumber: phone } : undefined,
|
||||
Taxable: taxable !== false
|
||||
};
|
||||
|
||||
// Adresse
|
||||
const addr = {};
|
||||
if (line1) addr.Line1 = line1;
|
||||
if (line2) addr.Line2 = line2;
|
||||
if (line3) addr.Line3 = line3;
|
||||
if (line4) addr.Line4 = line4;
|
||||
if (city) addr.City = city;
|
||||
if (state) addr.CountrySubDivisionCode = state;
|
||||
if (zip_code) addr.PostalCode = zip_code;
|
||||
if (Object.keys(addr).length > 0) updatePayload.BillAddr = addr;
|
||||
|
||||
console.log(`📤 Updating QBO Customer ${customer.qbo_id} (${name})...`);
|
||||
|
||||
await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/customer`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updatePayload)
|
||||
});
|
||||
|
||||
console.log(`✅ QBO Customer ${customer.qbo_id} updated.`);
|
||||
}
|
||||
} catch (qboError) {
|
||||
console.error(`⚠️ QBO update failed for Customer ${customer.qbo_id}:`, qboError.message);
|
||||
// Nicht abbrechen — lokales Update war erfolgreich
|
||||
}
|
||||
}
|
||||
|
||||
res.json(customer);
|
||||
} catch (error) {
|
||||
console.error('Error updating customer:', error);
|
||||
res.status(500).json({ error: 'Error updating customer' });
|
||||
|
|
@ -220,9 +282,60 @@ app.put('/api/customers/:id', async (req, res) => {
|
|||
|
||||
app.delete('/api/customers/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
// Kunde laden
|
||||
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];
|
||||
|
||||
// In QBO deaktivieren falls vorhanden
|
||||
if (customer.qbo_id) {
|
||||
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';
|
||||
|
||||
// SyncToken holen
|
||||
const qboRes = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/customer/${customer.qbo_id}`,
|
||||
method: 'GET'
|
||||
});
|
||||
const qboData = qboRes.getJson ? qboRes.getJson() : qboRes.json;
|
||||
const syncToken = qboData.Customer?.SyncToken;
|
||||
|
||||
if (syncToken !== undefined) {
|
||||
console.log(`🗑️ Deactivating QBO Customer ${customer.qbo_id} (${customer.name})...`);
|
||||
|
||||
await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/customer`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
Id: customer.qbo_id,
|
||||
SyncToken: syncToken,
|
||||
sparse: true,
|
||||
Active: false
|
||||
})
|
||||
});
|
||||
|
||||
console.log(`✅ QBO Customer ${customer.qbo_id} deactivated.`);
|
||||
}
|
||||
} catch (qboError) {
|
||||
console.error(`⚠️ QBO deactivate failed for Customer ${customer.qbo_id}:`, qboError.message);
|
||||
// Trotzdem lokal löschen
|
||||
}
|
||||
}
|
||||
|
||||
// Lokal löschen
|
||||
await pool.query('DELETE FROM customers WHERE id = $1', [id]);
|
||||
res.json({ success: true });
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting customer:', error);
|
||||
res.status(500).json({ error: 'Error deleting customer' });
|
||||
|
|
@ -672,9 +785,63 @@ app.delete('/api/invoices/:id', async (req, res) => {
|
|||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Invoice laden um qbo_id zu prüfen
|
||||
const invResult = await client.query('SELECT qbo_id, qbo_sync_token, invoice_number FROM invoices WHERE id = $1', [id]);
|
||||
if (invResult.rows.length === 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(404).json({ error: 'Invoice not found' });
|
||||
}
|
||||
|
||||
const invoice = invResult.rows[0];
|
||||
|
||||
// In QBO löschen falls vorhanden
|
||||
if (invoice.qbo_id) {
|
||||
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';
|
||||
|
||||
// Aktuellen SyncToken aus QBO holen (sicherste Methode)
|
||||
const qboRes = await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/invoice/${invoice.qbo_id}`,
|
||||
method: 'GET'
|
||||
});
|
||||
const qboData = qboRes.getJson ? qboRes.getJson() : qboRes.json;
|
||||
const syncToken = qboData.Invoice?.SyncToken;
|
||||
|
||||
if (syncToken !== undefined) {
|
||||
// QBO Invoice "voiden" (nicht löschen — QBO empfiehlt Void statt Delete)
|
||||
// Void setzt Balance auf 0 und markiert als nichtig
|
||||
console.log(`🗑️ Voiding QBO Invoice ${invoice.qbo_id} (DocNumber: ${invoice.invoice_number})...`);
|
||||
|
||||
await makeQboApiCall({
|
||||
url: `${baseUrl}/v3/company/${companyId}/invoice?operation=void`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
Id: invoice.qbo_id,
|
||||
SyncToken: syncToken
|
||||
})
|
||||
});
|
||||
|
||||
console.log(`✅ QBO Invoice ${invoice.qbo_id} voided.`);
|
||||
}
|
||||
} catch (qboError) {
|
||||
// QBO-Fehler loggen aber lokales Löschen trotzdem durchführen
|
||||
console.error(`⚠️ QBO void failed for Invoice ${invoice.qbo_id}:`, qboError.message);
|
||||
// Nicht abbrechen — lokal trotzdem löschen
|
||||
}
|
||||
}
|
||||
|
||||
// Lokal löschen (payment_invoices hat ON DELETE CASCADE)
|
||||
await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]);
|
||||
await client.query('DELETE FROM payment_invoices WHERE invoice_id = $1', [id]);
|
||||
await client.query('DELETE FROM invoices WHERE id = $1', [id]);
|
||||
await client.query('COMMIT');
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
|
|
|
|||
Loading…
Reference in New Issue