This commit is contained in:
2026-06-03 13:58:12 -05:00
commit 2f13b8c590
54 changed files with 5136 additions and 0 deletions

107
server/services/reports.js Normal file
View File

@@ -0,0 +1,107 @@
const PDFDocument = require('pdfkit');
const { all, one, parseJson } = require('../db');
const { annualSchedule, money, monthlyFromAnnual } = require('../depreciation');
const { assetExportRows, assetFromRow } = require('./assets');
function activeRuleSet() {
const row = one('SELECT * FROM tax_rule_sets WHERE active = 1 ORDER BY effective_date DESC, id DESC LIMIT 1');
return row ? parseJson(row.rules_json, {}) : {};
}
function depreciationReport(year, bookType) {
const rules = activeRuleSet();
const rows = all(`
SELECT a.*, b.book_type, b.active, b.cost, b.depreciation_method, b.convention, b.useful_life_months AS book_life,
b.section_179_amount, b.bonus_percent, b.business_use_percent AS book_business_use_percent,
b.investment_use_percent AS book_investment_use_percent, b.manual_depreciation, e.name AS entity_name
FROM assets a
JOIN asset_books b ON b.asset_id = a.id
LEFT JOIN entities e ON e.id = a.entity_id
WHERE b.active = 1 AND b.book_type = ?
ORDER BY a.asset_id
`, [bookType]);
const reportRows = rows.map((row) => {
const asset = assetFromRow(row);
const book = {
...row,
useful_life_months: row.book_life,
business_use_percent: row.book_business_use_percent,
investment_use_percent: row.book_investment_use_percent
};
const schedule = annualSchedule(asset, book, rules, { startYear: year, endYear: year })[0] || {};
return {
asset_id: row.asset_id,
description: row.description,
entity_name: row.entity_name,
category: row.category,
book: bookType,
method: row.depreciation_method,
methodLabel: schedule.methodLabel || row.depreciation_method,
cost: money(row.cost ?? row.acquisition_cost),
depreciation: money(schedule.depreciation || 0),
accumulated: money(schedule.accumulated || 0),
net_book_value: money(schedule.netBookValue ?? row.acquisition_cost)
};
});
return {
year,
book: bookType,
totals: {
cost: money(reportRows.reduce((sum, row) => sum + row.cost, 0)),
depreciation: money(reportRows.reduce((sum, row) => sum + row.depreciation, 0)),
accumulated: money(reportRows.reduce((sum, row) => sum + row.accumulated, 0)),
netBookValue: money(reportRows.reduce((sum, row) => sum + row.net_book_value, 0))
},
rows: reportRows
};
}
function monthlyDepreciationReport(year, bookType) {
const rules = activeRuleSet();
const assets = all('SELECT * FROM assets ORDER BY asset_id').map(assetFromRow);
const rows = assets.flatMap((asset) => {
const book = one('SELECT * FROM asset_books WHERE asset_id = ? AND book_type = ? AND active = 1', [asset.id, bookType]);
if (!book) return [];
return annualSchedule(asset, book, rules, { startYear: year, endYear: year }).flatMap(monthlyFromAnnual);
});
const months = Array.from({ length: 12 }, (_, index) => {
const month = index + 1;
return {
month,
depreciation: money(rows.filter((row) => row.month === month).reduce((sum, row) => sum + row.depreciation, 0))
};
});
return { year, book: bookType, months };
}
function writeDepreciationPdf(res, year, book) {
const rows = assetExportRows();
const doc = new PDFDocument({ margin: 36, size: 'LETTER' });
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="mixedassets-${book}-${year}.pdf"`);
doc.pipe(res);
doc.fontSize(18).text('MixedAssets Depreciation Report');
doc.fontSize(10).text(`Book: ${book} Year: ${year} Generated: ${new Date().toLocaleString()}`);
doc.moveDown();
doc.fontSize(9).text('Asset ID', 36, doc.y, { continued: true, width: 80 });
doc.text('Description', 100, doc.y, { continued: true, width: 210 });
doc.text('Category', 300, doc.y, { continued: true, width: 110 });
doc.text('Cost', 430, doc.y, { align: 'right' });
doc.moveDown(0.5);
rows.slice(0, 80).forEach((row) => {
doc.text(row.asset_id, 36, doc.y, { continued: true, width: 80 });
doc.text(String(row.description).slice(0, 36), 100, doc.y, { continued: true, width: 210 });
doc.text(String(row.category).slice(0, 20), 300, doc.y, { continued: true, width: 110 });
doc.text(money(row.acquisition_cost).toLocaleString(), 430, doc.y, { align: 'right' });
});
doc.end();
}
module.exports = {
activeRuleSet,
depreciationReport,
monthlyDepreciationReport,
writeDepreciationPdf
};