Files
Investidor-V2/app/Services/ExistingBusinessObjects.php
T
nala 3dc7c5c649
tests / ci (push) Canceled after 0s
commit 30/9
2026-09-30 09:05:43 +02:00

79 lines
4.4 KiB
PHP

<?php
namespace App\Services;
use App\Models\ExistingBusinessObject;
use App\Models\User;
use App\Services\Mne\BusinessRepository;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
final class ExistingBusinessObjects
{
public const KINDS = ['company', 'participation', 'asset', 'property', 'unit', 'improvement', 'agricultural_investment', 'land_right', 'contract', 'license', 'authorization', 'encumbrance', 'valuation', 'capital_request'];
/** @return array<string,array<int,string>> */
public static function fields(): array
{
return [
'label' => ['required', 'string', 'max:200'],
'declared_holder' => ['nullable', 'string', 'max:500'],
'document_reference' => ['nullable', 'string', 'max:1000'],
'source_reference' => ['required', 'string', 'min:10', 'max:2000'],
'competent_entity' => ['nullable', 'string', 'max:500'],
'purpose' => ['nullable', 'string', 'max:2000'],
'location_description' => ['nullable', 'string', 'max:2000'],
'boundaries_description' => ['nullable', 'string', 'max:2000'],
'occupancy' => ['nullable', 'string', 'max:2000'],
'condition' => ['nullable', 'string', 'max:2000'],
'encumbrances' => ['nullable', 'string', 'max:4000'],
'restrictions' => ['nullable', 'string', 'max:4000'],
'required_authorizations' => ['nullable', 'string', 'max:4000'],
'transfer_condition' => ['nullable', 'string', 'max:4000'],
'contract_type' => ['nullable', 'in:purchase,sale,lease,operation,management,other'],
'valid_from' => ['nullable', 'date_format:Y-m-d'],
'valid_until' => ['nullable', 'date_format:Y-m-d'],
'area' => ['nullable', 'string', 'regex:/^\d{1,12}(\.\d{1,4})?$/', 'numeric', 'gt:0'],
'area_unit' => ['nullable', 'required_with:area', 'in:m2,ha,km2'],
'amount' => ['nullable', 'string', 'regex:/^\d{1,15}(\.\d{1,2})?$/', 'numeric', 'min:0'],
'currency' => ['nullable', 'required_with:amount', 'regex:/^[A-Z]{3}$/'],
'value_kind' => ['nullable', 'required_with:amount', 'in:asking,indicative,appraised,proposed,negotiated,confirmed'],
'participation_percent' => ['nullable', 'numeric', 'between:0,100'],
];
}
/** @param array<string,mixed> $input */
public function save(User $actor, string $businessId, array $input, ?string $objectId = null): ExistingBusinessObject
{
$rules = self::fields();
if (! empty($input['payload']['valid_from'])) {
$rules['valid_until'][] = 'after_or_equal:valid_from';
}
$data = Validator::make($input, ['version_no' => ['required', 'integer', 'min:1'],
'kind' => ['required', 'in:'.implode(',', self::KINDS)], 'reason' => ['required', 'string', 'min:10', 'max:1000'],
'payload' => ['required', 'array:'.implode(',', array_keys($rules))],
])->validate();
// Cross-field rules operate on the payload itself, rather than sibling root fields.
Validator::make($data['payload'], $rules)->validate();
return DB::transaction(function () use ($actor, $businessId, $objectId, $data) {
$business = app(BusinessRepository::class)->find($businessId, true);
Gate::forUser($actor)->authorize('update', $business);
abort_unless($business->version_no === $data['version_no'], 409, 'Versão desactualizada.');
abort_if(! $objectId && ExistingBusinessObject::where('business_id', $businessId)->count() >= 100, 422, 'Máximo de 100 elementos por negócio.');
$object = $objectId ? ExistingBusinessObject::where('business_id', $businessId)->whereKey($objectId)->firstOrFail() : new ExistingBusinessObject;
if ($objectId) {
abort_unless($object->kind === $data['kind'], 422, 'Crie outro elemento para uma natureza diferente.');
}
$object->fill(['business_id' => $businessId, 'kind' => $data['kind'], 'classification' => 'restricted', 'verification_status' => 'pending', 'payload' => $data['payload']]);
$object->save();
$business->version_no++;
$business->save();
app(ExistingBusinessDrafts::class)->snapshot($business, $actor, $objectId ? 'object_updated' : 'object_added', $data['reason']);
return $object;
});
}
}