diff --git a/server.js b/server.js
index 61d3265..a41c61c 100644
--- a/server.js
+++ b/server.js
@@ -108,19 +108,15 @@ async function getNextQuoteNumber() {
}
async function getNextInvoiceNumber() {
- const year = new Date().getFullYear();
const result = await pool.query(
- 'SELECT invoice_number FROM invoices WHERE invoice_number LIKE $1 ORDER BY invoice_number DESC LIMIT 1',
- [`${year}-%`]
+ 'SELECT MAX(CAST(invoice_number AS INTEGER)) as max_number FROM invoices WHERE invoice_number ~ \'^[0-9]+$\''
);
- if (result.rows.length === 0) {
- return `${year}-001`;
+ if (result.rows.length === 0 || result.rows[0].max_number === null) {
+ return '110508';
}
- const lastNumber = parseInt(result.rows[0].invoice_number.split('-')[1]);
- const nextNumber = String(lastNumber + 1).padStart(3, '0');
- return `${year}-${nextNumber}`;
+ return String(parseInt(result.rows[0].max_number) + 1);
}
// Logo endpoints
@@ -419,14 +415,40 @@ app.get('/api/invoices/:id', async (req, res) => {
}
});
+// New endpoint to get next invoice number
+app.get('/api/invoices/next-number', async (req, res) => {
+ try {
+ const nextNumber = await getNextInvoiceNumber();
+ res.json({ next_number: nextNumber });
+ } catch (error) {
+ console.error('Error getting next invoice number:', error);
+ res.status(500).json({ error: 'Error getting next invoice number' });
+ }
+});
+
app.post('/api/invoices', async (req, res) => {
- const { 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 } = req.body;
const client = await pool.connect();
try {
await client.query('BEGIN');
- const invoice_number = await getNextInvoiceNumber();
+ // 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.` });
+ }
let subtotal = 0;
@@ -534,12 +556,29 @@ app.post('/api/quotes/:id/convert-to-invoice', async (req, res) => {
app.put('/api/invoices/:id', async (req, res) => {
const { id } = req.params;
- const { 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 } = 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 (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.` });
+ }
+
let subtotal = 0;
for (const item of items) {
@@ -554,10 +593,10 @@ app.put('/api/invoices/:id', async (req, res) => {
const total = subtotal + tax_amount;
await client.query(
- `UPDATE invoices SET customer_id = $1, invoice_date = $2, terms = $3, auth_code = $4, tax_exempt = $5,
- tax_rate = $6, subtotal = $7, tax_amount = $8, total = $9, updated_at = CURRENT_TIMESTAMP
- WHERE id = $10`,
- [customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, id]
+ `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]
);
await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]);
@@ -598,6 +637,8 @@ app.delete('/api/invoices/:id', async (req, res) => {
}
});
+// PDF Generation code continues below...
+
// PDF Generation using templates and persistent browser
app.get('/api/quotes/:id/pdf', async (req, res) => {
const { id } = req.params;
@@ -858,8 +899,6 @@ app.get('/api/invoices/:id/pdf', async (req, res) => {
});
-// Nach den PDF-Endpoints, vor "Start server", einfügen:
-
// HTML Debug Endpoints
app.get('/api/quotes/:id/html', async (req, res) => {
const { id } = req.params;
diff --git a/templates/invoice-template.html b/templates/invoice-template.html
index f23791a..f132d72 100644
--- a/templates/invoice-template.html
+++ b/templates/invoice-template.html
@@ -26,7 +26,7 @@
display: flex;
justify-content: space-between;
align-items: flex-start;
- margin-bottom: 40px;
+ margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 2px solid #333;
position: relative;
@@ -70,7 +70,7 @@
.bill-to-section {
display: flex;
justify-content: space-between;
- margin: 30px 0 40px 0;
+ margin: 0px 0 20px 0;
}
.bill-to {
@@ -166,7 +166,7 @@
.items-table td.rate,
.items-table td.amount {
text-align: right;
- width: 100px;
+ width: 70px;
}
.footer-row td {
@@ -220,11 +220,9 @@
Providing IT Services and Support in South Texas Since 1996
- Phone:
(361) 765-8400
(361) 765-8401
(361) 232-6578
- Email:
accounting@bayarea-cc.com
diff --git a/templates/quote-template.html b/templates/quote-template.html
index feb9ce4..312ccaa 100644
--- a/templates/quote-template.html
+++ b/templates/quote-template.html
@@ -26,7 +26,7 @@
display: flex;
justify-content: space-between;
align-items: flex-start;
- margin-bottom: 40px;
+ margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 2px solid #333;
position: relative;
@@ -70,7 +70,7 @@
.bill-to-section {
display: flex;
justify-content: space-between;
- margin: 30px 0 60px 0;
+ margin: 0px 0 20px 0;
}
.bill-to {
@@ -166,7 +166,7 @@
.items-table td.rate,
.items-table td.amount {
text-align: right;
- width: 100px;
+ width: 70px;
}
.footer-row td {
@@ -220,11 +220,9 @@