Fix double-counting: available = total (expenses already debit accounts)
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 35s

The previous Remaining Budget card subtracted approved expenses from
the account balance sum — but postExpenseTransaction already posts
negative-amount rows to the ledger, so the balance sum already reflects
them. Replaced with:
  - Available Cash (= sum of account balances)
  - Allocated (with % progress bar)
  - Unallocated (cash not assigned to any project)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 13:22:16 +07:00
parent 94e38aca9c
commit 7fba11941f
@@ -9,12 +9,15 @@
const spent = $derived(data.projects.reduce((s, p) => s + parseFloat(p.spent), 0));
const total = $derived(parseFloat(data.company.totalBudget));
const income = $derived(parseFloat(data.totalIncome ?? '0'));
const remaining = $derived(total - spent);
const remainingPct = $derived(total > 0 ? (remaining / total) * 100 : 0);
// Total already reflects approved expenses (they post negative txns to the ledger),
// so available cash IS the total. Spent stays informational.
const available = $derived(total);
const unallocated = $derived(total - allocated);
const allocatedPct = $derived(total > 0 ? (allocated / total) * 100 : 0);
const net = $derived(income - spent);
const netPositive = $derived(net >= 0);
const tone = $derived(remaining < 0 ? 'red' : remainingPct < 20 ? 'amber' : 'green');
const tone = $derived(available < 0 ? 'red' : available < Math.abs(allocated) * 0.2 ? 'amber' : 'green');
const toneRing: Record<string, string> = {
green: 'border-green-300 dark:border-green-700',
@@ -85,36 +88,20 @@
</div>
</div>
<!-- Budget KPIs (secondary) -->
<!-- Cash KPIs (secondary) -->
<div class="grid grid-cols-2 gap-3 lg:grid-cols-3">
<div class="rounded-lg border {toneRing[tone]} bg-white p-4 dark:bg-gray-800">
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
Remaining Budget
Available Cash
</p>
<p class="mt-1 text-2xl font-bold {toneText[tone]}">
{formatCurrency(remaining, currency)}
{formatCurrency(available, currency)}
</p>
<div class="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
<div
class="h-full transition-all {toneBar[tone]}"
style="width: {Math.max(0, Math.min(remainingPct, 100))}%"
></div>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{remainingPct.toFixed(1)}% of total
Sum of account balances (base currency)
</p>
</div>
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
Total Budget
</p>
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
{formatCurrency(total, currency)}
</p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">From account balances</p>
</div>
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
Allocated
@@ -122,8 +109,23 @@
<p class="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
{formatCurrency(allocated, currency)}
</p>
<div class="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
<div class="h-full bg-blue-500 transition-all" style="width: {Math.min(allocatedPct, 100)}%"></div>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{total > 0 ? ((allocated / total) * 100).toFixed(1) : '0'}% of total
{allocatedPct.toFixed(1)}% of available
</p>
</div>
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
Unallocated
</p>
<p class="mt-1 text-2xl font-bold {unallocated < 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white'}">
{formatCurrency(unallocated, currency)}
</p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Cash not assigned to a project
</p>
</div>
</div>