update
This commit is contained in:
109
server.js
109
server.js
@@ -457,31 +457,32 @@ app.get('/api/invoices/:id', async (req, res) => {
|
||||
});
|
||||
|
||||
app.post('/api/invoices', async (req, res) => {
|
||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, created_from_quote_id } = req.body;
|
||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, created_from_quote_id, scheduled_send_date } = req.body;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Validate invoice_number is provided and is numeric
|
||||
if (!invoice_number || !/^\d+$/.test(invoice_number)) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: 'Invalid invoice number. Must be a numeric value.' });
|
||||
}
|
||||
|
||||
// Check if invoice number already exists
|
||||
const existingInvoice = await client.query(
|
||||
'SELECT id FROM invoices WHERE invoice_number = $1',
|
||||
[invoice_number]
|
||||
);
|
||||
|
||||
if (existingInvoice.rows.length > 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: `Invoice number ${invoice_number} already exists.` });
|
||||
// invoice_number ist jetzt OPTIONAL — wird erst beim QBO Export vergeben
|
||||
// Wenn angegeben, muss sie numerisch sein und darf nicht existieren
|
||||
if (invoice_number && invoice_number.trim() !== '') {
|
||||
if (!/^\d+$/.test(invoice_number)) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: 'Invalid invoice number. Must be a numeric value.' });
|
||||
}
|
||||
|
||||
const existingInvoice = await client.query(
|
||||
'SELECT id FROM invoices WHERE invoice_number = $1',
|
||||
[invoice_number]
|
||||
);
|
||||
|
||||
if (existingInvoice.rows.length > 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: `Invoice number ${invoice_number} already exists.` });
|
||||
}
|
||||
}
|
||||
|
||||
let subtotal = 0;
|
||||
|
||||
for (const item of items) {
|
||||
const amount = parseFloat(item.amount.replace(/[$,]/g, ''));
|
||||
if (!isNaN(amount)) {
|
||||
@@ -493,19 +494,22 @@ app.post('/api/invoices', async (req, res) => {
|
||||
const tax_amount = tax_exempt ? 0 : (subtotal * tax_rate / 100);
|
||||
const total = subtotal + tax_amount;
|
||||
|
||||
// invoice_number kann NULL sein
|
||||
const invNum = (invoice_number && invoice_number.trim() !== '') ? invoice_number : null;
|
||||
const sendDate = scheduled_send_date || null;
|
||||
|
||||
const invoiceResult = await client.query(
|
||||
`INSERT INTO invoices (invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, created_from_quote_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *`,
|
||||
[invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, created_from_quote_id]
|
||||
`INSERT INTO invoices (invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, created_from_quote_id, scheduled_send_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
||||
[invNum, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, created_from_quote_id, sendDate]
|
||||
);
|
||||
|
||||
const invoiceId = invoiceResult.rows[0].id;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
await client.query(
|
||||
// qbo_item_id hinzugefügt
|
||||
'INSERT INTO invoice_items (invoice_id, quantity, description, rate, amount, item_order, qbo_item_id) VALUES ($1, $2, $3, $4, $5, $6, $7)',
|
||||
[invoiceId, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9'] // Default '9' (Parts)
|
||||
[invoiceId, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -556,7 +560,7 @@ app.post('/api/quotes/:id/convert-to-invoice', async (req, res) => {
|
||||
return res.status(400).json({ error: 'Cannot convert quote with TBD items to invoice. Please update all TBD items first.' });
|
||||
}
|
||||
|
||||
const invoice_number = await getNextInvoiceNumber();
|
||||
const invoice_number = null;
|
||||
const invoiceDate = new Date().toISOString().split('T')[0];
|
||||
|
||||
const invoiceResult = await client.query(
|
||||
@@ -588,31 +592,32 @@ app.post('/api/quotes/:id/convert-to-invoice', async (req, res) => {
|
||||
|
||||
app.put('/api/invoices/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items } = req.body;
|
||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, scheduled_send_date } = req.body;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Validate invoice_number is provided and is numeric
|
||||
if (!invoice_number || !/^\d+$/.test(invoice_number)) {
|
||||
// invoice_number ist optional. Wenn angegeben und nicht leer, muss sie numerisch sein
|
||||
const invNum = (invoice_number && invoice_number.trim() !== '') ? invoice_number : null;
|
||||
|
||||
if (invNum && !/^\d+$/.test(invNum)) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: 'Invalid invoice number. Must be a numeric value.' });
|
||||
}
|
||||
|
||||
// Check if invoice number already exists (excluding current invoice)
|
||||
const existingInvoice = await client.query(
|
||||
'SELECT id FROM invoices WHERE invoice_number = $1 AND id != $2',
|
||||
[invoice_number, id]
|
||||
);
|
||||
|
||||
if (existingInvoice.rows.length > 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: `Invoice number ${invoice_number} already exists.` });
|
||||
if (invNum) {
|
||||
const existingInvoice = await client.query(
|
||||
'SELECT id FROM invoices WHERE invoice_number = $1 AND id != $2',
|
||||
[invNum, id]
|
||||
);
|
||||
if (existingInvoice.rows.length > 0) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(400).json({ error: `Invoice number ${invNum} already exists.` });
|
||||
}
|
||||
}
|
||||
|
||||
let subtotal = 0;
|
||||
|
||||
for (const item of items) {
|
||||
const amount = parseFloat(item.amount.replace(/[$,]/g, ''));
|
||||
if (!isNaN(amount)) {
|
||||
@@ -623,12 +628,13 @@ app.put('/api/invoices/:id', async (req, res) => {
|
||||
const tax_rate = 8.25;
|
||||
const tax_amount = tax_exempt ? 0 : (subtotal * tax_rate / 100);
|
||||
const total = subtotal + tax_amount;
|
||||
const sendDate = scheduled_send_date || null;
|
||||
|
||||
await client.query(
|
||||
`UPDATE invoices SET invoice_number = $1, customer_id = $2, invoice_date = $3, terms = $4, auth_code = $5, tax_exempt = $6,
|
||||
tax_rate = $7, subtotal = $8, tax_amount = $9, total = $10, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $11`,
|
||||
[invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, id]
|
||||
tax_rate = $7, subtotal = $8, tax_amount = $9, total = $10, scheduled_send_date = $11, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $12`,
|
||||
[invNum, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, sendDate, id]
|
||||
);
|
||||
|
||||
await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]);
|
||||
@@ -651,6 +657,7 @@ app.put('/api/invoices/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.delete('/api/invoices/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const client = await pool.connect();
|
||||
@@ -1580,7 +1587,29 @@ app.patch('/api/invoices/:id/mark-unpaid', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.patch('/api/invoices/:id/reset-qbo', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE invoices
|
||||
SET qbo_id = NULL, qbo_sync_token = NULL, qbo_doc_number = NULL, invoice_number = NULL,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Invoice not found' });
|
||||
}
|
||||
|
||||
console.log(`🔄 Invoice ID ${id} QBO-Verknüpfung zurückgesetzt`);
|
||||
res.json(result.rows[0]);
|
||||
} catch (error) {
|
||||
console.error('Error resetting QBO link:', error);
|
||||
res.status(500).json({ error: 'Error resetting QBO link' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user