Phase 1: accounting

This commit is contained in:
2026-05-06 14:10:46 -05:00
parent 373c1cb945
commit d6d70e641c
8 changed files with 1241 additions and 9 deletions

115
src/routes/accounting.js Normal file
View File

@@ -0,0 +1,115 @@
/**
* 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
*/
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.stack) console.error(err.stack);
res.status(500).json({
error: err.message || 'QBO request failed',
context
});
}
// ─── GET /api/accounting/accounts ───────────────────────────────────
// Optional ?type=Bank|CreditCard|Expense|Income|... ?activeOnly=false
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 });
res.json(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' });
}
try {
const result = await accountingService.getRegister({
accountId,
startDate,
endDate
});
res.json(result);
} 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');
}
});
// ─── 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(data);
} catch (err) {
handleQboError(err, res, 'balance-sheet');
}
});
module.exports = router;