Files
InvoiceShelf/tests/Feature/Admin/DashboardTest.php
T
Darko Gjorgjijoski 5ef7804e60 refactor: adopt modular domain architecture (#747)
* refactor: stabilize model identities for domain migration

* refactor: extract module platform context

* refactor: assign models to domain contexts

* refactor: extract ai platform context

* refactor: extract storage platform context

* refactor: extract mail platform context

* refactor: extract pdf platform context

* refactor: extract operations platform context

* refactor: move installation into operations platform

* refactor: extract money domain context

* refactor: extract taxation domain context

* refactor: extract catalog domain context

* refactor: extract metadata domain context

* refactor: extract reporting domain context

* refactor: extract purchases domain context

* refactor: extract receivables domain context

* refactor: extract accounts domain context

* refactor: complete reporting statement boundary

* refactor: extract contacts domain context

* refactor: extract sales domain context

* refactor: remove legacy application layers

* fix: migrate legacy bouncer role identities
2026-08-05 17:40:03 +02:00

123 lines
4.2 KiB
PHP

<?php
use App\Domains\Accounts\Models\User;
use App\Domains\Sales\Models\Invoice;
use Illuminate\Support\Facades\Artisan;
use Laravel\Sanctum\Sanctum;
use function Pest\Laravel\getJson;
use function Pest\Laravel\postJson;
beforeEach(function () {
Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
$user = User::find(1);
$this->withHeaders([
'company' => $user->companies()->first()->id,
]);
Sanctum::actingAs(
$user,
['*']
);
});
getJson('api/v1/dashboard')->assertOk();
getJson('api/v1/search?name=ab')->assertOk();
test('the invoice count excludes credit notes while the sales total nets them out', function () {
$before = getJson('api/v1/dashboard')->assertOk();
$baselineCount = $before->json('total_invoice_count');
$baselineSales = (int) $before->json('total_sales');
// The line item carries the whole invoice: a credit note is derived from
// the invoice's own figures, so its total only nets the sale out when the
// items agree with the document totals, as they do on a real invoice.
$invoice = Invoice::factory()
->hasItems(1, [
'price' => 10000,
'quantity' => 1,
'total' => 10000,
'tax' => 0,
'discount_val' => 0,
'exchange_rate' => 1,
'base_price' => 10000,
'base_total' => 10000,
'base_tax' => 0,
'base_discount_val' => 0,
])
->create([
'status' => Invoice::STATUS_SENT,
'invoice_date' => now()->format('Y-m-d'),
'sub_total' => 10000,
'total' => 10000,
'base_total' => 10000,
'tax' => 0,
'discount_val' => 0,
'due_amount' => 10000,
'base_due_amount' => 10000,
'exchange_rate' => 1,
]);
postJson("api/v1/invoices/{$invoice->id}/credit-note")->assertStatus(201);
$after = getJson('api/v1/dashboard')->assertOk();
// One invoice was issued, and one reversal of it exists. "Invoices" counts
// the issued document only.
expect($after->json('total_invoice_count'))->toBe($baselineCount + 1);
// The sums deliberately keep credit notes: the negated total is exactly
// what takes the reversed sale back out of the figure.
expect((int) $after->json('total_sales'))->toBe($baselineSales);
});
test('the dashboard renders while a partially credited invoice is among the recent due', function () {
// Regression: the recent-due list serializes raw Invoice models, so every
// loaded relation runs the full $appends set. A column-limited creditNotes
// eager load left the credit-note children without company_id and the date
// accessors exploded on a null format, taking the whole endpoint down with
// a 500. A partially credited invoice is the trigger: it still has a due
// amount, so it is the one credited document the recent-due list shows.
$invoice = Invoice::factory()
->hasItems(1, [
'price' => 5000,
'quantity' => 2,
'total' => 10000,
'tax' => 0,
'discount_val' => 0,
'exchange_rate' => 1,
'base_price' => 5000,
'base_total' => 10000,
'base_tax' => 0,
'base_discount_val' => 0,
])
->create([
'status' => Invoice::STATUS_SENT,
'invoice_date' => now()->format('Y-m-d'),
'sub_total' => 10000,
'total' => 10000,
'base_total' => 10000,
'tax' => 0,
'discount_val' => 0,
'due_amount' => 10000,
'base_due_amount' => 10000,
'exchange_rate' => 1,
]);
$item = $invoice->items()->first();
postJson("api/v1/invoices/{$invoice->id}/credit-note", [
'items' => [['id' => $item->id, 'quantity' => 1]],
])->assertStatus(201);
expect((int) $invoice->fresh()->due_amount)->toBe(5000);
$response = getJson('api/v1/dashboard')->assertOk();
expect(collect($response->json('recent_due_invoices'))->pluck('id'))
->toContain($invoice->id);
});