84 lines
7.2 KiB
PHP
84 lines
7.2 KiB
PHP
<?php
|
|
use App\Models\User;
|
|
use App\Modules\Purchasing\{SupplierPayables, SupplierPaymentsController};
|
|
use App\Shared\Context\CompanyContext;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tempo\Finance\Payables\{Payables, SettlementSchema, Workspace};
|
|
|
|
$f = json_decode(file_get_contents($s['root'].'/fixture.json'), true, 64, JSON_THROW_ON_ERROR);
|
|
$scope = $f['scope']; config(['supplier_payable_history.synthetic_enabled' => true]);
|
|
$reader = User::findOrFail($f['actors']['read']); $service = app(SupplierPayables::class);
|
|
$enter = function (User $user) use ($scope): void { auth()->setUser($user); app(CompanyContext::class)->selectFor($user->id, $scope['tenant_id'], $scope['company_id'], $scope['unit_id']); };
|
|
$checks = 0; $assert = function (bool $ok, string $message) use (&$checks): void { $checks++; if (! $ok) throw new RuntimeException($message); };
|
|
$request = function (array $query) use ($reader): Request {
|
|
$r = Request::create('/api/supplier-payments?'.http_build_query($query), 'GET');
|
|
$r->headers->set('Accept', 'application/json'); $r->setUserResolver(fn () => $reader);
|
|
$r->setLaravelSession(app('session')->driver());
|
|
return $r;
|
|
};
|
|
if (DB::getSchemaBuilder()->hasTable('ps_payments')) throw new LogicException('Retained fixture must have no settlement schema');
|
|
DB::beginTransaction();
|
|
try {
|
|
SettlementSchema::install();
|
|
foreach (['payments_view', 'payment_prepare'] as $action) DB::table('pa_access')->insert($scope + ['user_id' => $reader->id, 'action' => $action, 'person' => 'sgf-history-person-'.$reader->id]);
|
|
$enter($reader);
|
|
$base = $service->document($reader, $f['source_id']); unset($base['local_id']);
|
|
$ids = [];
|
|
foreach (['A', 'B'] as $n => $label) {
|
|
$doc = array_replace($base, ['document' => 900001 + $n, 'number' => 'SIM-FOCUSED-ELIGIBLE-'.$label, 'payment_count' => 0]);
|
|
$payload = Payables::canonical($doc);
|
|
$id = DB::table('pa_imported_sources')->insertGetId($scope + ['payload' => $payload, 'receipt_hash' => hash('sha256', $payload)]);
|
|
$enter(User::findOrFail($f['actors']['prepare']));
|
|
$proposal = $service->engine(auth()->user())->command('prepare', $id, 'Explicit nonhistorical synthetic source.', (string) Str::uuid());
|
|
$enter(User::findOrFail($f['actors']['validate']));
|
|
$result = $service->engine(auth()->user())->command('validate', $proposal['proposal_id'], 'Independent synthetic recognition.', (string) Str::uuid());
|
|
$assert($result['state'] === 'recognised', 'Normal synthetic obligation recognised through common engine');
|
|
$ids[] = $id;
|
|
}
|
|
$enter($reader);
|
|
$controller = app(SupplierPaymentsController::class);
|
|
$a = $controller->index($request([]))->getData(true);
|
|
$b = $controller->index($request(['page' => 2]))->getData(true);
|
|
$assert(count($a['obligations']) === 25 && count($b['obligations']) === 25, 'Payment obligation windows bounded');
|
|
$assert(! array_intersect(array_column($a['obligations'], 'id'), array_column($b['obligations'], 'id')), 'Second obligation page accessible and disjoint');
|
|
$assert($a['obligation_pagination']['total'] === 10002, 'Count reflects server document filters');
|
|
$focused = $controller->index($request(['source' => $ids[0]]))->getData(true);
|
|
$assert(count($focused['obligations']) === 1 && $focused['obligations'][0]['balance_minor'] === 6000 && ! $focused['obligations'][0]['history_imported'], 'Focused eligible debt retains common balance');
|
|
$assert($focused['obligations'][0]['evidence_url'] === '/supplier-payables?source='.$ids[0], 'Exact source evidence link');
|
|
$origin = $focused['obligations'][0]['origin_snapshot'];
|
|
$assert($origin['number'] === 'SIM-FOCUSED-ELIGIBLE-A', 'Exact recognition proposal snapshot');
|
|
$source = $focused['obligations'][0]['source_id'];
|
|
$proposal = (array) DB::table('pa_proposals')->where($scope)->where('source_id', $source)->first(); unset($proposal['id']);
|
|
for ($n = 1; $n <= 40; $n++) {
|
|
$new = json_decode($proposal['snapshot'], true, 64, JSON_THROW_ON_ERROR); $new['supplier_name'] = 'LATER PROPOSAL MUST NOT REPLACE RECOGNITION';
|
|
DB::table('pa_proposals')->insert(array_replace($proposal, ['capture_version' => $proposal['capture_version'] + $n, 'snapshot' => Payables::canonical($new)]));
|
|
}
|
|
$latest = $controller->index($request(['source' => $ids[0], 'proposal_page' => 2]))->getData(true);
|
|
$assert($latest['obligations'][0]['origin_snapshot'] === $origin, 'Recognition evidence does not depend on paginated proposal window');
|
|
$html = $controller->page($request(['source' => $ids[0]]))->getContent();
|
|
$assert(str_contains($html, 'name="source" value="'.$ids[0].'"') && str_contains($html, 'href="/supplier-payables?source='.$ids[0].'"'), 'Rendered selection and return retain focus');
|
|
$assert(str_contains($html, 'Conferir origem, propostas e histórico') && str_contains($html, 'SIM-FOCUSED-ELIGIBLE-A'), 'Evidence link and selected source visible');
|
|
$page = $controller->page($request([]))->getContent();
|
|
$assert(str_contains($page, 'Paginação de origens para pagamentos') && str_contains($page, 'page=2'), 'Rendered next-page navigation');
|
|
$payable = $service->listing($reader, ['source' => $ids[0]]);
|
|
$html = Workspace::render($payable, ['host' => 'QA', 'actor' => 'QA', 'organisation' => 'QA', 'api' => '/api/supplier-payables', 'document_filter' => 'source', 'csrf' => '']);
|
|
$assert(str_contains($html, 'href="/supplier-payments?source='.$ids[0].'"'), 'Obligation workspace carries focus into payment workspace');
|
|
$invalid = false;
|
|
try { $controller->index($request(['company_id' => 999])); } catch (Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e) { $invalid = $e->getStatusCode() === 422; }
|
|
$assert($invalid, 'Payment workspace refuses injected scope');
|
|
$foreign = false;
|
|
try { $controller->index($request(['source' => PHP_INT_MAX])); } catch (Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e) { $foreign = $e->getStatusCode() === 403; }
|
|
$assert($foreign, 'Payment workspace cannot bypass source isolation');
|
|
$assert(DB::table('ps_payments')->count() === 0 && DB::table('ps_allocations')->count() === 0, 'Navigation creates no payment or allocation');
|
|
DB::table('pa_access')->where($scope)->where(['user_id' => $reader->id, 'action' => 'payments_view'])->delete();
|
|
$revoked = false;
|
|
try { $controller->index($request(['source' => $ids[0]])); } catch (Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e) { $revoked = $e->getStatusCode() === 403; }
|
|
$assert($revoked, 'Payment read right independently enforced');
|
|
} finally { while (DB::transactionLevel() > 0) DB::rollBack(); }
|
|
$assert(! DB::getSchemaBuilder()->hasTable('ps_payments'), 'Temporary settlement schema rolled back');
|
|
$report = ['passed' => true, 'assertions' => $checks, 'bounded_obligation_selection' => true, 'exact_recognition_proposal' => true, 'source_focus_bidirectional' => true, 'no_payment_or_allocation' => true, 'schema_and_test_rows_rolled_back' => true, 'payment_history_pagination_implemented' => false];
|
|
file_put_contents($s['root'].'/evidence-settlements.json', json_encode($report, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($report)."\n";
|