commit21
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\GoodsReceipt;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\ReceiptDocument;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
class ReceiptDocuments
|
||||
{
|
||||
public function ready(): bool
|
||||
{
|
||||
return Schema::hasTable('receipt_documents');
|
||||
}
|
||||
|
||||
public function canAdd(User $actor, GoodsReceipt $receipt): bool
|
||||
{
|
||||
$order = PurchaseOrder::query()->find($receipt->purchase_order_id);
|
||||
|
||||
return $order !== null && ! app(OrganizationAccess::class)->maintenanceOnly($actor)
|
||||
&& Gate::forUser($actor)->allows('view', $receipt) && Gate::forUser($actor)->allows('create', [GoodsReceipt::class, $order]);
|
||||
}
|
||||
|
||||
public function canRead(User $actor, GoodsReceipt $receipt): bool
|
||||
{
|
||||
return ! app(OrganizationAccess::class)->maintenanceOnly($actor) && Gate::forUser($actor)->allows('view', $receipt);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $input */
|
||||
public function add(User $actor, GoodsReceipt $receipt, array $input, UploadedFile $file): ReceiptDocument
|
||||
{
|
||||
abort_unless($this->ready(), 503, 'Anexos indisponíveis nesta instalação.');
|
||||
$data = Validator::make([...$input, 'file' => $file], [
|
||||
'operation_key' => 'required|uuid', 'document_type' => 'required|in:delivery_note,quality,other',
|
||||
'file' => 'required|file|mimes:pdf,jpg,jpeg,png|extensions:pdf,jpg,jpeg,png|max:2048',
|
||||
])->validate();
|
||||
$data['operation_key'] = Str::lower($data['operation_key']);
|
||||
$sha = hash_file('sha256', $file->getPathname());
|
||||
abort_unless(is_string($sha) && $file->getSize() > 0, 422);
|
||||
$name = Str::substr(Str::replaceMatches('/[^\pL\pN ._()-]/u', '_', basename(str_replace('\\', '/', $file->getClientOriginalName()))), 0, 180);
|
||||
$hash = hash('sha256', json_encode([$receipt->id, $data['document_type'], $sha, $name], JSON_THROW_ON_ERROR));
|
||||
$newPath = null;
|
||||
try {
|
||||
return DB::transaction(function () use ($actor, $receipt, $data, $file, $sha, $hash, $name, &$newPath) {
|
||||
app(ConflictGuard::class)->lockCompany($actor);
|
||||
$actor = app(CurrentActor::class)->lock($actor);
|
||||
$receipt = GoodsReceipt::query()->lockForUpdate()->findOrFail($receipt->id);
|
||||
abort_unless($this->canAdd($actor, $receipt), 403);
|
||||
$old = AuditLog::query()->whereIn('action', ['receipt.document_added', 'receipt.document_reused'])->where('after->operation_key', $data['operation_key'])->first();
|
||||
if ($old !== null) {
|
||||
if ($old->user_id !== $actor->id || ($old->after['input_hash'] ?? null) !== $hash) {
|
||||
throw ValidationException::withMessages(['operation_key' => 'Chave utilizada com outro documento.']);
|
||||
}
|
||||
|
||||
$documentId = $old->after['document_id'] ?? null;
|
||||
abort_unless(is_int($documentId), 409, 'Referência documental inválida.');
|
||||
|
||||
return ReceiptDocument::query()->where('goods_receipt_id', $receipt->id)->findOrFail($documentId);
|
||||
}
|
||||
$document = ReceiptDocument::query()->where('goods_receipt_id', $receipt->id)->where('document_type', $data['document_type'])->where('sha256', $sha)->first();
|
||||
$reused = $document !== null;
|
||||
if ($document === null) {
|
||||
$newPath = $file->store($actor->tenant_id.'/'.$receipt->id, 'receipt_documents');
|
||||
abort_unless(is_string($newPath), 500);
|
||||
$document = ReceiptDocument::query()->create(['tenant_id' => $actor->tenant_id, 'goods_receipt_id' => $receipt->id,
|
||||
'uploaded_by' => $actor->id, 'document_type' => $data['document_type'], 'original_name' => $name,
|
||||
'path' => $newPath, 'mime_type' => $file->getMimeType(), 'size' => $file->getSize(), 'sha256' => $sha, 'created_at' => now()]);
|
||||
}
|
||||
$order = PurchaseOrder::query()->findOrFail($receipt->purchase_order_id);
|
||||
app(RequisitionWorkflow::class)->audit($actor, $reused ? 'receipt.document_reused' : 'receipt.document_added', Requisition::query()->findOrFail($order->requisition_id), null,
|
||||
['receipt_id' => $receipt->id, 'document_id' => $document->id, 'operation_key' => $data['operation_key'], 'input_hash' => $hash]);
|
||||
|
||||
return $document;
|
||||
});
|
||||
} catch (Throwable $error) {
|
||||
if (is_string($newPath)) {
|
||||
// Never delete a file whose metadata may already have committed.
|
||||
try {
|
||||
if (! DB::table('receipt_documents')->where('path', $newPath)->exists()) {
|
||||
Storage::disk('receipt_documents')->delete($newPath);
|
||||
}
|
||||
} catch (Throwable $cleanupError) {
|
||||
report($cleanupError);
|
||||
}
|
||||
}
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user