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

32 lines
12 KiB
PHP

<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use App\Modules\Industry\IndustrialCosts;
use App\Modules\Accounting\IndustrialLedgerSource;
use Tempo\Finance\Payables\Payables;
if(config('industrial_ledger.synthetic_enabled')!==true||!DB::getSchemaBuilder()->hasTable('gl_industrial_sources'))throw new LogicException('Explicit new industrial QA 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']]]);
$ic=app(IndustrialCosts::class);$provider=app(IndustrialLedgerSource::class);
if($phase==='ledger-cost-industrial-ready'){
$output=$s['root'].'/industrial-fixture.json';if(file_exists($output)||DB::table('ic_lots')->exists())throw new LogicException('Fresh industrial fixture only');$grants=DB::table('permission_grants')->pluck('id')->all();$base=['manager'=>$enter('prepare'),'reader'=>$enter('validate'),'tenant'=>$scope['tenant_id'],'company'=>$scope['company_id'],'unit'=>$scope['unit_id']];$i=Tests\Support\IndustrialCostFixture::create($base);$r=$ic->calculate($i['lot'],$enter('prepare')->id,['version'=>1,'previous_id'=>null,'refs'=>$i['refs'],'currency'=>'MZN'],(string)Str::uuid());$ic->decide($i['lot'],$enter('validate')->id,['type'=>'result','id'=>$r['id'],'version'=>1,'decision'=>'approved','reason'=>'Distinct actual industrial1000+500+300=1800; residual300 retained separately.'],(string)Str::uuid());DB::table('permission_grants')->whereNotIn('id',$grants)->delete();foreach(['prepare'=>['view','prepare'],'validate'=>['view','validate'],'read'=>['view']] as $role=>$rights)foreach($rights as $right)Tests\Support\IdentityFixture::grant($g['actors'][$role],$scope['tenant_id'],$scope['company_id'],'industrial_costs.'.$right,$scope['unit_id']);$origins=[];foreach(['material','labour','pool','machine','production'] as $kind)$origins[$kind]=DB::table('ic_inputs')->where('id',$i['refs'][$kind]['id'])->value('source_ref');DB::table('gl_industrial_sources')->insert($scope+['lot_id'=>$i['lot'],'origins'=>Payables::canonical($origins),'period_key'=>'QA-2026-10','created_by'=>$g['actors']['prepare'],'reason'=>'Explicit new authorised synthetic lot perimeter.','created_at'=>now()]);$accounts=[];foreach(['QA-CUSTO-INDUSTRIAL','QA-COMPENSACAO-INDUSTRIAL'] as $code)$accounts[$code]=DB::table('gl_accounts')->insertGetId($scope+['code'=>$code,'label'=>'Conta artificial '.$code,'currency'=>'MZN','is_movement'=>true]);DB::table('gl_rules')->insert($scope+['rule_key'=>'QA-INDUSTRIAL','version'=>1,'policy'=>'QA-INDUSTRIAL-JOURNAL-v1','effect_type'=>'industry.cost.validated','currency'=>'MZN','valid_from'=>'2026-10-01','valid_to'=>'2026-10-31','debit_account_id'=>$accounts['QA-CUSTO-INDUSTRIAL'],'credit_account_id'=>$accounts['QA-COMPENSACAO-INDUSTRIAL'],'created_by'=>$g['actors']['prepare'],'reason'=>'Approved artificial industrial recognition rule.','created_at'=>now()]);$source=$provider->source($enter('read'),$i['lot']);if($source['principal_minor']!==180000||$source['residual_exact']!=='300'||$source['unit_cost_exact']!=='180')throw new LogicException('Actual industrial source must reconcile1800/180/residual300');file_put_contents($output,json_encode(['passed'=>true,'scope'=>$scope,'lot_id'=>$i['lot'],'result_id'=>$r['id'],'refs'=>$i['refs'],'accounts'=>$accounts,'principal_minor'=>180000,'residual_exact'=>'300','good_units'=>'10','unit_cost_exact'=>'180','initial_industrial_journals'=>0,'actual_owner_workflow'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Actual industrial result1800/10UN=180 approved; residual300 separate; no industrial journal.\n";exit;
}
$i=json_decode(file_get_contents($s['root'].'/industrial-fixture.json'),true,64,JSON_THROW_ON_ERROR);$body=['effect_type'=>'industry.cost.validated','source_id'=>$i['lot_id'],'journal_on'=>'2026-10-29','period_key'=>'QA-2026-10','rule_key'=>'QA-INDUSTRIAL','reason'=>'Capture actual independently validated synthetic industrial1800; residual excluded.'];
$fingerprint=function(){$h=hash_init('sha256');foreach(['ic_products','ic_lots','ic_inputs','ic_results','ic_decisions','ic_commands','ic_events','ic_outbox','permission_grants'] as $t){hash_update($h,$t);hash_update($h,DB::table($t)->orderBy($t==='ic_outbox'?'event_id':'id')->get()->toJson());}return hash_final($h);};
if($phase==='ledger-cost-industrial-legacy-after-correction'){
$out=$s['root'].'/industrial-demo-after-correction.json';if(file_exists($out))throw new LogicException('Preserved old demo proof');$old=json_decode(file_get_contents($s['root'].'/industrial-demo-baseline.json'),true);$source=$provider->source($enter('read'),$i['lot_id']);$refused=false;try{$provider->correctionSource($enter('read'),'industry.cost.reversal',1);}catch(Symfony\Component\HttpKernel\Exception\HttpException $e){$refused=$e->getStatusCode()===403;}$packet=(array)DB::table('gl_periods')->where($scope)->orderBy('version')->first();$h=hash_init('sha256');$facts=0;$pending=0;foreach([app(App\Modules\Accounting\SalesLedgerSource::class),app(App\Modules\Accounting\CostLedgerSource::class),$provider] as $p){$v=$p->period($enter('read'),$packet);$facts+=$v['facts'];$pending+=$v['pending'];hash_update($h,$v['hash']);}$expected=['facts'=>$facts,'pending'=>$pending,'hash'=>hash_final($h)];$engine=$ledger->engine($enter('read'));$callback=(new ReflectionProperty($engine,'additionalPeriod'))->getValue($engine);
if(config('industrial_ledger.correction_enabled')!==false||DB::getSchemaBuilder()->hasTable('gl_industrial_correction_capability')||$fingerprint()!==$old['source_hash']||$source['principal_minor']!==180000||!$refused||$callback($packet)!==$expected||DB::table('gl_journals')->count()!==$old['journal_count']||DB::table('gl_corrections')->exists())throw new LogicException('Existing8058 flags/schema/source/outbox/rights/provider packet changed');file_put_contents($out,json_encode(['passed'=>true,'old8058_source_outbox_rights_exact'=>true,'correction_default_off'=>true,'correction_schema_absent'=>true,'uncomposed_correction_refused403'=>true,'three_provider_packet_unchanged'=>true,'old_initial_journal_count'=>$old['journal_count'],'read_only'=>true],JSON_PRETTY_PRINT));echo "Old8058 source/outbox/rights, three-provider packet and original book intact; new correction flag/schema absent.\n";exit;
}
if(str_starts_with($phase,'ledger-cost-industrial-correction-')){require __DIR__.'/ledger-industrial-correction-proof.php';exit;}
if(in_array($phase,['ledger-cost-industrial-baseline','ledger-cost-industrial-preservation'],true)){$path=$s['root'].'/industrial-demo-baseline.json';$hash=$fingerprint();if($phase==='ledger-cost-industrial-baseline'){if(file_exists($path))throw new LogicException('Preserved baseline');file_put_contents($path,json_encode(['passed'=>true,'source_hash'=>$hash,'journal_count'=>DB::table('gl_journals')->count()]));exit;}$out=$s['root'].'/industrial-demo-preservation.json';$old=json_decode(file_get_contents($path),true);if(file_exists($out)||$hash!==$old['source_hash']||DB::table('gl_journals')->count()!==$old['journal_count']||DB::table('gl_journals')->where('snapshot->source->effect_type','industry.cost.validated')->exists()||DB::table('gl_period_closures')->exists())throw new LogicException('Team state changed; no reset');file_put_contents($out,json_encode(['passed'=>true,'sources_outbox_rights_unchanged'=>true,'initial_industrial_journals'=>0,'initial_closures'=>0,'no_reset'=>true],JSON_PRETTY_PRINT));exit;}
if($phase==='ledger-cost-industrial-cases'){require __DIR__.'/ledger-industrial-cases.php';exit;}
if($phase==='ledger-cost-industrial-extra-cases'){require __DIR__.'/ledger-industrial-extra-cases.php';exit;}
if($phase==='ledger-cost-industrial-final'){
$out=$s['root'].'/industrial-final.json';if(file_exists($out))throw new LogicException('Completed final preserved');$source=$provider->source($enter('read'),$i['lot_id']);$j=DB::table('gl_journals')->where('snapshot->source->effect_type','industry.cost.validated')->first();$snapshot=$j?json_decode($j->snapshot,true):null;$totals=$ledger->report($enter('read'),'trial-balance',['from_on'=>'2026-10-01','to_on'=>'2026-10-31']);$data=$ledger->industrial($enter('prepare'),$i['lot_id']);$page=view('accounting.industrial-ledger',['data'=>$data])->render();$workspace=app(App\Modules\Accounting\LedgerComposition::class)->workspace($enter('read'));
if(!$snapshot||Payables::canonical($snapshot['source'])!==Payables::canonical($source)||DB::table('gl_journals')->count()!==9||DB::table('gl_lines')->count()!==19||DB::table('gl_period_closures')->count()!==1||$totals['totals']['debit_minor']!==510500||$totals['totals']['credit_minor']!==510500||str_contains($page,'data-action=')||$workspace['panels']['industrial']['rows'][0]['original_journal_id']!==$j->id||DB::table('ic_outbox')->where('state','<>','captured_no_stock_or_accounting')->exists())throw new LogicException('Industrial final reconciliation/preservation/closed UI differs');DB::select('SELECT gl_assert_balanced(id) FROM gl_journals');file_put_contents($out,json_encode(['passed'=>true,'journals'=>9,'lines'=>19,'gross_debit_minor'=>510500,'gross_credit_minor'=>510500,'closures'=>1,'current_industrial_source_matches_posted_evidence'=>true,'residual_exact'=>$source['residual_exact'],'residual_not_posted'=>true,'closed_industrial_ui_without_commands'=>true,'all_industrial_outbox_still_captured'=>true,'panel_links_posted_industrial_journal'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Joint final:9 journals/19 lines, gross5105; industrial source/outbox exact, residual300 separate, closed UI read only.\n";exit;
}
if(in_array($phase,['ledger-cost-industrial-race','ledger-cost-industrial-worker'],true)){require __DIR__.'/ledger-industrial-race.php';exit;}
if($phase==='ledger-cost-industrial-workflow'){
$out=$s['root'].'/industrial-workflow.json';if(file_exists($out))throw new LogicException('Completed proof preserved');$before=$fingerprint();$p=$command('prepare','effect-prepare',$body);$command('validate','validate',['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'decision'=>'approved','reason'=>'Distinct result1800 and exact two-line impact conference.']);$post=['proposal_id'=>$p['proposal_id'],'revision'=>$p['revision'],'reason'=>'Apply selected industrial1800 once; residual300 never posted.'];$key=(string)Str::uuid();$j=$command('prepare','post',$post,$key);$repeat=$command('prepare','post',$post,$key);$other=$command('prepare','post',$post);$report=$ledger->report($enter('read'),'account-ledger',['from_on'=>'2026-10-01','to_on'=>'2026-10-31','account_id'=>$i['accounts']['QA-CUSTO-INDUSTRIAL']]);$book=$ledger->listing($enter('read'),['industrial_lot_id'=>$i['lot_id']]);if(!$repeat['replayed']||$other['journal_id']!==$j['journal_id']||count($book['journals'])!==1||count($book['lines'])!==2||count($book['events'])!==3||$report['closing_net_minor']!==180000||$before!==$fingerprint())throw new LogicException('Industrial effect/reports/preservation mismatch');DB::select('SELECT gl_assert_balanced(?)',[$j['journal_id']]);file_put_contents($out,json_encode(['passed'=>true,'journal_id'=>$j['journal_id'],'journals'=>1,'lines'=>2,'events'=>3,'principal_minor'=>180000,'cost_per_unit'=>'180','good_units'=>'10','residual_exact'=>'300','residual_not_posted'=>true,'source_outbox_rights_unchanged'=>true,'same_and_other_key_single_effect'=>true,'typed_filter_and_reports'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo "Industrial1800 posted once/two lines; residual300 separate; typed account/source reconciled.\n";exit;
}
throw new LogicException('Unknown industrial phase');