fix(accounting): tax report degrades gracefully if cost side fails (+ surface the error)
The cost side is supplementary — it must never 500 the core revenue report. getTaxReport now wraps loadCosts in try/catch: on failure it returns empty costs + a costsError string and logs the real error. The tax page shows the revenue report plus a non-fatal amber banner with the cost-side error message, so the actual cause is visible in the UI instead of an opaque 500.
This commit is contained in:
@@ -49,6 +49,7 @@ const REPORTABLE_STATUSES = ['sent', 'paid', 'overdue', 'pending_delivery', 'can
|
|||||||
|
|
||||||
// D.2 — `ensureInt` consolidated into utils/numericHelpers.
|
// D.2 — `ensureInt` consolidated into utils/numericHelpers.
|
||||||
const { ensureInt } = require('../utils/numericHelpers');
|
const { ensureInt } = require('../utils/numericHelpers');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
function ensureRate(v) {
|
function ensureRate(v) {
|
||||||
if (v === null || v === undefined || v === '') return 0;
|
if (v === null || v === undefined || v === '') return 0;
|
||||||
@@ -449,10 +450,20 @@ async function getTaxReport({ from, to, currency, includeCosts = true } = {}) {
|
|||||||
const totalsByVatRate = Array.from(byRate.values()).sort((a, b) => a.vatRate - b.vatRate);
|
const totalsByVatRate = Array.from(byRate.values()).sort((a, b) => a.vatRate - b.vatRate);
|
||||||
|
|
||||||
// Cost side (Einnahmen-Ausgaben). Optional so legacy callers that
|
// Cost side (Einnahmen-Ausgaben). Optional so legacy callers that
|
||||||
// only want the revenue listing can opt out.
|
// only want the revenue listing can opt out. The cost side is
|
||||||
const costs = includeCosts
|
// SUPPLEMENTARY — if it fails (e.g. an accounting table/column missing
|
||||||
? await loadCosts({ from, to, cur })
|
// on an older install) it must NOT take down the core revenue report.
|
||||||
: { rows: [], totalNet: 0, totalVat: 0, totalGross: 0 };
|
// Degrade to empty costs + log the real error for diagnosis.
|
||||||
|
let costs = { rows: [], totalNet: 0, totalVat: 0, totalGross: 0 };
|
||||||
|
let costsError = null;
|
||||||
|
if (includeCosts) {
|
||||||
|
try {
|
||||||
|
costs = await loadCosts({ from, to, cur });
|
||||||
|
} catch (err) {
|
||||||
|
costsError = err.message;
|
||||||
|
logger.error?.(`taxReport: cost side failed (revenue still returned): ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Summary: income vs cost vs result. Result = a simplified
|
// Summary: income vs cost vs result. Result = a simplified
|
||||||
// Einnahmen-Ausgaben surplus (net basis); vatPayable = output VAT
|
// Einnahmen-Ausgaben surplus (net basis); vatPayable = output VAT
|
||||||
@@ -478,6 +489,7 @@ async function getTaxReport({ from, to, currency, includeCosts = true } = {}) {
|
|||||||
grandTotal,
|
grandTotal,
|
||||||
cancelledCount,
|
cancelledCount,
|
||||||
costs,
|
costs,
|
||||||
|
costsError,
|
||||||
summary,
|
summary,
|
||||||
currency: cur,
|
currency: cur,
|
||||||
period: { from, to },
|
period: { from, to },
|
||||||
|
|||||||
@@ -358,6 +358,19 @@ export const TaxReportPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Non-fatal: the revenue report loaded but the cost side errored. */}
|
||||||
|
{report?.costsError && (
|
||||||
|
<Card padding="md">
|
||||||
|
<div className="flex items-start gap-3 text-amber-700 dark:text-amber-400">
|
||||||
|
<AlertCircle className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">{t('taxReport.costsErrorTitle', 'Costs could not be loaded')}</p>
|
||||||
|
<p className="text-sm text-neutral-600 dark:text-neutral-400 mt-1 break-words">{report.costsError}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Results */}
|
{/* Results */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Card padding="lg"><Loading /></Card>
|
<Card padding="lg"><Loading /></Card>
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ export interface TaxReport {
|
|||||||
/** Cost side (#4). Present when the accounting tables exist; empty
|
/** Cost side (#4). Present when the accounting tables exist; empty
|
||||||
* otherwise. */
|
* otherwise. */
|
||||||
costs: TaxReportCosts;
|
costs: TaxReportCosts;
|
||||||
|
/** Non-fatal: set when the cost side failed to load (revenue still shown). */
|
||||||
|
costsError?: string | null;
|
||||||
/** Income/cost/result summary (#4). */
|
/** Income/cost/result summary (#4). */
|
||||||
summary: TaxReportSummary;
|
summary: TaxReportSummary;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user