commit de compras
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace App\Modules\Purchasing;
|
||||
|
||||
use Illuminate\Http\{Request,UploadedFile};
|
||||
use Illuminate\Support\Facades\{Crypt,DB,Gate};
|
||||
use Tempo\Finance\Payables\Payables;
|
||||
use Tempo\Purchasing\Embedded\Models\{GoodsReceipt,PurchaseOrder,ReceiptDocument,AuditLog,Requisition};
|
||||
use Tempo\Purchasing\Embedded\Services\{ReceiptDocuments,RequisitionWorkflow};
|
||||
|
||||
/** Private transport; all attachment decisions and writes belong to the pinned native service. */
|
||||
final class ReceiptDocumentsController
|
||||
{
|
||||
private const FIELDS=['id','goods_receipt_id','document_type','original_name','mime_type','size','sha256','uploaded_by','created_at'];
|
||||
private function guard():void { abort_unless(config('receipt_qa.receipt_documents_enabled')===true,404); }
|
||||
private function hash(array $v):string { return hash('sha256',Payables::canonical($v)); }
|
||||
private function generation():string { return $this->hash([request()->session()->get('company_context')]); }
|
||||
private function source(int $id,array $binding):GoodsReceipt {
|
||||
$receipt=GoodsReceipt::findOrFail($id);
|
||||
$order=PurchaseOrder::where('id',$receipt->purchase_order_id)->where(fn($q)=>$q->whereIn('id',$binding['order_ids'])->orWhereIn('requisition_id',RequisitionAccess::ids($binding)))->first();
|
||||
abort_unless($order,403);Gate::authorize('viewOrder',[GoodsReceipt::class,$order]);Gate::authorize('view',$receipt);return $receipt;
|
||||
}
|
||||
private function meta(UploadedFile $file):array { return ['sha256'=>hash_file('sha256',$file->getPathname()),'size'=>$file->getSize(),'mime'=>$file->getMimeType(),'name'=>$file->getClientOriginalName()]; }
|
||||
private function integrity(ReceiptDocument $doc,string $root):void {
|
||||
$file=app(SupplierDocumentFiles::class)->path($root,$doc->path,(int)$doc->tenant_id,(int)$doc->goods_receipt_id);
|
||||
abort_unless(filesize($file)===(int)$doc->size&&hash_equals($doc->sha256,hash_file('sha256',$file)),409,'O ficheiro difere da evidência registada.');
|
||||
}
|
||||
private function same(GoodsReceipt $receipt,array $cut):?ReceiptDocument { return ReceiptDocument::where('goods_receipt_id',$receipt->id)->where('document_type',$cut['document_type'])->where('sha256',$cut['file']['sha256'])->first(); }
|
||||
public function index(int $id,Request $r,ReceiptRuntime $runtime) {
|
||||
$this->guard();$d=$r->validate(['page'=>'sometimes|integer|min:1|max:10000']);abort_if(array_diff(array_keys($r->query()),array_keys($d)),422);
|
||||
return $runtime->run('purchasing_receipts.view',function($a,$b)use($id,$d){$receipt=$this->source($id,$b);abort_unless(app(ReceiptDocuments::class)->canRead($a,$receipt),403);return response()->json(ReceiptDocument::where('goods_receipt_id',$id)->orderByDesc('id')->paginate(15,self::FIELDS,'page',(int)($d['page']??1)))->header('Cache-Control','private, no-store');});
|
||||
}
|
||||
public function preview(int $id,Request $r,ReceiptRuntime $runtime) {
|
||||
$this->guard();$d=$r->validate(['operation_key'=>'required|uuid','revision'=>'required|string|size:64','reason'=>'required|string|min:10|max:2000','document_type'=>'required|string|max:30','file'=>'required|file|max:2048']);abort_if(array_diff(array_keys($r->all()),array_keys($d)),422);unset($d['file']);$d['operation_key']=strtolower($d['operation_key']);$generation=$this->generation();$meta=$this->meta($r->file('file'));
|
||||
return $runtime->run('purchasing_receipts.confirm',function($a,$b,$f)use($id,$r,$d,$generation,$meta){$receipt=$this->source($id,$b);$service=app(ReceiptDocuments::class);abort_unless($service->canAdd($a,$receipt),403);abort_unless(hash_equals($this->hash([$receipt->getAttributes()]),$d['revision']),409,'A recepção mudou. Confira novamente.');$files=app(SupplierDocumentFiles::class);$stage=$files->root(true,'receipt');$db=DB::connection();$level=$db->transactionLevel();$document=null;$existing=$this->same($receipt,$d+['file'=>$meta]);$db->beginTransaction();
|
||||
try{$document=$files->using($stage,fn()=>$service->add($a,$receipt,$d,$r->file('file')),'receipt_documents');$impact=['document'=>$document->only(self::FIELDS),'reused'=>$existing!==null,'receipt_unchanged'=>true,'stock_unchanged'=>true,'reason'=>$d['reason']];unset($impact['document']['id'],$impact['document']['created_at']);}
|
||||
finally{while($db->transactionLevel()>$level)$db->rollBack();if($document&&!$existing)$files->discard($stage,$document->path,(int)$receipt->tenant_id,$id,'receipt_documents');}
|
||||
// A reused document belongs to permanent storage; never delete or stage it.
|
||||
if($existing)$this->integrity($existing,$files->root(false,'receipt'));
|
||||
$cut=$d+['receipt_id'=>$id,'actor'=>$a->id,'binding'=>$f,'generation'=>$generation,'file'=>$meta,'observed'=>time()];return response()->json(['preview'=>Crypt::encryptString(json_encode($cut,JSON_THROW_ON_ERROR)),'impact'=>$impact]);});
|
||||
}
|
||||
public function confirm(int $id,Request $r,ReceiptRuntime $runtime) {
|
||||
$this->guard();$d=$r->validate(['preview'=>'required|string|max:131072','file'=>'required|file|max:2048']);abort_if(array_diff(array_keys($r->all()),array_keys($d)),422);try{$cut=json_decode(Crypt::decryptString($d['preview']),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}$meta=$this->meta($r->file('file'));$generation=$this->generation();$created=null;
|
||||
try{return $runtime->run('purchasing_receipts.confirm',function($a,$b,$f,$o)use($id,$r,$cut,$meta,$generation,&$created){$receipt=$this->source($id,$b);$service=app(ReceiptDocuments::class);abort_unless($service->canAdd($a,$receipt),403);
|
||||
abort_unless(($cut['receipt_id']??null)===$id&&($cut['actor']??null)===$a->id&&($cut['binding']??null)===$f&&($cut['generation']??null)===$generation&&($cut['file']??null)===$meta&&is_int($cut['observed']??null)&&$cut['observed']<=time()&&$cut['observed']>=time()-600,409,'O ficheiro, contexto ou prévia mudou.');
|
||||
$hash=$this->hash([$id,$cut['file'],$cut['document_type'],$cut['reason']]);$files=app(SupplierDocumentFiles::class);$root=$files->root(false,'receipt');$old=AuditLog::where('action','sgf.receipt.document')->where('after->operation_key',$cut['operation_key'])->first();
|
||||
if($old){abort_unless($old->user_id===$a->id&&($old->after['request_hash']??null)===$hash&&($old->after['binding']??null)===$f,409,'Chave usada com outro conteúdo.');$doc=ReceiptDocument::where('goods_receipt_id',$id)->findOrFail($old->after['result']['document_id']);$this->integrity($doc,$root);return response()->json($old->after['result']+['replayed'=>true]);}
|
||||
abort_unless(hash_equals($this->hash([$receipt->getAttributes()]),$cut['revision']),409,'A recepção mudou desde a conferência.');$existing=$this->same($receipt,$cut);$document=$files->using($root,fn()=>$service->add($a,$receipt,$cut,$r->file('file')),'receipt_documents');if(!$existing)$created=['root'=>$root,'path'=>$document->path,'tenant'=>(int)$receipt->tenant_id,'receipt'=>$id];$this->integrity($document,$root);
|
||||
$result=['receipt_id'=>$id,'document_id'=>$document->id,'document'=>$document->only(self::FIELDS),'reused'=>$existing!==null,'receipt_unchanged'=>true,'stock_unchanged'=>true];$order=PurchaseOrder::findOrFail($receipt->purchase_order_id);app(RequisitionWorkflow::class)->audit($a,'sgf.receipt.document',Requisition::findOrFail($order->requisition_id),null,$o+['operation_key'=>$cut['operation_key'],'request_hash'=>$hash,'binding'=>$f,'reason'=>$cut['reason'],'result'=>$result]);return response()->json($result+['replayed'=>false],201);});}
|
||||
catch(\Throwable $e){if($created){try{$exists=DB::connection('receipt_native')->table('receipt_documents')->where(['tenant_id'=>$created['tenant'],'goods_receipt_id'=>$id,'path'=>$created['path']])->exists();if(!$exists)app(SupplierDocumentFiles::class)->discard($created['root'],$created['path'],$created['tenant'],$id,'receipt_documents');}catch(\Throwable){/* An uncertain commit retains its evidence. */}}throw $e;}
|
||||
}
|
||||
public function download(int $id,int $document,Request $r,ReceiptRuntime $runtime) {
|
||||
$this->guard();abort_if($r->query(),422);return $runtime->run('purchasing_receipts.view',function($a,$b,$f,$o)use($id,$document,$r){$receipt=$this->source($id,$b);$doc=ReceiptDocument::where('goods_receipt_id',$id)->findOrFail($document);$files=app(SupplierDocumentFiles::class);$root=$files->root(false,'receipt');$files->path($root,$doc->path,(int)$doc->tenant_id,$id);$input=Request::create('/embedded/receipt-document','GET');$input->setUserResolver(fn($guard=null)=>$a);
|
||||
$response=$files->using($root,fn()=>(new \Tempo\Purchasing\Embedded\Http\Controllers\ReceiptDocumentController)->download($input,$receipt,$document,app(ReceiptDocuments::class)),'receipt_documents');$order=PurchaseOrder::findOrFail($receipt->purchase_order_id);app(RequisitionWorkflow::class)->audit($a,'sgf.receipt.document_downloaded',Requisition::findOrFail($order->requisition_id),null,$o+['receipt_id'=>$id,'document_id'=>$document,'sha256'=>$doc->sha256]);return $response;});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user