feat(accounting): VAT registration/reclaim settings + un-gated VAT-codes read
Slice 1 of the VAT consolidation backend: - PUT /admin/settings/accounting accepts accounting_vat_registered (bool) + accounting_vat_reclaim_countries (ISO-2 list); GET /:type already returns them parsed, so no GET change needed. - New read-only GET /api/admin/vat-codes (adminAuth, NOT accounting-gated) so the invoice/quote editors can populate their VAT dropdown even when the accounting layer is off. Management CRUD stays under /admin/ledger.
This commit is contained in:
@@ -709,6 +709,9 @@ app.use('/api/admin/deals', require('./src/routes/adminDeals'));
|
||||
app.use('/api/admin/tax-report', require('./src/routes/adminTaxReport'));
|
||||
app.use('/api/admin/expenses', require('./src/routes/adminExpenses'));
|
||||
app.use('/api/admin/ledger', require('./src/routes/adminLedger'));
|
||||
// Read-only VAT-code registry for the invoice/quote editors — un-gated by the
|
||||
// accounting flag (management stays under /ledger).
|
||||
app.use('/api/admin/vat-codes', require('./src/routes/adminVatCodes'));
|
||||
app.use('/api/admin/system-health', require('./src/routes/adminSystemHealth'));
|
||||
app.use('/api/admin/dev', require('./src/routes/adminDev'));
|
||||
app.use('/api/public/quotes', require('./src/routes/publicQuotes'));
|
||||
|
||||
@@ -255,6 +255,29 @@ router.put('/accounting', adminAuth, requirePermission('settings.edit'), async (
|
||||
setting_type: 'accounting',
|
||||
});
|
||||
}
|
||||
// VAT registration + reclaim. `registered` drives whether output VAT applies
|
||||
// + whether input VAT is deductible; `reclaim_countries` = the ISO-2 list of
|
||||
// countries whose input VAT can be reclaimed (drives cost tax-treatment +
|
||||
// the report's VAT-payable).
|
||||
if (Object.prototype.hasOwnProperty.call(req.body, 'accounting_vat_registered')) {
|
||||
updates.push({
|
||||
setting_key: 'accounting_vat_registered',
|
||||
setting_value: JSON.stringify(!!req.body.accounting_vat_registered),
|
||||
setting_type: 'accounting',
|
||||
});
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(req.body, 'accounting_vat_reclaim_countries')) {
|
||||
const arr = Array.isArray(req.body.accounting_vat_reclaim_countries)
|
||||
? req.body.accounting_vat_reclaim_countries
|
||||
.map((c) => String(c || '').toUpperCase().trim())
|
||||
.filter((c) => /^[A-Z]{2}$/.test(c))
|
||||
: [];
|
||||
updates.push({
|
||||
setting_key: 'accounting_vat_reclaim_countries',
|
||||
setting_value: JSON.stringify(arr),
|
||||
setting_type: 'accounting',
|
||||
});
|
||||
}
|
||||
for (const u of updates) {
|
||||
const existing = await db('app_settings').where('setting_key', u.setting_key).first();
|
||||
if (existing) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Read-only VAT-code registry for the invoice / quote editors.
|
||||
*
|
||||
* Mounted at /api/admin/vat-codes. UN-gated by the `accounting` flag on purpose:
|
||||
* invoices need their preset VAT codes even when the accounting layer is off, so
|
||||
* the editor must be able to read the list regardless. Any authenticated admin
|
||||
* may read the (innocuous) tax-code list. MANAGEMENT (create/update/delete) stays
|
||||
* in the accounting-gated /api/admin/ledger routes — this is read-only.
|
||||
*
|
||||
* GET / [?direction=output|input] → { items: [{ id, code, name, rate, direction }] }
|
||||
*/
|
||||
const express = require('express');
|
||||
const { adminAuth } = require('../middleware/auth');
|
||||
const { handleAsync, successResponse } = require('../utils/routeHelpers');
|
||||
const ledgerService = require('../services/ledgerService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', adminAuth, handleAsync(async (req, res) => {
|
||||
const items = await ledgerService.listVatCodes();
|
||||
const active = items.filter((v) => v.active !== false);
|
||||
const { direction } = req.query;
|
||||
const filtered = (direction === 'output' || direction === 'input')
|
||||
? active.filter((v) => v.direction === direction)
|
||||
: active;
|
||||
return successResponse(res, {
|
||||
items: filtered.map((v) => ({
|
||||
id: v.id, code: v.code, name: v.name, rate: Number(v.rate) || 0, direction: v.direction,
|
||||
})),
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user