Files
SGF/scripts/sgf-document-math-proof.php
T
2026-11-11 09:00:17 +02:00

14 lines
5.8 KiB
PHP

<?php
use App\Modules\Services\FiscalDocumentMath;use Illuminate\Support\Facades\DB;
require __DIR__.'/sgf-document-math-proof-bootstrap.php';$before=[];foreach(['fr_calculations','fr_events','fr_commands','an_criteria','an_results','br_events','fx_versions','si_invoices','sr_effects','cash_events']as$t)$before[$t]=$host->table($t)->orderBy('id')->get()->toJson();$host->beginTransaction();try{
$p=$rulePreview(13,$documentInput('net_added',[$line('N1','400'),$line('N2','600')]));$r=$p['impact']['result'];$ok($r['base_document']==='1000.00'&&$r['tax_document']==='160.00'&&$r['total']==='1160.00'&&count($r['lines'])===2,'net400+600 exact1000+160=1160');$ok($host->table('fr_calculations')->orderBy('id')->get()->toJson()===$before['fr_calculations'],'document preview fully reverted');$included=$rulePreview(13,$documentInput('gross_included',[$line('G1','464'),$line('G2','696')]));$r=$included['impact']['result'];$ok($r['base_document']==='1000.00'&&$r['tax_document']==='160.00'&&$r['total']==='1160.00','included464+696 decomposes1000+160');
[$hundred,$cut]=$ruleCommand(13,$documentInput('gross_included',[$line('G100','100')]));$selectRule(17);$detail=$rules->detail(17,$hundred['id'],'calculation');$r=$detail['result'];$ok($r['base_document']==='86.21'&&$r['tax_document']==='13.79'&&$r['total']==='100.00','gross100 produces86.21+13.79');$ok($r['lines'][0]['base_exact_fraction']==='2500/29'&&$r['lines'][0]['tax_exact_fraction']==='400/29'&&$r['base_difference_fraction']==='9/2900'&&$r['tax_difference_fraction']==='9/2900','both exact fractions and differences9/2900 preserved');$report=$rules->documentReport(17,$hundred['id']);$ok(hash('sha256',$report['bytes'])===$report['sha256']&&str_contains($report['bytes'],'86.21;13.79;100.00')&&str_contains($report['bytes'],'SEM VALIDADE FISCAL'),'internal per-line map matches frozen result');$selectRule(13);$again=$rules->confirm(13,$cut['preview']);$ok($again['replayed']&&$again['id']===$hundred['id'],'same document calculation key no duplicate');
$small=$rulePreview(13,$documentInput('net_added',[$line('A','0.03'),$line('B','0.03'),$line('C','0.03')]));$r=$small['impact']['result'];$ok($r['base_document']==='0.09'&&$r['tax_document']==='0.00'&&$r['tax_difference_fraction']==='9/625','per-line tax sum preserved instead of aggregate re-round');$noncent=$rulePreview(13,$documentInput('net_added',[$line('P','0.001')]));$ok($noncent['impact']['result']['state']==='pending_representation'&&$noncent['impact']['result']['base_document']===null,'non-cent net base remains pending');$grossNoncent=$rulePreview(13,$documentInput('gross_included',[$line('P','0.001')]));$ok($grossNoncent['impact']['result']['state']==='pending_representation'&&$grossNoncent['impact']['result']['total']===null,'non-cent included gross remains pending');
$many=[];for($i=1;$i<=25;$i++)$many[]=$line('L'.$i,'1');$limit=$rulePreview(13,$documentInput('net_added',$many));$ok(count($limit['impact']['result']['lines'])===25&&$limit['impact']['result']['tax_document']==='4.00','25 line boundary exact');$many[]=$line('L26','1');$reject(fn()=>$rulePreview(13,$documentInput('net_added',$many)),422,'26 lines refused');$reject(fn()=>$rulePreview(13,$documentInput('net_added',[])),422,'empty lines refused');$reject(fn()=>$rulePreview(13,$documentInput('net_added',[$line('X','1','0')])),422,'zero quantity refused');$reject(fn()=>$rulePreview(13,$documentInput('net_added',[$line('X','-1')])),422,'negative price refused');$reject(fn()=>$rulePreview(13,$documentInput('net_added',[$line('X','1'),$line('X','2')])),422,'duplicate references refused');$bad=$documentInput('net_added',[$line('X','1')]);$bad['data']['rate']='0.99';$reject(fn()=>$rulePreview(13,$bad),422,'client rate injection refused');$reject(fn()=>$rulePreview(17,$documentInput('net_added',[$line('X','1')])),403,'read-only calculation writer refused');$wrong=$documentInput('net_added',[$line('X','1')]);$wrong['data']['currency']='USD';$out=$rulePreview(13,$wrong);$ok($out['impact']['result']['state']==='pending'&&$out['impact']['result']['tax_document']===null,'wrong currency does not become zero');
$outOfDate=$rulePreview(13,$documentInput('net_added',[$line('X','1')],null,'2026-12-01'));$ok($outOfDate['impact']['result']['state']==='pending','rule expiry enforced in document consumer');
$csv=$rulePreview(13,$documentInput('gross_included',[$line(' =DANGER','100')]));$selectRule(13);$csvRecord=$rules->confirm(13,$csv['preview']);$selectRule(17);$safe=$rules->documentReport(17,$csvRecord['id']);$ok(str_contains($safe['bytes'],"' =DANGER"),'CSV formula-like reference escaped');
$inflight=$rulePreview(13,$documentInput('net_added',[$line('C','100')]));$source=$host->table('fr_events')->find($ruleId);[$withdraw]=$ruleCommand(14,$ruleInput('withdraw',$source->revision,[],$ruleId,$source->rule_key));$selectRule(13);$reject(fn()=>$rules->confirm(13,$inflight['preview']),409,'source withdrawal invalidates in-flight document calculation');$selectRule(17);$old=$rules->detail(17,$hundred['id'],'calculation');$ok($old['result']===$detail['result']&&$old['current_evaluation']['state']==='pending','old line result preserved after rule withdrawal');
}finally{$host->rollBack();}
// Pure arithmetic for the already-approved successor17%, no new rule or document.
$future=FiscalDocumentMath::calculate([$line('F','1170')],'gross_included','0.17');$ok($future['base_document']==='1000.00'&&$future['tax_document']==='170.00'&&$future['total']==='1170.00','successor artificial17 included1170=1000+170');foreach($before as$t=>$json)$ok($host->table($t)->orderBy('id')->get()->toJson()===$json,'unchanged '.$t);file_put_contents($work.'/document-math-result.json',json_encode(['passed'=>true,'checks'=>$checks,'rolled_back'=>true,'new_rights'=>0,'new_financial_effects'=>0,'new_demo'=>false],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/document-math-result.json']).PHP_EOL;