Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions server/src/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,32 @@ class Transaction extends BaseTransaction
'expires_at',
];

/**
* Tables the cached transactions list embeds, so writes to them invalidate it.
*
* TransactionFilter::queryForInternal eager loads the line items, the journal
* with its debit and credit accounts, and the subject, payer, payee, initiator
* and context morphs. Morph targets have no single table, so the ones ledger
* writes are listed: ledger invoices, the invoice customer types (contacts, vendors)
* and companies. Orders and drivers are left out on purpose: they are written
* constantly, so declaring them would leave the list almost never cached; their
* snapshots in the list may lag by up to the list cache TTL.
*
* Requires fleetbase/core-api's cache dependency support; inert before it.
*
* @var string[]
*/
protected $apiCacheDependencies = [
'items',
'journal',
'journal.debitAccount',
'journal.creditAccount',
'ledger_invoices',
'contacts',
'vendors',
'companies',
];

/**
* Merge ledger-specific fillable fields into the parent's list.
*/
Expand Down
25 changes: 25 additions & 0 deletions server/tests/Models/ModelContractsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@ public function where(...$arguments): self
->and($gateway->toArray())->not->toHaveKey('config');
});

test('ledger transaction declares the relations its cached list eager loads as cache dependencies', function () {
$transaction = new Transaction();
$dependencies = (fn () => $this->apiCacheDependencies)->call($transaction);

expect($dependencies)->toBe(['items', 'journal', 'journal.debitAccount', 'journal.creditAccount', 'ledger_invoices', 'contacts', 'vendors', 'companies'])
// Raw entries name the morph targets' tables; contacts and vendors belong to FleetOps
->and((new Invoice())->getTable())->toBe('ledger_invoices')
->and((new Fleetbase\Models\Company())->getTable())->toBe('companies');

// Every relation path must resolve, or core's cache rejects the declaration
$tables = [];
foreach (array_filter($dependencies, fn ($path) => method_exists($transaction, explode('.', $path)[0])) as $path) {
$model = $transaction;
foreach (explode('.', $path) as $segment) {
$relation = $model->{$segment}();
expect($relation)->toBeInstanceOf(Illuminate\Database\Eloquent\Relations\Relation::class)
->and($relation)->not->toBeInstanceOf(MorphTo::class);
$model = $relation->getRelated();
$tables[] = $model->getTable();
}
}

expect(array_values(array_unique($tables)))->toBe(['transaction_items', 'ledger_journals', 'ledger_accounts']);
});

test('wallet relationships computed values and state rules cover every owner type', function () {
$wallet = new WalletStateProbe(['balance' => 1050, 'status' => Wallet::STATUS_ACTIVE]);

Expand Down