mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Budget Workbook: editable unit count for per-unit assessment rate
The per-unit assessment (annual expenses / 12 / units) used the live association unit count only. Add an override so the rate can use weighted/excluded units, persisted on accounting.budget_workbooks.unit_override (null = live count). New "# Units" control with a reset link; summary card and CSV use the effective count. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,7 @@ export default function BudgetWorkbookPage() {
|
||||
const [ytdOv, setYtdOv] = useState<Record<string, string>>({});
|
||||
const [infl, setInfl] = useState<Record<string, string>>({});
|
||||
const [projOv, setProjOv] = useState<Record<string, string>>({});
|
||||
const [unitOv, setUnitOv] = useState(""); // "" = use the live unit count
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [pushing, setPushing] = useState(false);
|
||||
|
||||
@@ -114,6 +115,7 @@ export default function BudgetWorkbookPage() {
|
||||
}
|
||||
setYtdOv(y); setInfl(i); setProjOv(p);
|
||||
if ((workbook.head as any)?.through_month) setThrough((workbook.head as any).through_month);
|
||||
setUnitOv((workbook.head as any)?.unit_override != null ? String((workbook.head as any).unit_override) : "");
|
||||
}, [workbook]);
|
||||
|
||||
// Computed YTD actual per account from the GL
|
||||
@@ -146,7 +148,10 @@ export default function BudgetWorkbookPage() {
|
||||
const expenseAnnual = sum(sections.exp);
|
||||
const incomeAnnual = sum(sections.inc);
|
||||
const monthly = expenseAnnual / 12;
|
||||
const perUnit = unitCount > 0 ? monthly / unitCount : 0;
|
||||
// Per-unit assessment rate = annual expenses / 12 / units. Units default to the
|
||||
// live association count but can be overridden (weighted/excluded units).
|
||||
const effUnits = unitOv.trim() ? Number(unitOv) : unitCount;
|
||||
const perUnit = effUnits > 0 ? monthly / effUnits : 0;
|
||||
|
||||
const resetRow = (id: string) => {
|
||||
setYtdOv((m) => ({ ...m, [id]: "" }));
|
||||
@@ -159,7 +164,7 @@ export default function BudgetWorkbookPage() {
|
||||
setSaving(true);
|
||||
try {
|
||||
const { data: head, error: hErr } = await accounting.from("budget_workbooks")
|
||||
.upsert({ company_id: cid, fiscal_year: fy, through_month: through, updated_at: new Date().toISOString() },
|
||||
.upsert({ company_id: cid, fiscal_year: fy, through_month: through, unit_override: unitOv.trim() ? Math.round(Number(unitOv)) : null, updated_at: new Date().toISOString() },
|
||||
{ onConflict: "company_id,fiscal_year" })
|
||||
.select("id").single();
|
||||
if (hErr || !head) throw new Error(hErr?.message || "save failed");
|
||||
@@ -333,6 +338,16 @@ export default function BudgetWorkbookPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs"># Units (assessment)</Label>
|
||||
<Input type="number" step="1" min="0" className="h-9 w-[120px]"
|
||||
value={unitOv} placeholder={String(unitCount)}
|
||||
onChange={(e) => setUnitOv(e.target.value)} />
|
||||
{unitOv.trim() && Number(unitOv) !== unitCount && (
|
||||
<button type="button" className="text-[11px] text-muted-foreground hover:underline mt-0.5"
|
||||
onClick={() => setUnitOv("")}>Reset to {unitCount}</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => qc.invalidateQueries({ queryKey: ["wb-gl", cid, from, to] })} disabled={glFetching}>
|
||||
<RefreshCw className={`h-4 w-4 mr-1 ${glFetching ? "animate-spin" : ""}`} /> Refresh
|
||||
@@ -348,7 +363,7 @@ export default function BudgetWorkbookPage() {
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<Card><CardContent className="p-4"><div className="text-xs text-muted-foreground uppercase tracking-wide">Annual Budget (Expenses)</div><div className="text-xl font-semibold mt-1 tabular-nums">{money(expenseAnnual)}</div></CardContent></Card>
|
||||
<Card><CardContent className="p-4"><div className="text-xs text-muted-foreground uppercase tracking-wide">Monthly (÷12)</div><div className="text-xl font-semibold mt-1 tabular-nums">{money(monthly)}</div></CardContent></Card>
|
||||
<Card><CardContent className="p-4"><div className="text-xs text-muted-foreground uppercase tracking-wide">Per Unit / Month ({unitCount} units)</div><div className="text-xl font-semibold mt-1 tabular-nums">{unitCount > 0 ? money(perUnit) : "—"}</div></CardContent></Card>
|
||||
<Card><CardContent className="p-4"><div className="text-xs text-muted-foreground uppercase tracking-wide">Per Unit / Month ({effUnits} units{unitOv.trim() && Number(unitOv) !== unitCount ? " · override" : ""})</div><div className="text-xl font-semibold mt-1 tabular-nums">{effUnits > 0 ? money(perUnit) : "—"}</div></CardContent></Card>
|
||||
<Card><CardContent className="p-4"><div className="text-xs text-muted-foreground uppercase tracking-wide">Projected Surplus / (Deficit)</div><div className={`text-xl font-semibold mt-1 tabular-nums ${incomeAnnual - expenseAnnual < 0 ? "text-destructive" : "text-emerald-700"}`}>{money(incomeAnnual - expenseAnnual)}</div></CardContent></Card>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user