fix(accounts): floor purchase/loan start dates at 1900 to stop OOM at source

Review follow-up addressing the root cause of PHP-LARAVEL-49/4A. The batched
upsert stops the crash, but purchase_date (`before_or_equal:today`) and
loan_start_date (no constraint) had no LOWER bound, so a mistyped or absurd
year (e.g. 0201, 1080) would still make the historical balance generator
produce a multi-century monthly series — thousands of fabricated interpolated
rows per account (storage bloat and a misleading 100-year net-worth ramp) even
once it no longer OOMs.

Add `after_or_equal:1900-01-01` to both dates. 1900 is generous enough to
accept any legitimately old asset in a personal-finance app while rejecting the
data-entry typos that drive the runaway series — capping both the in-memory
date list and the generated row count at the input boundary, complementing the
batching defense-in-depth.

Not adding `before_or_equal:today` to loan_start_date here (real estate has it,
loan doesn't): a future start date does not cause the OOM (the generator no-ops
on it), so aligning that is a separate, non-urgent consistency change.

Refs PHP-LARAVEL-49, PHP-LARAVEL-4A
This commit is contained in:
Víctor Falcón 2026-07-08 23:26:34 +02:00
parent 65e0af7e88
commit d54d54cbd0
2 changed files with 27 additions and 2 deletions

View File

@ -24,7 +24,10 @@ trait ValidatesAccountDetailRules
],
'address' => ['nullable', 'string', 'max:500'],
'purchase_price' => ['nullable', 'integer', 'min:0'],
'purchase_date' => ['nullable', 'date', 'before_or_equal:today'],
// Floor the date: a mistyped/ancient year would make the historical
// balance generator build a multi-century monthly series and OOM the
// queue worker (PHP-LARAVEL-49). 1900 rejects typos, not real assets.
'purchase_date' => ['nullable', 'date', 'after_or_equal:1900-01-01', 'before_or_equal:today'],
'area_value' => ['nullable', 'numeric', 'min:0', 'max:99999999.99'],
'area_unit' => ['nullable', 'string', Rule::in(['sqm', 'sqft', 'acres', 'hectares'])],
'linked_loan_account_id' => [
@ -52,7 +55,9 @@ trait ValidatesAccountDetailRules
return [
'annual_interest_rate' => ['nullable', 'numeric', 'min:0', 'max:100'],
'loan_term_months' => ['nullable', 'integer', 'min:1', 'max:600'],
'loan_start_date' => ['nullable', 'date'],
// Floor the date for the same reason as purchase_date above: an
// ancient loan start date OOMs the balance generator (PHP-LARAVEL-49).
'loan_start_date' => ['nullable', 'date', 'after_or_equal:1900-01-01'],
'original_amount' => ['nullable', 'integer', 'min:0'],
];
}

View File

@ -63,6 +63,26 @@ it('can create a real estate account with property details', function () {
]);
});
it('rejects a purchase date before the 1900 floor', function () {
actingAs($this->user);
// A pre-1900 (typically mistyped) date would make the historical balance
// generator build a multi-century monthly series and OOM the queue worker
// (PHP-LARAVEL-49). It must be rejected at validation.
$data = [
'name' => 'My Apartment',
'currency_code' => 'EUR',
'type' => AccountType::RealEstate->value,
'property_type' => PropertyType::Residential->value,
'purchase_price' => 25000000,
'purchase_date' => '1899-12-31',
];
$response = $this->post(route('accounts.store'), $data);
$response->assertSessionHasErrors(['purchase_date']);
});
it('can create a real estate account with only required fields', function () {
actingAs($this->user);