29 lines
6.4 KiB
PHP
29 lines
6.4 KiB
PHP
<?php
|
|
namespace App\Modules\Accounting;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\{Crypt,DB,Validator};
|
|
use Tempo\Finance\Payables\Payables;
|
|
/** Preview via rollback, confirm through the original writer, receipt through its original store. */
|
|
final class FixedAssetJournalController
|
|
{
|
|
private function hash(array $a):string{return hash('sha256',Payables::canonical($a));}
|
|
private function validate(array $v):array{abort_if(array_diff(array_keys($v),['action','data']),422);return Validator::make($v,['action'=>'required|in:effect-prepare,validate,post,correct-prepare,correct-validate,correct-post','data'=>'required|array'])->validate();}
|
|
private function run(Request $r,string $right,\Closure $fn){$actor=$r->user();app(FixedAssets::class)->scope($actor->id);try{return app(ComposedLedger::class)->run($right,fn($e,$a,$b,$o,$s,$stock,$hostRead)=>$fn($e,DB::connection(),$s,$hostRead));}catch(\DomainException $e){abort(409,$e->getMessage());}catch(\InvalidArgumentException $e){abort(422,$e->getMessage());}}
|
|
private function right(string $action):string{return $action==='effect-prepare'?'prepare':str_replace('-','_',$action);}
|
|
private function qualified(object $db,array $s,array $d):void{
|
|
$v=$d['data'];if($d['action']==='effect-prepare'){abort_unless(in_array($v['effect_type']??null,['assets.admitted','assets.depreciation','assets.writeoff'],true),422);return;}
|
|
if(in_array($d['action'],['validate','post'],true)){$p=$db->table('gl_proposals')->where($s)->where('id',$v['proposal_id']??0)->first();abort_unless($p&&str_starts_with(json_decode($p->snapshot,true)['source']['effect_type'],'assets.'),403);return;}
|
|
if($d['action']==='correct-prepare')$id=$v['journal_id']??0;else{$g=$db->table('gl_corrections')->where($s)->where('id',$v['correction_id']??0)->first();abort_unless($g,403);$id=$g->original_journal_id;}$j=$db->table('gl_journals')->where($s)->where('id',$id)->first();abort_unless($j&&json_decode($j->snapshot,true)['source']['effect_type']==='assets.depreciation',403);
|
|
}
|
|
private function impact(object $db,array $s,array $result):array{
|
|
$ids=[];if(isset($result['proposal_id']))$ids[]=$result['proposal_id'];if(isset($result['correction_id'])){$g=$db->table('gl_corrections')->where($s)->where('id',$result['correction_id'])->first();$ids=[$g->reverse_proposal_id,$g->replacement_proposal_id];}
|
|
$snap=[];foreach($ids as$id)$snap[]=json_decode($db->table('gl_proposals')->where($s)->where('id',$id)->value('snapshot'),true);return ['state'=>$result['state'],'snapshots'=>$snap,'journal_count'=>isset($result['journal_id'])?1:(isset($result['reverse_journal_id'])?2:0),'payment_created'=>false];
|
|
}
|
|
private function simulate($e,object $db,array $s,array $d,string $key):array{
|
|
$this->qualified($db,$s,$d);$db->beginTransaction();try{$result=$e->command($d['action'],$d['data'],$key);return$this->impact($db,$s,$result);}finally{$db->rollBack();}
|
|
}
|
|
public function preview(Request $r){abort_if(strlen(json_encode($r->all()))>16384,422);$d=$this->validate($r->all());$key=strtolower((string)$r->header('Idempotency-Key'));abort_unless(preg_match('/^[a-f0-9-]{36}$/D',$key),422);return$this->run($r,$this->right($d['action']),function($e,$db,$s)use($r,$d,$key){$impact=$this->simulate($e,$db,$s,$d,$key);$c=['actor'=>$r->user()->id,'scope'=>$s,'generation'=>$r->session()->get('company_context.token'),'observed'=>time(),'input'=>$d,'key'=>$key,'impact'=>$impact];return response()->json(['preview'=>Crypt::encryptString(Payables::canonical($c)),'impact'=>$impact])->header('Cache-Control','no-store');});}
|
|
public function confirm(Request $r){abort_if(array_diff(array_keys($r->all()),['preview']),422);$r->validate(['preview'=>'required|string|max:131072']);try{$c=json_decode(Crypt::decryptString($r->input('preview')),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}$d=$this->validate($c['input']??[]);return$this->run($r,$this->right($d['action']),function($e,$db,$s)use($r,$c,$d){abort_unless($c['actor']===$r->user()->id&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===$r->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$this->qualified($db,$s,$d);$old=$e->receipt($c['key']);if($old){abort_unless(hash_equals($old['request_hash'],$this->hash([$d['action'],$d['data']])),409);return response()->json($old['result']+['replayed'=>true])->header('Cache-Control','no-store');}$impact=$this->simulate($e,$db,$s,$d,$c['key']);abort_unless($this->hash($impact)===$this->hash($c['impact']),409,'Impacto ou origem mudou; confira novamente.');$out=$e->command($d['action'],$d['data'],$c['key']);return response()->json($out)->header('Cache-Control','no-store');});}
|
|
public function index(Request $r){$v=$r->validate(['asset_key'=>'required|uuid','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);abort_if(array_diff(array_keys($r->query()),array_keys($v)),422);$hostActor=$r->user();return$this->run($r,'view',function($e,$db,$s,$hostRead)use($r,$v,$hostActor){$key=$v['asset_key'];$hostRead(fn()=>app(FixedAssets::class)->approved($s,$key,'asset'));$p=$db->table('gl_proposals')->where($s)->where('snapshot->source->asset_key',$key)->orderByDesc('id')->paginate($v['per_page']??25,['*'],'page',$v['page']??1);$ids=$p->pluck('id');$reviews=$db->table('gl_reviews')->where($s)->whereIn('proposal_id',$ids)->get()->keyBy('proposal_id');$journals=$db->table('gl_journals')->where($s)->whereIn('proposal_id',$ids)->get()->keyBy('proposal_id');$items=[];foreach($p as$row)$items[]=['id'=>$row->id,'revision'=>$row->revision,'prepared_by'=>$row->prepared_by,'snapshot'=>json_decode($row->snapshot,true),'review'=>$reviews->get($row->id),'journal'=>$journals->get($row->id)];$p->setCollection(collect($items));$groups=$db->table('gl_corrections')->where($s)->where(fn($q)=>$q->whereIn('reverse_proposal_id',$ids)->orWhereIn('replacement_proposal_id',$ids))->orderByDesc('id')->get()->map(function($g)use($db,$s){$g->applied=$db->table('gl_correction_results')->where($s)->where('correction_id',$g->id)->exists();$g->approved=$db->table('gl_reviews')->where($s)->whereIn('proposal_id',[$g->reverse_proposal_id,$g->replacement_proposal_id])->where('decision','approved')->count()===2;return$g;});$balance=$hostRead(fn()=>app(FixedAssets::class)->balance($s,$key,$db));return response()->json(['records'=>$p->toArray(),'balance'=>$balance,'corrections'=>$groups])->header('Cache-Control','no-store');});}
|
|
}
|