Fix false 409 snapshot expiry in invoice-period tree preview (#350)
## Problem `POST /superuser/invoicing/period/tree-actions/preview` could return `409 Invoice-period snapshot is missing or expired` during normal UI flows when a snapshot binding aged out before the user triggered the action. ## Fix - introduce a dedicated snapshot cache TTL (`SNAPSHOT_BINDING_TTL_SECONDS`) - keep preview cache TTL unchanged (`PREVIEW_TTL_SECONDS`) - use the longer snapshot TTL for actor/customer snapshot binding writes This preserves existing safety because snapshot bindings are still revalidated against fresh revision data before use. ## Tests - `vendor/bin/pest tests/Unit/Invoicing/InvoiceCollectionBulkActionSafetyTest.php --colors=never` - `vendor/bin/pest tests/Api/CollectedInvoiceBulkActionsApiTest.php --colors=never` (suite present; skipped without `RUN_API_TESTS=1`) Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Jeppe Bundgaard
Copilot
parent
59107a6bb2
commit
bd2ff1be9a
@@ -27,6 +27,7 @@ class invoice_collection_bulk_action_service
|
||||
public const ACTION_QUEUE_ECONOMIC = 'queue_economic';
|
||||
|
||||
private const PREVIEW_TTL_SECONDS = 600;
|
||||
private const SNAPSHOT_BINDING_TTL_SECONDS = 7200;
|
||||
private const MAX_COLLECTIONS = 100;
|
||||
private const CONFIRMATION_PHRASES = [
|
||||
'da' => 'Bekræft',
|
||||
@@ -347,7 +348,7 @@ class invoice_collection_bulk_action_service
|
||||
(new redis())->setEx(
|
||||
$this->snapshotCacheKey($actorUserId, $revision),
|
||||
$encoded,
|
||||
self::PREVIEW_TTL_SECONDS
|
||||
self::SNAPSHOT_BINDING_TTL_SECONDS
|
||||
);
|
||||
|
||||
return $binding;
|
||||
|
||||
@@ -32,6 +32,18 @@ it('binds invoice-period tree bulk previews to the actor and cached preview acti
|
||||
->toContain("'snapshot_revision'");
|
||||
});
|
||||
|
||||
it('keeps snapshot bindings alive longer than preview caches for invoice-period tree actions', function (): void {
|
||||
$service = (string)file_get_contents(app_path('classes/invoice_collection_bulk_action_service.php'));
|
||||
$reflection = new ReflectionClass(invoice_collection_bulk_action_service::class);
|
||||
$previewTtl = (int)$reflection->getConstant('PREVIEW_TTL_SECONDS');
|
||||
$snapshotTtl = (int)$reflection->getConstant('SNAPSHOT_BINDING_TTL_SECONDS');
|
||||
|
||||
expect($snapshotTtl)->toBeGreaterThan($previewTtl)
|
||||
->and($service)->toContain('private const SNAPSHOT_BINDING_TTL_SECONDS = 7200;')
|
||||
->toContain('self::SNAPSHOT_BINDING_TTL_SECONDS')
|
||||
->toContain("setEx(\n \$this->snapshotCacheKey(\$actorUserId, \$revision),");
|
||||
});
|
||||
|
||||
it('previews whole-tree cleanup and merge supersession semantics', function (): void {
|
||||
$service = (string)file_get_contents(app_path('classes/invoice_collection_bulk_action_service.php'));
|
||||
$object = (string)file_get_contents(app_path('objects/collected_order_invoices_o.php'));
|
||||
|
||||
Reference in New Issue
Block a user