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

17 lines
8.2 KiB
PHP

<?php
use App\Modules\Services\{MultiLineInvoiceCalculation,MultiLineDrafts,MultiLineInvoicePdf,ServiceWorkflow,ServiceInvoicing,FiscalRules,InvoicePdf};use Illuminate\Support\Str;use Illuminate\Support\Facades\DB;
require __DIR__.'/sgf-multiline-invoice-proof-bootstrap.php';$host->beginTransaction();try{
$actor=$select('commercial_prepare');$draftWriter=app(MultiLineDrafts::class);$prototype=['draft_id'=>null,'version'=>0,'source_version'=>0,'preparation_version'=>0,'name'=>'QA-PENDING-MULTI','client_id'=>$client,'currency'=>'MZN','price_mode'=>'undefined','service_date'=>null,'period_start'=>null,'period_end'=>null,'proposed_due_date'=>null,'payment_terms'=>null,'lines'=>[['service_id'=>$service,'description'=>'Linha ainda indeterminada','quantity'=>null,'unit_price'=>null,'unit'=>null]],'reason'=>'Rascunho incompleto explícito, sem tomar desconhecidos por zero.'];$count=$host->table('mi_drafts')->count();$p=$draftWriter->preview($actor,$prototype,(string)Str::uuid());$ok($host->table('mi_drafts')->count()===$count,'draft preview creates no persisted source or registry');$pending=$draftWriter->confirm($actor,$p['preview']);$ok($pending['version']===1&&$pending['new_documents']===0,'unknown dates mode quantity price and unit can remain a saved draft');$actor=$select('finance_prepare');$reject(fn()=>app(MultiLineInvoiceCalculation::class)->preview($actor,['draft_id'=>$pending['id'],'draft_version'=>1,'preparation_version'=>1,'rule_event_id'=>$ruleEvent,'reason'=>'Não assumir datas, quantidade ou preço indeterminados.'],(string)Str::uuid()),422,'incomplete draft cannot become a zero calculation');
$actor=$select('commercial_prepare');$tooMany=$prototype;$tooMany['lines']=array_fill(0,26,$prototype['lines'][0]);$reject(fn()=>$draftWriter->preview($actor,$tooMany,(string)Str::uuid()),422,'26 lines refused');$secret=$prototype;$secret['payment_terms']='token=not-a-real-secret';$reject(fn()=>$draftWriter->preview($actor,$secret,(string)Str::uuid()),422,'secret assignments refused by composed preparation');$bad=$prototype;$bad['lines'][0]['quantity']='1e3';$reject(fn()=>$draftWriter->preview($actor,$bad,(string)Str::uuid()),422,'exponential quantity refused');$bad=$prototype;$bad['scope']=['company_id'=>999];$reject(fn()=>$draftWriter->preview($actor,$bad,(string)Str::uuid()),422,'scope injection refused');$reject(fn()=>$draftWriter->preview($actor,$prototype,str_repeat('-',36)),422,'non UUID key refused before SQL');
$x=$fixture('added',['400','600'],'BOUNDARY');$actor=$select('finance_prepare');$calcWriter=app(MultiLineInvoiceCalculation::class);$input=['draft_id'=>$x['draft']['id'],'draft_version'=>1,'preparation_version'=>1,'rule_event_id'=>$ruleEvent,'reason'=>'Cálculo vinculado a origem e preparador actuais.'];$cut=$calcWriter->preview($actor,$input,(string)Str::uuid());$calc=$calcWriter->confirm($actor,$cut['preview']);
$host->beginTransaction();try{$host->table('permission_grants')->where($f['scope']+['user_id'=>$profiles['finance_prepare']['user_id'],'permission'=>'fiscal_rules.prepare'])->delete();$actor=$select('commercial_review');$reject(fn()=>DB::transaction(fn()=>$calcWriter->current($actor,$calc['id'],$x['draft']['id'])),403,'calc preparer authority revalidated by independent reviewer');}finally{$host->rollBack();}
$host->beginTransaction();try{$host->table('users')->where('id',$profiles['finance_prepare']['user_id'])->update(['name'=>'Identidade QA alterada apenas em rollback']);$actor=$select('commercial_review');$reject(fn()=>DB::transaction(fn()=>$calcWriter->current($actor,$calc['id'],$x['draft']['id'])),409,'changed preparer identity requires fresh calculation');}finally{$host->rollBack();}
$host->beginTransaction();try{$actor=$select('finance_review','accounting');$rules=app(FiscalRules::class);$rule=$host->table('fr_events')->where('id',$ruleEvent)->first();$revision=(int)$host->table('fr_events')->where($f['scope'])->where('rule_key',$rule->rule_key)->max('revision');$data=['action'=>'withdraw','rule_key'=>$rule->rule_key,'revision'=>$revision,'target_id'=>$ruleEvent,'data'=>[],'reason'=>'Retirada de regra apenas para testar a fronteira documental em rollback.'];$withdraw=$rules->preview($actor,$data,(string)Str::uuid());$rules->confirm($actor,$withdraw['preview']);$actor=$select('commercial_review');$reject(fn()=>DB::transaction(fn()=>$calcWriter->current($actor,$calc['id'],$x['draft']['id'])),409,'withdrawn common rule blocks existing calculation');}finally{$host->rollBack();}
$actor=$select('finance_prepare');$pendingCut=$calcWriter->preview($actor,$input,(string)Str::uuid());$actor=$select('commercial_prepare');$edit=$prototype;$edit['draft_id']=$x['draft']['id'];$edit['version']=1;$edit['source_version']=1;$edit['preparation_version']=1;$edit['name']='QA-MULTI-EDIT';$edit['price_mode']='added';$edit['period_start']='2026-11-01';$edit['period_end']='2026-11-30';$edit['lines']=[['service_id'=>$service,'description'=>'Linha400','quantity'=>'1','unit_price'=>'400','unit'=>'UN'],['service_id'=>$service,'description'=>'Linha601','quantity'=>'1','unit_price'=>'601','unit'=>'UN']];$e=$draftWriter->preview($actor,$edit,(string)Str::uuid());$updated=$draftWriter->confirm($actor,$e['preview']);$ok($updated['version']===2&&$updated['source_version']===2&&$updated['preparation_version']===2,'composed edit updates all own versions atomically');$actor=$select('finance_prepare');$reject(fn()=>$calcWriter->confirm($actor,$pendingCut['preview']),409,'unconfirmed old preview refuses changed source');$prior=$calcWriter->confirm($actor,$cut['preview']);$ok($prior['replayed']&&$prior['id']===$calc['id'],'previous successful confirmation remains historical recovery, not a new calculation');$actor=$select('commercial_review');$reject(fn()=>DB::transaction(fn()=>$calcWriter->current($actor,$calc['id'],$x['draft']['id'])),409,'old calculation cannot validate changed line');
$select('read_only');$actor=$profiles['read_only']['user_id'];$reject(fn()=>$draftWriter->preview($actor,$prototype,(string)Str::uuid()),403,'read only cannot prepare new draft');$select('commercial_prepare','finance');$reject(fn()=>$draftWriter->preview($profiles['commercial_prepare']['user_id'],$prototype,(string)Str::uuid()),403,'wrong module cannot prepare commercial draft');
// New source calculation and PDF limit sample use the same writers, then roll back all data.
$x=$fixture('added',array_fill(0,25,'1'),'PDF25');$actor=$select('finance_prepare');$input=['draft_id'=>$x['draft']['id'],'draft_version'=>1,'preparation_version'=>1,'rule_event_id'=>$ruleEvent,'reason'=>'Conferência do limite25 por linhas, sem outra instalação.'];$cut=$calcWriter->preview($actor,$input,(string)Str::uuid());$max=$calcWriter->confirm($actor,$cut['preview']);$ok(count($max['result']['lines'])===25&&$max['result']['total']==='29.00','25 lines use common truncation and sum29');
$snp=DB::table('mi_calculations')->where('id',$max['id'])->value('snapshot');$snp=json_decode($snp,true);$doc=['renderer'=>MultiLineInvoicePdf::VERSION,'calculation_policy'=>MultiLineInvoiceCalculation::POLICY,'series_code'=>'TEST-MULTI-PDF25','number'=>0,'issued_local'=>'2026-11-11 12:00:00','timezone'=>ServiceInvoicing::ZONE,'document'=>$snp['document'],'calculation'=>['common_document_result'=>$max['result']],'profile_identifier'=>$snp['common']['rule']['rule_key'],'profile_version'=>$snp['common']['rule']['rule_version'],'rate_percent'=>'16','draft_id'=>$x['draft']['id'],'rule_id'=>0,'rule_version'=>0];$lines=MultiLineInvoicePdf::lines($doc);$ok(InvoicePdf::supports($lines)&&count(array_filter($lines,fn($l)=>$l[0]==='section'&&str_starts_with($l[1],'Linha ')))===25,'PDF projection includes all25 lines');$pdf=app(InvoicePdf::class)->render($doc);file_put_contents($work.'/multiline-25-lines-private.pdf',$pdf);$ok(substr_count($pdf,'/Type /Page ')>1&&str_starts_with($pdf,'%PDF-1.4'),'25 line PDF paginated with preserved test marks');
}finally{$host->rollBack();}
file_put_contents($work.'/multiline-invoice-boundaries.json',json_encode(['passed'=>true,'checks'=>$checks,'fixtures_rolled_back'=>true,'new_grants'=>0,'new_demo'=>false,'pdf_sample_not_issued'=>true],JSON_PRETTY_PRINT));echo json_encode(['passed'=>true,'checks'=>count($checks),'proof'=>$work.'/multiline-invoice-boundaries.json','pdf_sample'=>$work.'/multiline-25-lines-private.pdf']).PHP_EOL;