mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
P&L monthly: span the fiscal year so columns aren't limited to one month
The monthly P&L was bound to the page Period, which defaults to the current month — so 'Monthly columns' showed only a single column. When the selected period is a single month, widen the start to the fiscal year containing the end date (per company fiscal_year_start) so every month of the FY-to-date gets a column with none missing. A multi-month selected range is still respected as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -676,7 +676,7 @@ export default function AccountingReportsPage({ association }: { association?: {
|
|||||||
<BudgetVsActuals companyId={cid} from={from} to={to} currency={cur} companyName={associationName ?? "Company"} rangeLabel={rangeLabel} logoUrl={logoUrl} />
|
<BudgetVsActuals companyId={cid} from={from} to={to} currency={cur} companyName={associationName ?? "Company"} rangeLabel={rangeLabel} logoUrl={logoUrl} />
|
||||||
)}
|
)}
|
||||||
{active === "pnl" && pnlMonthView && (
|
{active === "pnl" && pnlMonthView && (
|
||||||
<IncomeStatementReport companyId={cid} companyName={associationName ?? "Company"} from={from} to={to} currency={cur} logoUrl={logoUrl} />
|
<IncomeStatementReport companyId={cid} companyName={associationName ?? "Company"} from={from} to={to} fiscalYearStart={fiscalYearStart} currency={cur} logoUrl={logoUrl} />
|
||||||
)}
|
)}
|
||||||
{active === "trial-balance" && (
|
{active === "trial-balance" && (
|
||||||
<TrialBalanceReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} to={to} />
|
<TrialBalanceReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} to={to} />
|
||||||
@@ -853,6 +853,14 @@ function isBuildPeriods(from: string, to: string, gran: ISGran): { key: string;
|
|||||||
return out;
|
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. */
|
/** Period key a given YYYY-MM-DD date falls into, for the chosen granularity. */
|
||||||
function isPeriodKey(date: string, gran: ISGran): string {
|
function isPeriodKey(date: string, gran: ISGran): string {
|
||||||
if (gran === "month") return date.slice(0, 7);
|
if (gran === "month") return date.slice(0, 7);
|
||||||
@@ -898,22 +906,31 @@ function isGroupAccts(accts: ISAcct[], periods: { key: string }[]): ISGroup[] {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
function IncomeStatementReport({ companyId, companyName, from, to, currency, logoUrl }: {
|
function IncomeStatementReport({ companyId, companyName, from, to, fiscalYearStart, currency, logoUrl }: {
|
||||||
companyId: string; companyName: string; from: string; to: string; currency: string; logoUrl?: string | null;
|
companyId: string; companyName: string; from: string; to: string; fiscalYearStart?: string; currency: string; logoUrl?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const [gran, setGran] = useState<ISGran>("month");
|
const [gran, setGran] = useState<ISGran>("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({
|
const { data: glLines = [], isLoading } = useQuery({
|
||||||
queryKey: ["income-statement-gl", companyId, from, to],
|
queryKey: ["income-statement-gl", companyId, effFrom, to],
|
||||||
enabled: !!companyId,
|
enabled: !!companyId,
|
||||||
queryFn: () => fetchAllGLLines(
|
queryFn: () => fetchAllGLLines(
|
||||||
companyId, to,
|
companyId, to,
|
||||||
"id,debit,credit,accounts!inner(id,name,code,type,category),journal_entries!inner(company_id,date)",
|
"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 model = useMemo(() => {
|
||||||
const accts = new Map<string, ISAcct>();
|
const accts = new Map<string, ISAcct>();
|
||||||
@@ -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 };
|
return { incomeGroups, expenseGroups, incTot, expTot, net, hasRows: income.length > 0 || expense.length > 0 };
|
||||||
}, [glLines, periods, gran]);
|
}, [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 { hasRows } = model;
|
||||||
|
|
||||||
const exportPdf = async () => {
|
const exportPdf = async () => {
|
||||||
@@ -995,7 +1012,7 @@ function IncomeStatementReport({ companyId, companyName, from, to, currency, log
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
drawBrandedFooter(doc);
|
drawBrandedFooter(doc);
|
||||||
doc.save(`profit-loss-${gran}-${from}-to-${to}.pdf`);
|
doc.save(`profit-loss-${gran}-${effFrom}-to-${to}.pdf`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportCsv = () => {
|
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 blob = new Blob([lines.join("\n")], { type: "text/csv" });
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = URL.createObjectURL(blob);
|
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();
|
a.click();
|
||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user