Files
SGF/scripts/ledger-composition-proof.php
T
2026-11-04 09:37:20 +02:00

40 lines
12 KiB
PHP

<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use App\Modules\Services\{ServicePermissions,DocumentPreview,InternalConfirmation,ServiceReceivables};
use App\Modules\Purchasing\SupplierPayables;
use App\Shared\Context\ModuleRecords;
use Tempo\Finance\Payables\Payables;
if(config('general_ledger.composition_enabled')!==true||config('sales_ledger.synthetic_enabled')!==true||config('cost_ledger.correction_enabled')!==true)throw new LogicException('Explicit new multi-source composition required');
config(['modules.contracted'=>['commercial','finance','workshop','accounting'],'modules.companies.'.$scope['tenant_id'].':'.$scope['company_id']=>['contracted'=>['commercial','finance','workshop','accounting'],'enabled'=>['commercial','finance','workshop','accounting']]]);
if($phase==='ledger-cost-composition-ready'){
$output=$s['root'].'/ledger-composition-fixture.json';if(file_exists($output)||DB::table('si_invoices')->exists()||DB::table('pa_obligations')->exists()||DB::table('gl_journals')->count()!==1)throw new LogicException('Fresh composition after original cost preparation required');Illuminate\Support\Facades\Date::setTestNow('2026-10-29 12:00:00');$oldGrants=DB::table('permission_grants')->pluck('id')->all();$base=['manager'=>$enter('prepare'),'reader'=>App\Models\User::findOrFail($g['actors']['validate']),'tenant'=>$scope['tenant_id'],'company'=>$scope['company_id'],'unit'=>$scope['unit_id']];
foreach(ServicePermissions::AREAS as $area){$unit=ServicePermissions::unitScoped($area)?$scope['unit_id']:null;foreach(ServicePermissions::ACTIONS as $a)Tests\Support\IdentityFixture::grant($g['actors']['prepare'],$scope['tenant_id'],$scope['company_id'],$area.'.'.$a,$unit);Tests\Support\IdentityFixture::grant($g['actors']['validate'],$scope['tenant_id'],$scope['company_id'],$area.'.view',$unit);}
Tests\Support\IdentityFixture::grant($g['actors']['prepare'],$scope['tenant_id'],$scope['company_id'],InternalConfirmation::PERMISSION,$scope['unit_id']);foreach(['issuer_documents','client_documents','draft_documents'] as $area){$unit=$area==='draft_documents'?$scope['unit_id']:null;foreach(['view','edit'] as $a)Tests\Support\IdentityFixture::grant($g['actors']['prepare'],$scope['tenant_id'],$scope['company_id'],$area.'.'.$a,$unit);Tests\Support\IdentityFixture::grant($g['actors']['validate'],$scope['tenant_id'],$scope['company_id'],$area.'.view',$unit);}Tests\Support\IdentityFixture::grant($g['actors']['prepare'],$scope['tenant_id'],$scope['company_id'],DocumentPreview::CONFIGURE);
$sale=Tests\Support\ReceivablesFixture::create($base);DB::table('permission_grants')->whereNotIn('id',$oldGrants)->delete();
$def=DB::selectOne("SELECT pg_get_constraintdef(oid) d FROM pg_constraint WHERE conrelid='permission_grants'::regclass AND conname='permission_grants_scope_check'")->d;DB::statement('ALTER TABLE permission_grants DROP CONSTRAINT permission_grants_scope_check');DB::statement("ALTER TABLE permission_grants ADD CONSTRAINT permission_grants_scope_check CHECK ((".substr($def,6).") OR (unit_id IS NOT NULL AND permission='supplier_payables.view'))");
foreach(['prepare','validate','read'] as $role){$actor=$g['actors'][$role];foreach(['service_invoices.view','service_receivables.view','supplier_payables.view'] as $right)Tests\Support\IdentityFixture::grant($actor,$scope['tenant_id'],$scope['company_id'],$right,$scope['unit_id']);foreach(match($role){'prepare'=>['service_receivables.prepare','service_receivables.allocate','service_receivables.correction_request'],'validate'=>['service_receivables.confirm','service_receivables.correction_approve'],default=>[]} as $right)Tests\Support\IdentityFixture::grant($actor,$scope['tenant_id'],$scope['company_id'],$right,$scope['unit_id']);if($role!=='read')DB::table('pa_access')->insert($scope+['user_id'=>$actor,'person'=>'cost-qa-person-'.$actor,'action'=>$role==='prepare'?'prepare':'validate']);}
foreach(ModuleRecords::AREAS as $area)foreach(DB::table('a_'.$area)->where('tenant_id',$scope['tenant_id'])->where('company_id',$scope['company_id'])->get(['id','version']) as $row)foreach(['commercial','finance','accounting'] as $module)ModuleRecords::associate($area,$row->id,$g['actors']['prepare'],['tenant_id'=>$scope['tenant_id'],'company_id'=>$scope['company_id'],'unit_id'=>str_ends_with($area,'_drafts')?$scope['unit_id']:null],$module,'Explicit evidence sharing only for this NEW combined synthetic fixture.',false,$row->version);
$common=['created_by'=>$g['actors']['prepare'],'reason'=>'Compose existing authorised source rules in one new QA book.','created_at'=>now()];$accounts=[];foreach(['QA-CLIENTES','QA-VENDAS','QA-IMPOSTO-TESTE','QA-LIQUIDACAO-CLIENTE','QA-CONTRAPARTIDA','QA-FORNECEDOR'] as $code)$accounts[$code]=DB::table('gl_accounts')->insertGetId($scope+['code'=>$code,'label'=>'Conta artificial '.$code,'is_movement'=>true,'currency'=>'MZN']);
foreach(['QA-SALES'=>['sales.invoice','QA-CLIENTES','QA-VENDAS','QA-IMPOSTO-TESTE'],'QA-RECEIVABLE'=>['receivable.allocated','QA-LIQUIDACAO-CLIENTE','QA-CLIENTES',null],'QA-RECEIVABLE-REVERSE'=>['receivable.reversed','QA-CLIENTES','QA-LIQUIDACAO-CLIENTE',null],'QA-PAYABLE-JOURNAL'=>['supplier_payable.recognised','QA-CONTRAPARTIDA','QA-FORNECEDOR',null]] as $rule=>[$type,$d,$c,$tax])DB::table('gl_rules')->insert($scope+$common+['rule_key'=>$rule,'version'=>1,'policy'=>$type==='supplier_payable.recognised'?Tempo\Finance\GeneralLedger\PayableJournal::POLICY:'QA-SALES-JOURNAL-v1','effect_type'=>$type,'currency'=>'MZN','valid_from'=>'2026-10-01','valid_to'=>'2026-10-31','debit_account_id'=>$accounts[$d],'credit_account_id'=>$accounts[$c],'tax_account_id'=>$tax?$accounts[$tax]:null]);
DB::statement('CREATE TABLE pa_imported_sources(id bigserial PRIMARY KEY,tenant_id bigint,company_id bigint,unit_id bigint,payload jsonb,receipt_hash varchar(64))');$bundle=json_decode(file_get_contents($s['root'].'/source.json'),true,64,JSON_THROW_ON_ERROR);$doc=$bundle['source'];unset($doc['local_id']);$doc['document']=990004;$doc['number']='QA-COMPOSITION-SUPPLIER-60';$doc['payment_count']=0;$payload=Payables::canonical($doc);$local=DB::table('pa_imported_sources')->insertGetId($scope+['payload'=>$payload,'receipt_hash'=>hash('sha256',$payload)]);$finance=app(SupplierPayables::class);$p=$finance->engine($enter('prepare'))->command('prepare',$local,'Existing authorised controlled synthetic supplier60 receipt, not live import.',(string)Str::uuid());$o=$finance->engine($enter('validate'))->command('validate',$p['proposal_id'],'Distinct confirmation of original supplier60.',(string)Str::uuid());
file_put_contents($output,json_encode(['passed'=>true,'scope'=>$scope,'invoice_id'=>$sale['invoice'],'draft_id'=>$sale['draft'],'supplier_obligation_id'=>$o['obligation_id'],'supplier_source_id'=>$local,'cost_order_id'=>$g['order_id'],'sales_accounts'=>$accounts,'initial_journals'=>1,'initial_customer_debt'=>false,'initial_correction_groups'=>0,'existing_qa_rights_union_only'=>true,'purchasing_not_activated'=>true,'fixture_day'=>'2026-10-29'],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Three source families composed in a new book: actual test invoice1000, cost275/current295 and controlled supplier debt60; no customer debt/correction/postings beyond original275.\n";exit;
}
$cf=json_decode(file_get_contents($s['root'].'/ledger-composition-fixture.json'),true,64,JSON_THROW_ON_ERROR);
if($phase==='ledger-cost-composition-financial-core'){require __DIR__.'/financial-receipt-core.php';exit;}
if($phase==='ledger-cost-composition-financial-receipts'){require __DIR__.'/financial-receipt-proof.php';exit;}
if($phase==='ledger-cost-composition-industrial-compatibility'){
$out=$s['root'].'/composition-industrial-compatibility.json';if(file_exists($out))throw new LogicException('Completed compatibility preserved');$before=$sourceFingerprint();$u=$enter('read');$w=app(App\Modules\Accounting\LedgerComposition::class)->workspace($u);$p=(array)DB::table('gl_periods')->where($scope)->where('period_key','QA-2026-10')->orderBy('version')->first();$a=app(App\Modules\Accounting\SalesLedgerSource::class)->period($u,$p);$b=app(App\Modules\Accounting\CostLedgerSource::class)->period($u,$p);$engine=$ledger->engine($u);$fn=(new ReflectionProperty($engine,'additionalPeriod'))->getValue($engine);$packet=$fn($p);$report=$ledger->report($u,'trial-balance',['from_on'=>'2026-10-01','to_on'=>'2026-10-31']);
if(file_exists($s['root'].'/composition-demo-baseline.json')){$hash=hash_init('sha256');foreach(['wo_orders','wo_entries','wo_decisions','wo_commands','dc_rates','dc_calculations','dc_decisions','dc_commands','dc_events','dc_outbox','permission_grants','pa_access','pa_obligations','pa_commands','si_invoices','sr_records','sr_effects','gl_proposals','gl_reviews','gl_journals','gl_lines','gl_corrections','gl_correction_results','gl_period_checks','gl_period_reviews','gl_period_closures','gl_commands'] as $table){hash_update($hash,$table);hash_update($hash,DB::table($table)->orderBy($table==='dc_outbox'?'event_id':'id')->get()->toJson());}if(hash_final($hash)!==json_decode(file_get_contents($s['root'].'/composition-demo-baseline.json'),true)['fingerprint'])throw new LogicException('Old team composition changed since its valid baseline');}
if(config('industrial_ledger.synthetic_enabled')!==false||DB::getSchemaBuilder()->hasTable('gl_industrial_sources')||array_keys($w['panels'])!==['sales','costs','suppliers']||$packet!==['facts'=>$a['facts']+$b['facts'],'pending'=>$a['pending']+$b['pending'],'hash'=>hash('sha256',$a['hash'].$b['hash'])]||$report['totals']['debit_minor']!==($w['period']->status==='closed'?330500:27500)||$sourceFingerprint()!==$before)throw new LogicException('Existing8057 composition/provider packet or source state changed');file_put_contents($out,json_encode(['passed'=>true,'old8057_industrial_flag_off'=>true,'new_schema_absent'=>true,'existing_three_panels_unchanged'=>true,'two_provider_packet_hash_unchanged'=>true,'observed_gross_minor'=>$report['totals']['debit_minor'],'source_outbox_rights_unchanged'=>true,'read_only'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Existing8057 three-source composition/source/hash preserved, industrial default off/schema absent.\n";exit;
}
if(in_array($phase,['ledger-cost-composition-baseline','ledger-cost-composition-preservation'],true)){
$base=$s['root'].'/composition-demo-baseline.json';$out=$s['root'].'/composition-demo-preservation.json';$hash=hash_init('sha256');foreach(['wo_orders','wo_entries','wo_decisions','wo_commands','dc_rates','dc_calculations','dc_decisions','dc_commands','dc_events','dc_outbox','permission_grants','pa_access','pa_obligations','pa_commands','si_invoices','sr_records','sr_effects','gl_proposals','gl_reviews','gl_journals','gl_lines','gl_corrections','gl_correction_results','gl_period_checks','gl_period_reviews','gl_period_closures','gl_commands'] as $t){hash_update($hash,$t);hash_update($hash,DB::table($t)->orderBy($t==='dc_outbox'?'event_id':'id')->get()->toJson());}$fingerprint=hash_final($hash);
if($phase==='ledger-cost-composition-baseline'){if(file_exists($base))throw new LogicException('Completed baseline preserved');file_put_contents($base,json_encode(['passed'=>true,'fingerprint'=>$fingerprint],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Composition team baseline recorded privately.\n";exit;}
if(file_exists($out)||$fingerprint!==json_decode(file_get_contents($base),true)['fingerprint']||DB::table('gl_journals')->count()!==1||DB::table('gl_corrections')->exists()||DB::table('gl_period_closures')->exists()||DB::table('sr_effects')->exists())throw new LogicException('Team composition changed; no reset');
file_put_contents($out,json_encode(['passed'=>true,'all_business_source_outbox_rights_book_hashes_unchanged'=>true,'initial_journals'=>1,'initial_corrections'=>0,'initial_customer_effects'=>0,'initial_closures'=>0,'real_or_public_grants'=>0,'new_effect_types'=>0,'no_reset'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Team composition preserved after linked read/form proofs; no business commands.\n";exit;
}
if($phase==='ledger-cost-composition-workflow'){require __DIR__.'/ledger-composition-workflow.php';exit;}
if($phase==='ledger-cost-composition-cases'){require __DIR__.'/ledger-composition-cases.php';exit;}
throw new LogicException('Unknown composition phase');