Technician
This commit is contained in:
@@ -167,6 +167,23 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div id="upload-status" class="mt-4"></div>
|
<div id="upload-status" class="mt-4"></div>
|
||||||
|
<hr class="my-8 border-gray-200">
|
||||||
|
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">Invoice Workers</h3>
|
||||||
|
<p class="text-gray-600 mb-4">
|
||||||
|
Comma-separated list of workers available in the invoice "Worker" dropdown.
|
||||||
|
Example: <code class="bg-gray-100 px-1 rounded">Wallie, Elvin, Andreas</code>
|
||||||
|
</p>
|
||||||
|
<div class="mb-4">
|
||||||
|
<input type="text" id="workers-input"
|
||||||
|
class="w-full max-w-lg px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="Wallie, Elvin, Andreas">
|
||||||
|
</div>
|
||||||
|
<button onclick="saveWorkerSettings()"
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg">
|
||||||
|
Save Worker List
|
||||||
|
</button>
|
||||||
|
<div id="workers-status" class="mt-4"></div>
|
||||||
|
|
||||||
<hr class="my-8 border-gray-200">
|
<hr class="my-8 border-gray-200">
|
||||||
|
|
||||||
@@ -448,12 +465,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div class="col-span-2">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Authorization (optional)</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Authorization (optional)</label>
|
||||||
<input type="text" id="invoice-authorization"
|
<input type="text" id="invoice-authorization"
|
||||||
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||||||
placeholder="P.O. Number, Authorization Code, etc.">
|
placeholder="P.O. Number, Authorization Code, etc.">
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">Worker</label>
|
||||||
|
<select id="invoice-worker"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="">— None —</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between items-center mb-3">
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
|||||||
@@ -8,9 +8,35 @@
|
|||||||
*/
|
*/
|
||||||
import { addItem, getItems, resetItemCounter } from '../utils/item-editor.js';
|
import { addItem, getItems, resetItemCounter } from '../utils/item-editor.js';
|
||||||
import { setDefaultDate, showSpinner, hideSpinner } from '../utils/helpers.js';
|
import { setDefaultDate, showSpinner, hideSpinner } from '../utils/helpers.js';
|
||||||
|
import '../utils/api.js';
|
||||||
|
|
||||||
let currentInvoiceId = null;
|
let currentInvoiceId = null;
|
||||||
let qboLaborRate = null;
|
let qboLaborRate = null;
|
||||||
|
let workerList = [];
|
||||||
|
|
||||||
|
export async function loadWorkers() {
|
||||||
|
try {
|
||||||
|
const result = await window.API.settings.get('invoice_workers');
|
||||||
|
if (result && result.value) {
|
||||||
|
workerList = result.value.split(',').map(w => w.trim()).filter(Boolean);
|
||||||
|
} else {
|
||||||
|
workerList = [];
|
||||||
|
}
|
||||||
|
populateWorkerDropdown();
|
||||||
|
console.log(`👷 ${workerList.length} Bearbeiter geladen`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Worker-Liste konnte nicht geladen werden.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateWorkerDropdown() {
|
||||||
|
const sel = document.getElementById('invoice-worker');
|
||||||
|
if (!sel) return;
|
||||||
|
const current = sel.value;
|
||||||
|
sel.innerHTML = `<option value="">— None —</option>` +
|
||||||
|
workerList.map(w => `<option value="${w}">${w}</option>`).join('');
|
||||||
|
if (current) sel.value = current;
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadLaborRate() {
|
export async function loadLaborRate() {
|
||||||
try {
|
try {
|
||||||
@@ -180,6 +206,11 @@ async function loadInvoiceForEdit(invoiceId) {
|
|||||||
document.getElementById('invoice-tax-exempt').checked = data.invoice.tax_exempt;
|
document.getElementById('invoice-tax-exempt').checked = data.invoice.tax_exempt;
|
||||||
document.getElementById('invoice-bill-to-name').value = data.invoice.bill_to_name || '';
|
document.getElementById('invoice-bill-to-name').value = data.invoice.bill_to_name || '';
|
||||||
|
|
||||||
|
// Worker
|
||||||
|
populateWorkerDropdown();
|
||||||
|
const workerEl = document.getElementById('invoice-worker');
|
||||||
|
if (workerEl) workerEl.value = data.invoice.worker || '';
|
||||||
|
|
||||||
const sendDateEl = document.getElementById('invoice-send-date');
|
const sendDateEl = document.getElementById('invoice-send-date');
|
||||||
if (sendDateEl) {
|
if (sendDateEl) {
|
||||||
sendDateEl.value = data.invoice.scheduled_send_date
|
sendDateEl.value = data.invoice.scheduled_send_date
|
||||||
@@ -227,6 +258,11 @@ function prepareNewInvoice() {
|
|||||||
document.getElementById('invoice-number').value = '';
|
document.getElementById('invoice-number').value = '';
|
||||||
document.getElementById('invoice-send-date').value = '';
|
document.getElementById('invoice-send-date').value = '';
|
||||||
|
|
||||||
|
// Worker zurücksetzen
|
||||||
|
populateWorkerDropdown();
|
||||||
|
const workerEl = document.getElementById('invoice-worker');
|
||||||
|
if (workerEl) workerEl.value = '';
|
||||||
|
|
||||||
// Reset recurring
|
// Reset recurring
|
||||||
const recurringCb = document.getElementById('invoice-recurring');
|
const recurringCb = document.getElementById('invoice-recurring');
|
||||||
const recurringGroup = document.getElementById('invoice-recurring-group');
|
const recurringGroup = document.getElementById('invoice-recurring-group');
|
||||||
@@ -278,6 +314,7 @@ export async function handleInvoiceSubmit(e) {
|
|||||||
tax_exempt: document.getElementById('invoice-tax-exempt').checked,
|
tax_exempt: document.getElementById('invoice-tax-exempt').checked,
|
||||||
scheduled_send_date: document.getElementById('invoice-send-date')?.value || null,
|
scheduled_send_date: document.getElementById('invoice-send-date')?.value || null,
|
||||||
bill_to_name: document.getElementById('invoice-bill-to-name')?.value || null,
|
bill_to_name: document.getElementById('invoice-bill-to-name')?.value || null,
|
||||||
|
worker: document.getElementById('invoice-worker')?.value || null,
|
||||||
is_recurring: isRecurring,
|
is_recurring: isRecurring,
|
||||||
recurring_interval: recurringInterval,
|
recurring_interval: recurringInterval,
|
||||||
items: getItems('invoice-items')
|
items: getItems('invoice-items')
|
||||||
@@ -315,6 +352,8 @@ export function initInvoiceModal() {
|
|||||||
const form = document.getElementById('invoice-form');
|
const form = document.getElementById('invoice-form');
|
||||||
if (form) form.addEventListener('submit', handleInvoiceSubmit);
|
if (form) form.addEventListener('submit', handleInvoiceSubmit);
|
||||||
|
|
||||||
|
loadWorkers(); // Bearbeiterliste laden
|
||||||
|
|
||||||
const taxExempt = document.getElementById('invoice-tax-exempt');
|
const taxExempt = document.getElementById('invoice-tax-exempt');
|
||||||
if (taxExempt) taxExempt.addEventListener('change', updateInvoiceTotals);
|
if (taxExempt) taxExempt.addEventListener('change', updateInvoiceTotals);
|
||||||
|
|
||||||
@@ -343,3 +382,4 @@ export function initInvoiceModal() {
|
|||||||
window.openInvoiceModal = openInvoiceModal;
|
window.openInvoiceModal = openInvoiceModal;
|
||||||
window.closeInvoiceModal = closeInvoiceModal;
|
window.closeInvoiceModal = closeInvoiceModal;
|
||||||
window.addInvoiceItem = addInvoiceItem;
|
window.addInvoiceItem = addInvoiceItem;
|
||||||
|
window.reloadInvoiceWorkers = loadWorkers;
|
||||||
@@ -212,7 +212,13 @@ const API = {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(r => r.json());
|
}).then(r => r.json());
|
||||||
}
|
},
|
||||||
|
get: (key) => fetch(`/api/settings/${encodeURIComponent(key)}`).then(r => r.json()),
|
||||||
|
set: (key, value) => fetch(`/api/settings/${encodeURIComponent(key)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ value })
|
||||||
|
}).then(r => r.json())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ let invoices = [];
|
|||||||
let filterCustomer = localStorage.getItem('inv_filterCustomer') || '';
|
let filterCustomer = localStorage.getItem('inv_filterCustomer') || '';
|
||||||
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
|
let filterStatus = localStorage.getItem('inv_filterStatus') || 'unpaid';
|
||||||
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
|
let groupBy = localStorage.getItem('inv_groupBy') || 'none';
|
||||||
|
let filterWorker = localStorage.getItem('inv_filterWorker') || '';
|
||||||
|
|
||||||
const OVERDUE_DAYS = 30;
|
const OVERDUE_DAYS = 30;
|
||||||
|
|
||||||
@@ -194,6 +195,7 @@ function saveSettings() {
|
|||||||
localStorage.setItem('inv_filterStatus', filterStatus);
|
localStorage.setItem('inv_filterStatus', filterStatus);
|
||||||
localStorage.setItem('inv_groupBy', groupBy);
|
localStorage.setItem('inv_groupBy', groupBy);
|
||||||
localStorage.setItem('inv_filterCustomer', filterCustomer);
|
localStorage.setItem('inv_filterCustomer', filterCustomer);
|
||||||
|
localStorage.setItem('inv_filterWorker', filterWorker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -237,6 +239,13 @@ function getFilteredInvoices() {
|
|||||||
const s = filterCustomer.toLowerCase();
|
const s = filterCustomer.toLowerCase();
|
||||||
f = f.filter(i => (i.customer_name || '').toLowerCase().includes(s));
|
f = f.filter(i => (i.customer_name || '').toLowerCase().includes(s));
|
||||||
}
|
}
|
||||||
|
if (filterWorker) {
|
||||||
|
if (filterWorker === '__none__') {
|
||||||
|
f = f.filter(i => !i.worker);
|
||||||
|
} else {
|
||||||
|
f = f.filter(i => i.worker === filterWorker);
|
||||||
|
}
|
||||||
|
}
|
||||||
f.sort((a, b) => (parseLocalDate(b.invoice_date) || 0) - (parseLocalDate(a.invoice_date) || 0));
|
f.sort((a, b) => (parseLocalDate(b.invoice_date) || 0) - (parseLocalDate(a.invoice_date) || 0));
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
@@ -563,6 +572,13 @@ export function injectToolbar() {
|
|||||||
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm w-48 focus:ring-blue-500 focus:border-blue-500">
|
class="px-3 py-1.5 border border-gray-300 rounded-md text-sm w-48 focus:ring-blue-500 focus:border-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div class="w-px h-8 bg-gray-300"></div>
|
<div class="w-px h-8 bg-gray-300"></div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="text-sm font-medium text-gray-700">Worker:</label>
|
||||||
|
<select id="invoice-filter-worker" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm bg-white">
|
||||||
|
<option value="">All</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="w-px h-8 bg-gray-300"></div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label class="text-sm font-medium text-gray-700">Group:</label>
|
<label class="text-sm font-medium text-gray-700">Group:</label>
|
||||||
<select id="invoice-group-by" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm bg-white">
|
<select id="invoice-group-by" class="px-3 py-1.5 border border-gray-300 rounded-md text-sm bg-white">
|
||||||
@@ -592,6 +608,11 @@ export function injectToolbar() {
|
|||||||
document.getElementById('invoice-group-by').addEventListener('change', (e) => {
|
document.getElementById('invoice-group-by').addEventListener('change', (e) => {
|
||||||
groupBy = e.target.value; saveSettings(); renderInvoiceView();
|
groupBy = e.target.value; saveSettings(); renderInvoiceView();
|
||||||
});
|
});
|
||||||
|
// Worker-Filter befüllen und verdrahten
|
||||||
|
populateWorkerFilter();
|
||||||
|
document.getElementById('invoice-filter-worker').addEventListener('change', (e) => {
|
||||||
|
filterWorker = e.target.value; saveSettings(); renderInvoiceView();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -806,6 +827,33 @@ async function _saveSentDates(invoiceId) {
|
|||||||
alert('Network error.');
|
alert('Network error.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function populateWorkerFilter() {
|
||||||
|
const sel = document.getElementById('invoice-filter-worker');
|
||||||
|
if (!sel) return;
|
||||||
|
|
||||||
|
// Worker aus den geladenen Rechnungen
|
||||||
|
const fromInvoices = new Set(invoices.map(i => i.worker).filter(Boolean));
|
||||||
|
|
||||||
|
// Plus konfigurierte Liste aus Settings
|
||||||
|
try {
|
||||||
|
const result = await window.API.settings.get('invoice_workers');
|
||||||
|
if (result && result.value) {
|
||||||
|
result.value.split(',').map(w => w.trim()).filter(Boolean)
|
||||||
|
.forEach(w => fromInvoices.add(w));
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
|
||||||
|
const workers = [...fromInvoices].sort();
|
||||||
|
const hasNoWorker = invoices.some(i => !i.worker);
|
||||||
|
|
||||||
|
sel.innerHTML =
|
||||||
|
`<option value="">All</option>` +
|
||||||
|
workers.map(w => `<option value="${w}">${w}</option>`).join('') +
|
||||||
|
(hasNoWorker ? `<option value="__none__">— No worker —</option>` : '');
|
||||||
|
|
||||||
|
sel.value = filterWorker;
|
||||||
|
}
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Expose
|
// Expose
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* settings-view.js — Logo upload, QBO import, QBO connection test
|
* settings-view.js — Logo upload, QBO import, QBO connection test
|
||||||
* Extracted from app.js
|
* Extracted from app.js
|
||||||
*/
|
*/
|
||||||
|
import '../utils/api.js';
|
||||||
|
|
||||||
let currentLogoFile = null;
|
let currentLogoFile = null;
|
||||||
|
|
||||||
@@ -69,6 +70,8 @@ export function initSettingsView() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadWorkerSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkQboOverdue() {
|
export async function checkQboOverdue() {
|
||||||
@@ -175,8 +178,42 @@ export async function importFromQBO() {
|
|||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function loadWorkerSettings() {
|
||||||
|
try {
|
||||||
|
const result = await window.API.settings.get('invoice_workers');
|
||||||
|
const input = document.getElementById('workers-input');
|
||||||
|
if (input) input.value = (result && result.value) ? result.value : '';
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading worker settings:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveWorkerSettings() {
|
||||||
|
const input = document.getElementById('workers-input');
|
||||||
|
const statusEl = document.getElementById('workers-status');
|
||||||
|
if (!input) return;
|
||||||
|
|
||||||
|
const value = input.value.trim();
|
||||||
|
statusEl.innerHTML = '<p class="text-blue-600">Saving...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await window.API.settings.set('invoice_workers', value);
|
||||||
|
if (result.success) {
|
||||||
|
statusEl.innerHTML = '<p class="text-green-600">✓ Worker list saved.</p>';
|
||||||
|
// Dropdown im Invoice-Modal aktualisieren
|
||||||
|
if (typeof window.reloadInvoiceWorkers === 'function') {
|
||||||
|
window.reloadInvoiceWorkers();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusEl.innerHTML = `<p class="text-red-600">✗ ${result.error || 'Save failed'}</p>`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving worker settings:', e);
|
||||||
|
statusEl.innerHTML = '<p class="text-red-600">✗ Save failed</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
// Expose for onclick handlers
|
// Expose for onclick handlers
|
||||||
window.uploadLogo = uploadLogo;
|
window.uploadLogo = uploadLogo;
|
||||||
window.checkQboOverdue = checkQboOverdue;
|
window.checkQboOverdue = checkQboOverdue;
|
||||||
window.importFromQBO = importFromQBO;
|
window.importFromQBO = importFromQBO;
|
||||||
|
window.saveWorkerSettings = saveWorkerSettings;
|
||||||
@@ -122,7 +122,7 @@ app.use('/api/invoices', invoiceRoutes);
|
|||||||
app.use('/api/payments', paymentRoutes);
|
app.use('/api/payments', paymentRoutes);
|
||||||
app.use('/api/qbo', qboRoutes);
|
app.use('/api/qbo', qboRoutes);
|
||||||
app.use('/api/accounting', accountingRoutes);
|
app.use('/api/accounting', accountingRoutes);
|
||||||
app.use('/api', settingsRoutes);
|
app.use('/api/settings', settingsRoutes);
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ router.get('/:id', async (req, res) => {
|
|||||||
|
|
||||||
// POST create invoice
|
// POST create invoice
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, scheduled_send_date, bill_to_name, created_from_quote_id, is_recurring, recurring_interval } = req.body;
|
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, scheduled_send_date, bill_to_name, created_from_quote_id, is_recurring, recurring_interval, worker } = req.body;
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
@@ -184,9 +184,9 @@ router.post('/', async (req, res) => {
|
|||||||
const total = subtotal + tax_amount;
|
const total = subtotal + tax_amount;
|
||||||
const next_recurring_date = is_recurring ? calculateNextRecurringDate(invoice_date, recurring_interval) : null;
|
const next_recurring_date = is_recurring ? calculateNextRecurringDate(invoice_date, recurring_interval) : null;
|
||||||
const invoiceResult = await client.query(
|
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, scheduled_send_date, bill_to_name, created_from_quote_id, is_recurring, recurring_interval, next_recurring_date)
|
`INSERT INTO invoices (invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date, bill_to_name, created_from_quote_id, is_recurring, recurring_interval, next_recurring_date, worker)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING *`,
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *`,
|
||||||
[tempNumber, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, created_from_quote_id, is_recurring || false, recurring_interval || null, next_recurring_date]
|
[tempNumber, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, created_from_quote_id, is_recurring || false, recurring_interval || null, next_recurring_date, worker || null]
|
||||||
);
|
);
|
||||||
const invoiceId = invoiceResult.rows[0].id;
|
const invoiceId = invoiceResult.rows[0].id;
|
||||||
|
|
||||||
@@ -228,8 +228,7 @@ router.post('/', async (req, res) => {
|
|||||||
// PUT update invoice
|
// PUT update invoice
|
||||||
router.put('/:id', async (req, res) => {
|
router.put('/:id', async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, scheduled_send_date, bill_to_name, is_recurring, recurring_interval } = req.body;
|
const { invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, items, scheduled_send_date, bill_to_name, is_recurring, recurring_interval, worker } = req.body;
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
@@ -278,16 +277,16 @@ router.put('/:id', async (req, res) => {
|
|||||||
if (invoice_number) {
|
if (invoice_number) {
|
||||||
await client.query(
|
await client.query(
|
||||||
`UPDATE invoices SET invoice_number = $1, customer_id = $2, invoice_date = $3, terms = $4, auth_code = $5, tax_exempt = $6,
|
`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, scheduled_send_date = $11, bill_to_name = $12, updated_at = CURRENT_TIMESTAMP
|
tax_rate = $7, subtotal = $8, tax_amount = $9, total = $10, scheduled_send_date = $11, bill_to_name = $12, worker = $13, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $13`,
|
WHERE id = $14`,
|
||||||
[invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, id]
|
[invoice_number, customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, worker || null, id]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await client.query(
|
await client.query(
|
||||||
`UPDATE invoices SET customer_id = $1, invoice_date = $2, terms = $3, auth_code = $4, tax_exempt = $5,
|
`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, scheduled_send_date = $10, bill_to_name = $11, updated_at = CURRENT_TIMESTAMP
|
tax_rate = $6, subtotal = $7, tax_amount = $8, total = $9, scheduled_send_date = $10, bill_to_name = $11, worker = $12, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $12`,
|
WHERE id = $13`,
|
||||||
[customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, id]
|
[customer_id, invoice_date, terms, auth_code, tax_exempt, tax_rate, subtotal, tax_amount, total, scheduled_send_date || null, bill_to_name || null, worker || null, id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,4 +68,41 @@ router.post('/upload-logo', upload.single('logo'), async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/settings/:key — liefert einen einzelnen Settings-Wert
|
||||||
|
router.get('/:key', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
'SELECT value FROM settings WHERE key = $1',
|
||||||
|
[req.params.key]
|
||||||
|
);
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res.json({ key: req.params.key, value: null });
|
||||||
|
}
|
||||||
|
res.json({ key: req.params.key, value: result.rows[0].value });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching setting:', error);
|
||||||
|
res.status(500).json({ error: 'Error fetching setting' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PUT /api/settings/:key — setzt einen Settings-Wert (upsert)
|
||||||
|
router.put('/:key', express.json(), async (req, res) => {
|
||||||
|
const { value } = req.body;
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return res.status(400).json({ error: 'value must be a string' });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO settings (key, value)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`,
|
||||||
|
[req.params.key, value]
|
||||||
|
);
|
||||||
|
res.json({ success: true, key: req.params.key, value });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving setting:', error);
|
||||||
|
res.status(500).json({ error: 'Error saving setting' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user