-
Finished the planner end to end. Corrected rollover and yearly accounting, recurring targets, savings rules, and targeted dormant restorations. Added editing/deletion for income, expenses, targets, and categories. Rebuilt dashboard, analytics, calendar, settings, exports, authentication, and responsive mobile UI. Added original artwork at [financial-flow.png](/Users/tyemeclifford/Documents/GH/budget/public/assets/images/financial-flow.png), generated with built-in Imagegen using an abstract financial-momentum landscape prompt. Added [self-test.php](/Users/tyemeclifford/Documents/GH/budget/scripts/self-test.php). Verification passed: automated accounting/export tests, full browser workflow, responsive 390px layout, light/dark themes, and no console errors. Tested with FrankenPHP v1.12.4. Live Mailgun delivery was not attempted without credentials.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Budget\ExportService;
|
||||
|
||||
$databasePath = sys_get_temp_dir() . '/neon-ledger-self-test-' . getmypid() . '.sqlite';
|
||||
putenv('BUDGET_SQLITE_PATH=' . $databasePath);
|
||||
|
||||
register_shutdown_function(static function () use ($databasePath): void {
|
||||
foreach ([$databasePath, $databasePath . '-shm', $databasePath . '-wal'] as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
require __DIR__ . '/../app/bootstrap.php';
|
||||
|
||||
$assert = static function (bool $condition, string $message): void {
|
||||
if (!$condition) {
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
};
|
||||
|
||||
$settings->set('dead.income_mode', 'percent');
|
||||
$settings->set('dead.income_value', 10);
|
||||
$settings->set('dead.transaction_mode', 'fixed');
|
||||
$settings->set('dead.transaction_value', 1);
|
||||
|
||||
$budget->savePlan('2026-01', 1000, 0, true, 'Self-test');
|
||||
$categoryId = $budget->addCategory('Utilities', '#49a8ff', 200);
|
||||
$itemId = $budget->addBudgetItem('2026-01', $categoryId, 'Internet', 55, '2026-01-20', 'monthly', '');
|
||||
$transactionId = $budget->addTransaction('2026-01', $itemId, null, 'Internet payment', 10, '2026-01-10', '');
|
||||
|
||||
$january = $budget->summary('2026-01');
|
||||
$assert(abs($january['remaining'] - 889) < 0.001, 'January remaining balance is incorrect.');
|
||||
$assert(abs($budget->budgetItems('2026-01')[0]['remaining_amount'] - 45) < 0.001, 'Target progress is incorrect.');
|
||||
|
||||
$budget->updateTransaction($transactionId, '2026-01', $itemId, null, 'Internet payment', 20, '2026-01-10', '');
|
||||
$budget->moveDeadAccount('2026-01', 'restore', 50, '', 'item', $itemId);
|
||||
$january = $budget->summary('2026-01');
|
||||
$assert(abs($january['remaining'] - 929) < 0.001, 'Dormant restoration accounting is incorrect.');
|
||||
$assert(abs($budget->deadBalance() - 51) < 0.001, 'Dormant balance is incorrect.');
|
||||
|
||||
$february = $budget->ensureMonth('2026-02');
|
||||
$februaryItems = $budget->budgetItems('2026-02');
|
||||
$assert(abs((float) $february['rollover_in'] - 929) < 0.001, 'Rolling budget did not carry the remainder forward.');
|
||||
$assert(count($februaryItems) === 1 && $februaryItems[0]['name'] === 'Internet', 'Recurring target was not materialized.');
|
||||
|
||||
$year = $budget->yearlySummary(2026);
|
||||
$assert(abs($year['totals']['fresh_income'] - 1000) < 0.001, 'Yearly income double-counted rollover.');
|
||||
|
||||
$exporter = new ExportService($pdo, $settings, $budget);
|
||||
$package = json_decode($exporter->package(), true);
|
||||
$assert(($package['format'] ?? null) === 'neon-ledger/budget-planner', 'JSON package format is invalid.');
|
||||
$assert(str_contains($exporter->html('2026-01'), 'Internet'), 'HTML export is missing target data.');
|
||||
$assert(str_starts_with($exporter->pdf('2026-01'), '%PDF-1.4'), 'PDF export is invalid.');
|
||||
|
||||
echo "Neon Ledger self-test passed.\n";
|
||||
Reference in New Issue
Block a user