update
This commit is contained in:
@@ -1,115 +1,169 @@
|
||||
/**
|
||||
* Accounting Routes
|
||||
* /api/accounting/*
|
||||
* Accounting Routes — /api/accounting/*
|
||||
*
|
||||
* Phase 1 — read-only:
|
||||
* GET /accounts
|
||||
* POST /sync-accounts (Phase-1 Stub)
|
||||
* GET /register
|
||||
* GET /reports/profit-loss
|
||||
* GET /reports/balance-sheet
|
||||
* Phase 1 + Phase 2 Lieferung 1 (Sync, Cache-Reads, Sync-Status)
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const accountingService = require('../services/accounting-service');
|
||||
|
||||
/**
|
||||
* Helper: einheitliches Error-Mapping QBO → HTTP
|
||||
* Fault-Details landen im Server-Log, der User sieht die kurze Message.
|
||||
*/
|
||||
// ────────────────────────────────────────────────────────────────────
|
||||
function handleQboError(err, res, context) {
|
||||
console.error(`❌ Accounting/${context} error:`, err.message);
|
||||
if (err.qboFault) {
|
||||
console.error(' QBO Fault detail:', JSON.stringify(err.qboFault));
|
||||
}
|
||||
if (err.qboFault) console.error(' QBO Fault detail:', JSON.stringify(err.qboFault));
|
||||
if (err.stack) console.error(err.stack);
|
||||
|
||||
res.status(500).json({
|
||||
error: err.message || 'QBO request failed',
|
||||
context
|
||||
});
|
||||
res.status(500).json({ error: err.message || 'QBO request failed', context });
|
||||
}
|
||||
|
||||
// ─── GET /api/accounting/accounts ───────────────────────────────────
|
||||
// Optional ?type=Bank|CreditCard|Expense|Income|... ?activeOnly=false
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// Phase 1 — read-only
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get('/accounts', async (req, res) => {
|
||||
try {
|
||||
const type = req.query.type || null;
|
||||
const activeOnly = req.query.activeOnly === 'false' ? false : true;
|
||||
|
||||
const accounts = await accountingService.listAccounts({ type, activeOnly });
|
||||
const accounts = await accountingService.listAccounts({
|
||||
type: req.query.type || null,
|
||||
activeOnly: req.query.activeOnly !== 'false'
|
||||
});
|
||||
res.json(accounts);
|
||||
} catch (err) {
|
||||
handleQboError(err, res, 'accounts');
|
||||
}
|
||||
} catch (err) { handleQboError(err, res, 'accounts'); }
|
||||
});
|
||||
|
||||
// ─── POST /api/accounting/sync-accounts ─────────────────────────────
|
||||
// Phase-1 Stub. Voll implementiert in Phase 2 mit qbo_account_cache.
|
||||
router.post('/sync-accounts', (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
synced: 0,
|
||||
cached: false,
|
||||
message:
|
||||
'Account-Sync wird in Phase 2 aktiviert (qbo_account_cache). ' +
|
||||
'In Phase 1 werden Accounts direkt live aus QBO geladen.'
|
||||
});
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/register ───────────────────────────────────
|
||||
// ?accountId=<id>&startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
|
||||
router.get('/register', async (req, res) => {
|
||||
const { accountId, startDate, endDate } = req.query;
|
||||
|
||||
if (!accountId) {
|
||||
return res.status(400).json({ error: 'accountId is required' });
|
||||
}
|
||||
|
||||
if (!accountId) return res.status(400).json({ error: 'accountId is required' });
|
||||
try {
|
||||
const result = await accountingService.getRegister({
|
||||
accountId,
|
||||
startDate,
|
||||
endDate
|
||||
});
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
handleQboError(err, res, 'register');
|
||||
}
|
||||
res.json(await accountingService.getRegister({ accountId, startDate, endDate }));
|
||||
} catch (err) { handleQboError(err, res, 'register'); }
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/reports/profit-loss ────────────────────────
|
||||
// ?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD&accountingMethod=Accrual|Cash
|
||||
router.get('/reports/profit-loss', async (req, res) => {
|
||||
const { startDate, endDate, accountingMethod } = req.query;
|
||||
|
||||
try {
|
||||
const data = await accountingService.getProfitAndLoss({
|
||||
startDate,
|
||||
endDate,
|
||||
accountingMethod: accountingMethod || 'Accrual'
|
||||
});
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
handleQboError(err, res, 'profit-loss');
|
||||
}
|
||||
res.json(await accountingService.getProfitAndLoss({
|
||||
startDate: req.query.startDate,
|
||||
endDate: req.query.endDate,
|
||||
accountingMethod: req.query.accountingMethod || 'Accrual'
|
||||
}));
|
||||
} catch (err) { handleQboError(err, res, 'profit-loss'); }
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/reports/balance-sheet ──────────────────────
|
||||
// ?asOfDate=YYYY-MM-DD&accountingMethod=Accrual|Cash
|
||||
router.get('/reports/balance-sheet', async (req, res) => {
|
||||
const { asOfDate, accountingMethod } = req.query;
|
||||
|
||||
try {
|
||||
const data = await accountingService.getBalanceSheet({
|
||||
asOfDate,
|
||||
accountingMethod: accountingMethod || 'Accrual'
|
||||
res.json(await accountingService.getBalanceSheet({
|
||||
asOfDate: req.query.asOfDate,
|
||||
accountingMethod: req.query.accountingMethod || 'Accrual'
|
||||
}));
|
||||
} catch (err) { handleQboError(err, res, 'balance-sheet'); }
|
||||
});
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
// Phase 2 Lieferung 1 — Sync + Cache-Reads
|
||||
// ════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ─── POST /api/accounting/sync-accounts ─────────────────────────────
|
||||
// Triggert den Account-Cache-Sync. Vorher: No-Op-Stub. Jetzt: voll.
|
||||
router.post('/sync-accounts', async (req, res) => {
|
||||
try {
|
||||
console.log('🔄 Syncing QBO accounts cache...');
|
||||
const result = await accountingService.syncAccountsCache();
|
||||
console.log(`✅ Synced ${result.synced} accounts in ${result.durationMs}ms`);
|
||||
res.json({
|
||||
success: true,
|
||||
cacheName: 'accounts',
|
||||
synced: result.synced,
|
||||
durationMs: result.durationMs
|
||||
});
|
||||
} catch (err) { handleQboError(err, res, 'sync-accounts'); }
|
||||
});
|
||||
|
||||
// ─── POST /api/accounting/sync-vendors ──────────────────────────────
|
||||
router.post('/sync-vendors', async (req, res) => {
|
||||
try {
|
||||
console.log('🔄 Syncing QBO vendors cache...');
|
||||
const result = await accountingService.syncVendorsCache();
|
||||
console.log(`✅ Synced ${result.synced} vendors in ${result.durationMs}ms`);
|
||||
res.json({
|
||||
success: true,
|
||||
cacheName: 'vendors',
|
||||
synced: result.synced,
|
||||
durationMs: result.durationMs
|
||||
});
|
||||
} catch (err) { handleQboError(err, res, 'sync-vendors'); }
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/sync-status ────────────────────────────────
|
||||
// Liefert Status beider Caches + Flag, ob heute schon synchronisiert wurde.
|
||||
router.get('/sync-status', async (req, res) => {
|
||||
try {
|
||||
const accounts = await accountingService.getCacheStatus('accounts');
|
||||
const vendors = await accountingService.getCacheStatus('vendors');
|
||||
|
||||
const accountsStaleToday = await accountingService.cacheIsStaleToday('accounts');
|
||||
const vendorsStaleToday = await accountingService.cacheIsStaleToday('vendors');
|
||||
|
||||
res.json({
|
||||
accounts: { ...accounts, staleToday: accountsStaleToday },
|
||||
vendors: { ...vendors, staleToday: vendorsStaleToday }
|
||||
});
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
handleQboError(err, res, 'balance-sheet');
|
||||
console.error('❌ sync-status error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// ─── GET /api/accounting/vendors ────────────────────────────────────
|
||||
// Aus Cache. Optional ?search=<q>&activeOnly=true&limit=200
|
||||
router.get('/vendors', async (req, res) => {
|
||||
try {
|
||||
const vendors = await accountingService.getVendorsFromCache({
|
||||
search: req.query.search || '',
|
||||
activeOnly: req.query.activeOnly !== 'false',
|
||||
limit: req.query.limit ? Math.min(parseInt(req.query.limit, 10) || 200, 1000) : 200
|
||||
});
|
||||
res.json(vendors);
|
||||
} catch (err) {
|
||||
console.error('❌ vendors error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/expense-accounts ───────────────────────────
|
||||
// Aus Cache. Liefert alle Expense-Accounts (für Category-Dropdown).
|
||||
router.get('/expense-accounts', async (req, res) => {
|
||||
try {
|
||||
const accounts = await accountingService.getExpenseAccountsFromCache({
|
||||
activeOnly: req.query.activeOnly !== 'false'
|
||||
});
|
||||
res.json(accounts);
|
||||
} catch (err) {
|
||||
console.error('❌ expense-accounts error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/payment-accounts ───────────────────────────
|
||||
// Aus Cache. Liefert Bank- und Credit-Card-Accounts (für Payment-Account-Dropdown im Expense-Modal).
|
||||
router.get('/payment-accounts', async (req, res) => {
|
||||
try {
|
||||
const accounts = await accountingService.getPaymentAccountsFromCache({
|
||||
activeOnly: req.query.activeOnly !== 'false'
|
||||
});
|
||||
res.json(accounts);
|
||||
} catch (err) {
|
||||
console.error('❌ payment-accounts error:', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── GET /api/accounting/payment-methods ────────────────────────────
|
||||
// Live aus QBO (kleine, selten ändernde Liste — kein Cache nötig).
|
||||
router.get('/payment-methods', async (req, res) => {
|
||||
try {
|
||||
const methods = await accountingService.getPaymentMethods({
|
||||
activeOnly: req.query.activeOnly !== 'false'
|
||||
});
|
||||
res.json(methods);
|
||||
} catch (err) { handleQboError(err, res, 'payment-methods'); }
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user