Files
SGF/scripts/sgf-multiline-invoice-preservation.php
2026-11-11 15:04:01 +02:00

8 lines
5.4 KiB
PHP

<?php
$book='8085';require __DIR__.'/sgf-multiline-invoice-proof-bootstrap.php';$before=json_decode(file_get_contents(json_decode(file_get_contents($s['root'].'/multiline-invoice-backup-pointer.json'),true)['root'].'/preservation-private.json'),true);$expected=['host.si_invoices'=>2,'host.sr_records'=>2,'host.sr_effects'=>2,'host.sr_commands'=>4,'host.sr_events'=>4,'host.sr_outbox'=>4,'host.si_proposals'=>3,'host.si_decisions'=>3,'host.si_counters'=>1,'host.si_commands'=>2,'host.si_events'=>2,'host.si_outbox'=>2,'host.a_service_drafts'=>2,'host.a_billing_drafts'=>2,'host.a_history'=>4,'host.a_events'=>4,'host.a_outbox'=>4,'host.a_commands'=>4,'native.gl_journals'=>2,'native.audit_logs'=>8,'native.fs_versions'=>2,'native.fs_results'=>1,'native.fs_result_lines'=>23,'native.fs_commands'=>2,'native.gl_proposals'=>2,'native.gl_reviews'=>2,'native.gl_lines'=>6,'native.gl_commands'=>6,'native.gl_events'=>6];$changes=[];$canon=fn($r)=>Tempo\Finance\Payables\Payables::canonical((array)$r);foreach(['host','native']as$n){$db=$n==='host'?$host:$native;foreach($before[$n]as$t=>$json){$old=json_decode($json,true);$now=$db->table($t)->get()->map(fn($r)=>(array)$r)->all();$seen=array_fill_keys(array_map($canon,$now),true);foreach($old as$r)if(!isset($seen[$canon($r)]))throw new LogicException('Original changed '.$n.'.'.$t);$delta=count($now)-count($old);$ok($delta===($expected[$n.'.'.$t]??0),'original rows preserved with exact append '.$n.'.'.$t);if($delta)$changes[$n.'.'.$t]=$delta;}}foreach($before['files']as$f=>$sha)$ok(hash_file('sha256',$s['root'].'/'.$f)===$sha,'binding preserved '.$f);
$ok($native->table('gl_journals')->count()===31&&$native->table('gl_accounts')->count()===24,'31 journals24 accounts no new accounts');foreach($native->table('gl_journals')->pluck('id')as$id)$native->select('SELECT gl_assert_balanced(?)',[$id]);$ok(true,'all31 journals satisfy native balance guard read only');$ok($native->table('gl_periods')->orderByDesc('version')->value('status')==='open','period remains open');$ok($host->table('mi_drafts')->count()===2&&$host->table('mi_calculations')->count()===2&&$host->table('mi_commands')->count()===8,'exact2drafts2calculations8newadministrative receipts');$ok((int)$host->table('si_counters')->where('code','TEST-MULTI-2026')->value('last_number')===2,'only new series advanced to2');
$progress=json_decode(file_get_contents($s['root'].'/multiline-invoice-http-progress-private.json'),true)['steps'];$actor=$select('finance_review','finance');$sr=app(App\Modules\Services\ServiceReceivables::class);foreach(['added','included']as$mode){$i=$progress[$mode.'Issue']['result']['id'];$ob=$progress[$mode.'Obligation']['result']['id'];$position=$sr->position($ob,$actor);$ok($position['balance']==='1160.00','new receivable uncollected1160 '.$mode);$rows=$native->table('gl_lines')->where('journal_id',$progress[$mode.'JournalPost']['result']['journal_id'])->orderBy('line_no')->get();$ok($rows->count()===3&&(int)$rows[0]->debit_minor===116000&&(int)$rows[1]->credit_minor===100000&&(int)$rows[2]->credit_minor===16000,'exact original3line writer1160/1000/160 '.$mode);}
$backup=json_decode(file_get_contents($s['root'].'/multiline-invoice-backup-pointer.json'),true)['root'];$oldPhp=file_get_contents($backup.'/before/app/Modules/Services/InvoicePdf.php');$oldPhp=str_replace('final class InvoicePdf','final class InvoicePdfLegacyMultilineProof',$oldPhp);eval(substr($oldPhp,5));$legacy=0;foreach(json_decode($before['host']['si_invoices'],true)as$row){$snapshot=json_decode($row['snapshot'],true);if(($snapshot['renderer']??null)===App\Modules\Services\InvoicePdf::VERSION){$ok(App\Modules\Services\InvoicePdfLegacyMultilineProof::lines($snapshot)===App\Modules\Services\InvoicePdf::lines($snapshot),'old one-line PDF projection unchanged invoice'.$row['id']);$legacy++;}$bytes=base64_decode($row['pdf_base64'],true);$ok(hash('sha256',$bytes)===$row['pdf_sha256'],'old frozen PDF hash intact invoice'.$row['id']);}$ok($legacy>0,'legacy one-line branch actually exercised without repeating emission');
foreach(['ServiceWorkflow.php','ServiceCalculation.php','ServiceFiscal.php','FiscalDocumentMath.php','DocumentPreview.php']as$file)$ok(hash_file('sha256',$backup.'/before/app/Modules/Services/'.$file)===hash_file('sha256',$s['app'].'/app/Modules/Services/'.$file),'original source unchanged '.$file);$install=json_decode(file_get_contents(json_decode(file_get_contents($s['root'].'/multiline-invoice-installation-pointer.json'),true)['root'].'/multiline-invoice-installation.json'),true);foreach($install['hashes']as$file=>$sha)if(hash_file('sha256',$s['app'].'/'.$file)!==$sha)throw new LogicException('Installation hash drift '.$file);$ok(true,'all installed files match recorded SHA');$pub=$host->selectOne("SELECT EXISTS(SELECT 1 FROM information_schema.table_privileges WHERE grantee='PUBLIC' AND table_name IN('mi_meta','mi_calculations','mi_commands','mi_draft_meta','mi_drafts')) p")->p;$ok(!$pub,'no PUBLIC access to new administrative tables');
file_put_contents($work.'/multiline-invoice-preservation.json',json_encode(['passed'=>true,'checks'=>$checks,'original_sets'=>count($before['host'])+count($before['native']),'appends'=>$changes,'new_grants'=>0,'new_accounts'=>0,'legacy_pdf_projections'=>$legacy,'new_demo'=>false,'cache_cleared'=>false,'old_rows_identical'=>true],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/multiline-invoice-preservation.json']).PHP_EOL;