95 lines
5.7 KiB
PHP
95 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\InvestmentZone;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class InvestmentZones
|
|
{
|
|
public function links(bool $territorial = false): \Illuminate\Database\Query\Builder
|
|
{
|
|
return DB::table(($territorial ? 'investment_zone_relations' : 'investment_zone_links').' as zl')->where('zl.validation_status', 'validated')
|
|
->whereIn('zl.zone_id', $this->visible()->select('investment_zones.id'))
|
|
->whereDate('zl.valid_from', '<=', today())->where(fn ($q) => $q->whereNull('zl.valid_until')->orWhereDate('zl.valid_until', '>=', today()))
|
|
->where(function ($q) {
|
|
$q->whereNull('zl.legal_source_id')->orWhereExists(fn ($s) => $s->selectRaw('1')->from('investment_legal_sources as ls')
|
|
->whereColumn('ls.id', 'zl.legal_source_id')->where('ls.validation_status', 'validated')->whereDate('ls.valid_from', '<=', today())
|
|
->where(fn ($s) => $s->whereNull('ls.valid_until')->orWhereDate('ls.valid_until', '>=', today())));
|
|
});
|
|
}
|
|
|
|
/** @return Builder<InvestmentZone> */
|
|
public function visible(): Builder
|
|
{
|
|
return InvestmentZone::query()->where('publication_status', 'published')->where('validation_status', 'validated')
|
|
->whereExists(fn ($q) => $q->selectRaw('1')->from('investment_legal_sources as source')
|
|
->whereColumn('source.id', 'investment_zones.legal_source_id')->where('source.validation_status', 'validated')
|
|
->whereDate('source.valid_from', '<=', today())
|
|
->where(fn ($q) => $q->whereNull('source.valid_until')->orWhereDate('source.valid_until', '>=', today())))
|
|
->whereExists(fn ($q) => $q->selectRaw('1')->from('organizations')->whereColumn('organizations.id', 'investment_zones.operator_id')->whereNull('organizations.deleted_at'));
|
|
}
|
|
|
|
/** @param array<string,mixed> $data */
|
|
public function save(?string $id, array $data, int $actor): InvestmentZone
|
|
{
|
|
return DB::transaction(function () use ($id, $data, $actor) {
|
|
$zone = $id ? InvestmentZone::whereKey($id)->lockForUpdate()->firstOrFail() : new InvestmentZone;
|
|
abort_if($id && (int) $zone->version_no !== (int) $data['version_no'], 409, 'Versão alterada. Recarregue antes de guardar.');
|
|
$version = $id ? (int) $zone->version_no + 1 : 1;
|
|
unset($data['version_no']);
|
|
$zone->fill([...$data, 'editor_id' => $actor, 'reviewer_id' => null, 'published_at' => null,
|
|
'version_no' => $version, 'publication_status' => 'draft', 'validation_status' => 'pending'])->save();
|
|
$this->snapshot($zone, $actor, 'saved');
|
|
|
|
return $zone;
|
|
});
|
|
}
|
|
|
|
public function publish(string $id, int $version, int $actor): InvestmentZone
|
|
{
|
|
return DB::transaction(function () use ($id, $version, $actor) {
|
|
$zone = InvestmentZone::whereKey($id)->lockForUpdate()->firstOrFail();
|
|
abort_if((int) $zone->version_no !== $version, 409, 'Versão alterada. Recarregue antes de aprovar.');
|
|
abort_if((int) $zone->editor_id === $actor, 403, 'A revisão exige outro gestor autorizado.');
|
|
$source = DB::table('investment_legal_sources')->where('id', $zone->legal_source_id)->lockForUpdate()->first();
|
|
abort_unless($source && $source->validation_status === 'validated' && $source->valid_from <= today()->toDateString()
|
|
&& (! $source->valid_until || $source->valid_until >= today()->toDateString()), 422, 'Fonte jurídica válida e revista em falta.');
|
|
abort_unless($zone->category !== 'pending' && $zone->operator_id && trim((string) $zone->name_en) !== ''
|
|
&& trim((string) $zone->description) !== '' && trim((string) $zone->description_en) !== '', 422, 'Categoria, operador e conteúdo PT/EN são obrigatórios.');
|
|
abort_unless(DB::table('organizations')->where('id', $zone->operator_id)->whereNull('deleted_at')->exists(), 422, 'Operador indisponível.');
|
|
$zone->fill(['version_no' => $version + 1, 'reviewer_id' => $actor, 'publication_status' => 'published',
|
|
'validation_status' => 'validated', 'published_at' => now()])->save();
|
|
$this->snapshot($zone, $actor, 'published');
|
|
|
|
return $zone;
|
|
});
|
|
}
|
|
|
|
public function suspend(string $id, int $version, int $actor): InvestmentZone
|
|
{
|
|
return DB::transaction(function () use ($id, $version, $actor) {
|
|
$zone = InvestmentZone::whereKey($id)->lockForUpdate()->firstOrFail();
|
|
abort_if((int) $zone->version_no !== $version, 409, 'Versão alterada.');
|
|
$zone->fill(['version_no' => $version + 1, 'publication_status' => 'suspended'])->save();
|
|
$this->snapshot($zone, $actor, 'suspended');
|
|
|
|
return $zone;
|
|
});
|
|
}
|
|
|
|
private function snapshot(InvestmentZone $zone, int $actor, string $action): void
|
|
{
|
|
$json = $zone->toJson(JSON_THROW_ON_ERROR);
|
|
DB::table('investment_zone_revisions')->insert(['id' => (string) Str::uuid(), 'zone_id' => $zone->id,
|
|
'version_no' => $zone->version_no, 'actor_id' => $actor, 'action' => $action,
|
|
'snapshot' => Crypt::encryptString($json), 'sha256' => hash('sha256', $json)]);
|
|
DB::table('audit_events')->insert(['source_system' => 'investment-zones', 'entity_type' => 'investment_zones',
|
|
'entity_id' => $zone->id, 'actor_user_id' => $actor, 'action' => $action,
|
|
'after_data' => json_encode(['version_no' => $zone->version_no, 'sha256' => hash('sha256', $json)], JSON_THROW_ON_ERROR)]);
|
|
}
|
|
}
|