commit melhorias1
This commit is contained in:
@@ -68,6 +68,9 @@ final class ServiceInvoicing
|
||||
abort_unless($d['code']===$in['identifier'],422,'Identidade da série deve coincidir com o código sintético.');
|
||||
if($previous) { $old=$this->data($previous); abort_unless($old['code']===$d['code'] && (int)$old['first_number']===(int)$d['first_number'],422,'Não reiniciar ou transferir contador existente.'); }
|
||||
$d['first_number']=(int)$d['first_number'];
|
||||
} elseif(($in['data']['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY){
|
||||
$d=app(MultiLineInvoiceIssuance::class)->temporal($actor,$in['data'],$s,$previous?$this->data($previous):null);
|
||||
abort_if(DB::table('si_proposals')->where($s)->where('kind','temporal')->whereRaw("data->>'draft_id' = ?",[(string)$d['draft_id']])->where('identifier','<>',$in['identifier'])->exists(),409,'Use a identidade temporal existente.');
|
||||
} else {
|
||||
$d=$this->exact($in['data'],$common+['draft_id'=>'required|integer|min:1','draft_version'=>'required|integer|min:1','preparation_version'=>'required|integer|min:1','profile_proposal_id'=>'required|integer|min:1','period_start'=>'required|date_format:Y-m-d','period_end'=>'required|date_format:Y-m-d|after_or_equal:period_start','issue_window_start'=>'required|date_format:Y-m-d','issue_window_end'=>'required|date_format:Y-m-d|after_or_equal:issue_window_start','event'=>'required|in:on_issue_within_deadline','readiness_basis'=>'required|string|max:4000']);
|
||||
$view=$this->preview->captureDraftForIssuance((int)$d['draft_id'],$actor);
|
||||
@@ -90,6 +93,7 @@ final class ServiceInvoicing
|
||||
$d=$this->exact($input,['version'=>'required|integer|min:1','decision'=>'required|in:approved,refused','reason'=>'required|string|max:4000']);
|
||||
$p=$this->row($id,$s); abort_unless((int)$p->version===(int)$d['version'] && (int)$p->actor_id!==$actor,403,'Validador distinto e versão exacta obrigatórios.');
|
||||
abort_if($this->decision($id),409,'Decisão imutável; correcção exige nova versão.');
|
||||
$mi=$this->data($p);if(($mi['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY){$fingerprint=$mi['calculation_fingerprint'];unset($mi['calculation_fingerprint']);$fresh=app(MultiLineInvoiceIssuance::class)->temporal($actor,$mi,$s);abort_unless(hash_equals($fingerprint,$fresh['calculation_fingerprint']),409,'A captura mudou; confira nova proposta.');}
|
||||
DB::table('si_decisions')->insert(['proposal_id'=>$id,'actor_id'=>$actor,'decision'=>$d['decision'],'reason'=>$d['reason'],'created_at'=>now()]);
|
||||
Audit::record('service_invoice.decided',$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>$p->version,'reason'=>$d['reason']]);
|
||||
return ['id'=>$id,'decision'=>$d['decision']];
|
||||
@@ -101,7 +105,7 @@ final class ServiceInvoicing
|
||||
if(!ServiceCalculation::isolated()) return ['real_blocked'=>true,'test_enabled'=>false,'notice'=>'Emissão real bloqueada: conformidade ainda não demonstrada.','proposals'=>[],'invoices'=>[]];
|
||||
TestingDatabaseGuard::assertSafe();
|
||||
if(!Schema::hasTable('si_invoices')) return ['real_blocked'=>true,'notice'=>'Migração documental ainda não aplicada.','proposals'=>[],'invoices'=>[]];
|
||||
return ['real_blocked'=>true,'test_enabled'=>true,'notice'=>self::NOTICE,'proposals'=>DB::table('si_proposals')->where($s)->orderByDesc('id')->get()->map(fn($p)=>(array)$p+['definition'=>$this->data($p),'decision'=>$this->decision($p->id)])->all(),'invoices'=>\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->orderByDesc('id')->get(['id','series_code','number','draft_id','issued_at'])->all()];
|
||||
return ['real_blocked'=>true,'test_enabled'=>true,'notice'=>self::NOTICE,'proposals'=>DB::table('si_proposals')->where($s)->orderByDesc('id')->get()->map(fn($p)=>(array)$p+['definition'=>$this->data($p),'decision'=>$this->decision($p->id)])->all(),'invoices'=>QualifiedInvoiceSource::query()->where($s)->orderByDesc('id')->get(['id','series_code','number','draft_id','issued_at'])->all()];
|
||||
}
|
||||
public function review(int $draft,int $series,int $rule,int $actor): array
|
||||
{
|
||||
@@ -115,6 +119,7 @@ final class ServiceInvoicing
|
||||
}
|
||||
private function reviewInside(int $draft,int $series,int $rule,int $actor,array $s): array
|
||||
{
|
||||
$mi=$this->data($this->row($rule,$s));if(($mi['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY)return app(MultiLineInvoiceIssuance::class)->review($draft,$series,$rule,$actor,$s);
|
||||
$view=$this->preview->captureDraftForIssuance($draft,$actor); $date=now(self::ZONE)->format('Y-m-d'); $blocks=[];
|
||||
$p=$this->row($series,$s); $t=$this->row($rule,$s); $sd=$this->data($p); $td=$this->data($t);
|
||||
if($p->kind!=='series' || !$this->approved($p,$s,$date) || ($sd['state'] ?? null)!=='open') $blocks[]='Série sintética sem configuração aprovada/vigente e aberta.';
|
||||
@@ -147,11 +152,12 @@ final class ServiceInvoicing
|
||||
}
|
||||
private function snapshot(array $p,int $number,string $instant): array
|
||||
{
|
||||
return ['notice'=>self::NOTICE,'renderer'=>InvoicePdf::VERSION,'draft_id'=>$p['draft_id'],'series_code'=>$p['series_code'],'number'=>$number,'issued_at'=>$instant,'issued_local'=>\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d H:i:s'),'timezone'=>self::ZONE,
|
||||
return ['notice'=>self::NOTICE,'renderer'=>isset($p['calculation_policy'])?MultiLineInvoicePdf::VERSION:InvoicePdf::VERSION,'draft_id'=>$p['draft_id'],'series_code'=>$p['series_code'],'number'=>$number,'issued_at'=>$instant,'issued_local'=>\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d H:i:s'),'timezone'=>self::ZONE,
|
||||
'series_id'=>$p['series_id'],'series_version'=>$p['series_version'],'series_definition'=>$p['series_definition'],'rule_id'=>$p['rule_id'],'rule_version'=>$p['rule_version'],'rule'=>$p['rule'],'document'=>$p['document'],
|
||||
'calculation'=>array_replace($p['calculation_entry']['snapshot']['result'],['temporal_state'=>'issue_simulated','exigibility_date'=>$p['expected_date'],'assessment_period'=>substr($p['expected_date'],0,7),'pending'=>[]]),'prior_calculation_snapshot'=>$p['calculation_entry']['snapshot'],'calculation_id'=>$p['calculation_entry']['id'],'profile_identifier'=>$p['calculation_entry']['snapshot']['input']['profile_identifier'],'profile_version'=>$p['calculation_entry']['snapshot']['input']['profile_version'],'profile'=>$p['profile'],'rate_percent'=>$p['profile']['rate_percent'],
|
||||
'exigibility_date'=>$p['expected_date'],'assessment_period'=>substr($p['expected_date'],0,7),'simulation_only'=>true];
|
||||
}
|
||||
public function multiLineSnapshot(array $payload,int $number,string $instant):array{abort_unless(($payload['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY,422);return ['calculation_policy'=>MultiLineInvoiceCalculation::POLICY]+$this->snapshot($payload,$number,$instant);}
|
||||
public function issue(int $draft,int $actor,array $input,string $key): array
|
||||
{
|
||||
return $this->command($actor,'service_invoices.issue',function($s)use($draft,$actor,$input,$key) {
|
||||
@@ -170,7 +176,7 @@ final class ServiceInvoicing
|
||||
DB::table('si_counters')->where('id',$counter->id)->update(['last_number'=>$number]);
|
||||
$instant=now()->toIso8601String();
|
||||
abort_unless(\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d')===$p['expected_date'],409,'Data mudou durante a confirmação; reveja antes de emitir.');
|
||||
$snapshot=$this->snapshot($p,$number,$instant);
|
||||
$snapshot=isset($p['calculation_policy'])?$this->multiLineSnapshot($p,$number,$instant):$this->snapshot($p,$number,$instant);
|
||||
$snapshot['document_lines']=InvoicePdf::lines($snapshot); // Freeze browser representation as well as PDF bytes.
|
||||
$bytes=$this->pdf->render($snapshot);
|
||||
$id=\App\Shared\Context\ModuleRecords::query('si_invoices')->insertGetId($s+['draft_id'=>$draft,'source_id'=>$p['document']['source_id'],'series_id'=>$p['series_id'],'rule_id'=>$p['rule_id'],'series_code'=>$sd['code'],'number'=>$number,'snapshot'=>json_encode($snapshot,JSON_THROW_ON_ERROR),'pdf_base64'=>base64_encode($bytes),'pdf_sha256'=>hash('sha256',$bytes),'actor_id'=>$actor,'issued_at'=>$instant]);
|
||||
@@ -185,13 +191,13 @@ final class ServiceInvoicing
|
||||
public function show(int $id,int $actor): array
|
||||
{
|
||||
self::guard(); $s=$this->scope($actor,'service_invoices.view');
|
||||
$r=\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->where('id',$id)->first(); abort_unless($r,403,'Documento fora do âmbito.');
|
||||
$r=QualifiedInvoiceSource::query()->where($s)->where('id',$id)->first(); abort_unless($r,403,'Documento fora do âmbito.');
|
||||
$snapshot=json_decode($r->snapshot,true,32,JSON_THROW_ON_ERROR);
|
||||
return ['id'=>$r->id,'snapshot'=>$snapshot,'document_lines'=>$snapshot['document_lines'] ?? InvoicePdf::lines($snapshot),'pdf_sha256'=>$r->pdf_sha256,'corrections'=>DB::table('si_correction_requests')->where('invoice_id',$id)->get(['id','reason','actor_id','created_at'])->all()];
|
||||
}
|
||||
public function pdf(int $id,int $actor): string
|
||||
{
|
||||
$this->show($id,$actor); $r=\App\Shared\Context\ModuleRecords::query('si_invoices')->where('id',$id)->first(['pdf_base64','pdf_sha256']);
|
||||
$this->show($id,$actor); $r=QualifiedInvoiceSource::query()->where('id',$id)->first(['pdf_base64','pdf_sha256']);
|
||||
$bytes=base64_decode($r->pdf_base64,true); abort_unless($bytes!==false && hash_equals($r->pdf_sha256,hash('sha256',$bytes)),409,'Integridade do PDF não confirmada.'); return $bytes;
|
||||
}
|
||||
public function requestCorrection(int $id,int $actor,array $input): array
|
||||
|
||||
Reference in New Issue
Block a user