84 lines
4.5 KiB
PHP
84 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Opportunity;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\Rule;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
/** Optional commercial fields confirmed by Tempo for phase four; no title certification. */
|
|
final class PrivateOfferDetails
|
|
{
|
|
/** @param array<string,mixed> $input
|
|
* @return array<string,mixed> */
|
|
public function validate(array $input): array
|
|
{
|
|
$bytes = file_get_contents(resource_path('data/investmoz-opportunity-modular.schema.json'));
|
|
if ($bytes === false) {
|
|
throw new \RuntimeException('Opportunity schema unavailable.');
|
|
}
|
|
$schema = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
|
|
$property = $schema['$defs']['realEstatePatch']['properties'];
|
|
|
|
return Validator::make($input, [
|
|
'business' => 'sometimes|nullable|array:sector_id,secondary_sector_id,revenue,cash_flow,currency,reason,availability',
|
|
'business.sector_id' => ['nullable', 'integer', Rule::exists('sectors', 'id')->where('is_active', true)],
|
|
'business.secondary_sector_id' => ['nullable', 'integer', Rule::exists('sectors', 'id')->where('is_active', true)],
|
|
'business.revenue' => ['nullable', 'string', 'regex:/^\d{1,16}(\.\d{1,2})?$/'],
|
|
'business.cash_flow' => ['nullable', 'string', 'regex:/^-?\d{1,16}(\.\d{1,2})?$/'],
|
|
'business.currency' => 'nullable|required_with:business.revenue,business.cash_flow|in:MZN,USD',
|
|
'business.reason' => 'nullable|string|max:1000', 'business.availability' => 'nullable|in:available,sold',
|
|
'property' => 'sometimes|nullable|array:transaction_type,property_type,land_area,built_area,area_unit,bedrooms,bathrooms,amenities,availability',
|
|
'property.transaction_type' => 'nullable|in:sale,lease',
|
|
'property.property_type' => ['nullable', Rule::in($property['property_type']['enum'])],
|
|
'property.land_area' => 'nullable|numeric|gt:0', 'property.built_area' => 'nullable|numeric|gt:0',
|
|
'property.area_unit' => ['nullable', Rule::in(OpportunityResources::AREA_UNITS)],
|
|
'property.bedrooms' => 'nullable|integer|min:0|max:10000', 'property.bathrooms' => 'nullable|integer|min:0|max:10000',
|
|
'property.amenities' => 'nullable|array|max:100', 'property.amenities.*' => 'string|max:250',
|
|
'property.availability' => 'nullable|in:available,sold,leased',
|
|
])->validate();
|
|
}
|
|
|
|
/** @param array<string,mixed> $offer */
|
|
public function assertCategory(array $offer, string $category): void
|
|
{
|
|
Validator::make($offer, [
|
|
'business' => $category === 'business_sale' ? 'nullable|array' : 'prohibited',
|
|
'property' => $category === 'real_estate' ? 'nullable|array' : 'prohibited',
|
|
])->validate();
|
|
}
|
|
|
|
/** Snapshot all active linked originals and metadata in the existing editorial version.
|
|
* @return array<string,array<string,mixed>> */
|
|
public function resources(Opportunity $record): array
|
|
{
|
|
$result = ['documents' => [], 'images' => [], 'locations' => []];
|
|
foreach (array_keys($result) as $kind) {
|
|
foreach (app(OpportunityResources::class)->listing($record->id, $kind) as $row) {
|
|
if ($row->deleted_at !== null) {
|
|
continue;
|
|
}
|
|
if ($kind !== 'locations') {
|
|
$file = $row->file;
|
|
$doc = DB::table('documents')->where('id', $row->document_id)->first();
|
|
if (! $doc || $doc->deleted_at !== null || $doc->status !== 'active' || $doc->malware_scan_status !== 'clean'
|
|
|| ! hash_equals(trim($doc->sha256), trim((string) $doc->malware_scanned_sha256))) {
|
|
throw ValidationException::withMessages(['attachments' => 'Aguarde uma análise de segurança válida dos anexos.']);
|
|
}
|
|
$result[$kind][$row->id] = ['version' => $row->version, 'document_id' => $row->document_id, 'payload' => $row->payload, 'file' => $file];
|
|
} else {
|
|
$result[$kind][$row->id] = ['version' => $row->version, 'payload' => $row->payload];
|
|
}
|
|
}
|
|
ksort($result[$kind]);
|
|
}
|
|
if (count($result['images']) > 6) {
|
|
throw ValidationException::withMessages(['images' => 'A oferta admite no máximo seis fotografias.']);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|