diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index 2f0ef71..dc9c79b 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -676,7 +676,7 @@ export default function AccountingReportsPage({ association }: { association?: { )} {active === "pnl" && pnlMonthView && ( - + )} {active === "trial-balance" && ( @@ -853,6 +853,14 @@ function isBuildPeriods(from: string, to: string, gran: ISGran): { key: string; return out; } +/** First day of the fiscal year that contains `to`, given an "MM-DD" FY start. */ +function isFyStartFor(to: string, fyStart: string): string { + const [mm, dd] = (fyStart || "01-01").split("-").map(Number); + const ty = Number(to.slice(0, 4)); + const candidate = `${ty}-${isPad2(mm || 1)}-${isPad2(dd || 1)}`; + return to >= candidate ? candidate : `${ty - 1}-${isPad2(mm || 1)}-${isPad2(dd || 1)}`; +} + /** Period key a given YYYY-MM-DD date falls into, for the chosen granularity. */ function isPeriodKey(date: string, gran: ISGran): string { if (gran === "month") return date.slice(0, 7); @@ -898,22 +906,31 @@ function isGroupAccts(accts: ISAcct[], periods: { key: string }[]): ISGroup[] { return groups; } -function IncomeStatementReport({ companyId, companyName, from, to, currency, logoUrl }: { - companyId: string; companyName: string; from: string; to: string; currency: string; logoUrl?: string | null; +function IncomeStatementReport({ companyId, companyName, from, to, fiscalYearStart, currency, logoUrl }: { + companyId: string; companyName: string; from: string; to: string; fiscalYearStart?: string; currency: string; logoUrl?: string | null; }) { const [gran, setGran] = useState("month"); + // The point of the monthly P&L is a column per period. If the page Period is a + // single month (the default), there'd be just one column — so widen the start + // to the fiscal year containing `to`, giving every month of the FY-to-date. + // A multi-month selected range is respected as-is. + const effFrom = useMemo( + () => (from.slice(0, 7) === to.slice(0, 7) ? isFyStartFor(to, fiscalYearStart || "01-01") : from), + [from, to, fiscalYearStart], + ); + const { data: glLines = [], isLoading } = useQuery({ - queryKey: ["income-statement-gl", companyId, from, to], + queryKey: ["income-statement-gl", companyId, effFrom, to], enabled: !!companyId, queryFn: () => fetchAllGLLines( companyId, to, "id,debit,credit,accounts!inner(id,name,code,type,category),journal_entries!inner(company_id,date)", - from, + effFrom, ), }); - const periods = useMemo(() => isBuildPeriods(from, to, gran), [from, to, gran]); + const periods = useMemo(() => isBuildPeriods(effFrom, to, gran), [effFrom, to, gran]); const model = useMemo(() => { const accts = new Map(); @@ -942,7 +959,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log return { incomeGroups, expenseGroups, incTot, expTot, net, hasRows: income.length > 0 || expense.length > 0 }; }, [glLines, periods, gran]); - const subtitle = `${fmtDate(from)} – ${fmtDate(to)}, By ${gran[0].toUpperCase()}${gran.slice(1)}, Accrual basis`; + const subtitle = `${fmtDate(effFrom)} – ${fmtDate(to)}, By ${gran[0].toUpperCase()}${gran.slice(1)}, Accrual basis`; const { hasRows } = model; const exportPdf = async () => { @@ -995,7 +1012,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log }, }); drawBrandedFooter(doc); - doc.save(`profit-loss-${gran}-${from}-to-${to}.pdf`); + doc.save(`profit-loss-${gran}-${effFrom}-to-${to}.pdf`); }; const exportCsv = () => { @@ -1019,7 +1036,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log const blob = new Blob([lines.join("\n")], { type: "text/csv" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); - a.download = `profit-loss-${gran}-${from}-to-${to}.csv`; + a.download = `profit-loss-${gran}-${effFrom}-to-${to}.csv`; a.click(); URL.revokeObjectURL(a.href); };