validatedCatalog($catalog); if ($catalog === 'countries') { return $this->countries($request); } $rules = [ 'q' => ['nullable', 'string', 'max:100'], 'version' => ['nullable', 'integer', 'min:1'], 'page' => ['nullable', 'integer', 'min:1'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], 'parent_id' => ['nullable', 'uuid'], 'jurisdiction_id' => ['nullable', 'uuid'], ]; if ($catalog === 'jurisdictions') { $rules['type'] = ['required', 'in:country,province,district,admin_post,locality']; $rules['parent_id'][] = 'prohibited_if:type,country'; $rules['parent_id'][] = 'required_if:type,district,admin_post,locality'; } if ($catalog === 'economic-activities') { $rules['sector_id'] = ['nullable', 'integer', 'exists:sectors,id']; $rules['origin'] = ['nullable', 'in:portal,cae']; } $data = $request->validate($rules); $cae = $catalog === 'economic-activities' && ($data['origin'] ?? 'portal') === 'cae'; $versionQuery = DB::table('reference_catalog_versions')->where('catalog_code', $cae ? 'economic-activities-cae' : $catalog)->where('locale', 'pt-MZ'); if ($cae && ! app(\App\Services\EconomicReferenceCatalog::class)->available()) { $versionQuery->whereRaw('false'); } if (isset($data['version'])) { $versionQuery->where('version_no', $data['version'])->whereIn('status', ['published', 'retired']); } else { $versionQuery->where('status', 'published') ->where(fn (Builder $query) => $query->whereNull('valid_from')->orWhere('valid_from', '<=', today()->toDateString())) ->where(fn (Builder $query) => $query->whereNull('valid_to')->orWhere('valid_to', '>', today()->toDateString())); } $version = $versionQuery->orderByDesc('version_no')->first(); if (isset($data['version']) && ! $version) { abort(404, 'Versão publicada do catálogo não encontrada.'); } if (! $version) { return response()->json([ 'data' => [], 'links' => ['next' => null, 'previous' => null], 'next_page_url' => null, 'prev_page_url' => null, 'current_page' => 1, 'per_page' => $data['per_page'] ?? 20, 'total' => 0, 'last_page' => 1, 'meta' => ['catalog' => $catalog, 'version' => null, 'locale' => 'pt-MZ', 'status' => 'not_published', 'current_page' => 1, 'per_page' => $data['per_page'] ?? 20, 'total' => 0, 'last_page' => 1], ])->header('Cache-Control', 'public, max-age=60'); } $query = match ($catalog) { 'authorities' => $this->authorities((string) $version->id, $data), 'jurisdictions' => $this->jurisdictions($data), 'economic-activities' => $this->economicActivities((string) $version->id, $data), }; $page = $query->orderBy('code')->orderBy('id')->paginate($data['per_page'] ?? 20)->withQueryString(); $items = collect($page->items())->map(fn (object $row): array => $this->present($catalog, (array) $row))->values(); if ($cae) { $paths = app(\App\Services\EconomicReferenceCatalog::class)->activities()->keyBy('id'); $items = $items->map(fn ($row) => [...$row, 'level' => $paths[$row['id']]->level, 'path' => $paths[$row['id']]->path, 'source_version' => $paths[$row['id']]->source_version, 'edition' => 'not_determined', 'divergence' => '600/579 documentalmente aberta']); } return response()->json([ 'data' => $items, 'links' => ['next' => $page->nextPageUrl(), 'previous' => $page->previousPageUrl()], 'next_page_url' => $page->nextPageUrl(), 'prev_page_url' => $page->previousPageUrl(), 'current_page' => $page->currentPage(), 'per_page' => $page->perPage(), 'total' => $page->total(), 'last_page' => $page->lastPage(), 'meta' => ['catalog' => $catalog, 'version' => (int) $version->version_no, 'locale' => $version->locale, 'source_version' => $version->source_version, 'valid_from' => $version->valid_from, 'valid_to' => $version->valid_to, 'current_page' => $page->currentPage(), 'per_page' => $page->perPage(), 'total' => $page->total(), 'last_page' => $page->lastPage()], ])->header('Cache-Control', 'public, max-age=300') ->header('ETag', 'W/"catalog-'.substr(hash('sha256', $catalog.'|'.$version->version_no.'|'.json_encode($data, JSON_THROW_ON_ERROR)), 0, 32).'"'); } private function countries(Request $request): JsonResponse { $data = $request->validate(['q' => 'nullable|string|max:100', 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:100', 'parent_id' => 'prohibited', 'version' => 'prohibited']); $term = strtolower(Str::ascii(trim($data['q'] ?? ''))); $items = collect(app(CountryCatalogue::class)->all()) ->filter(fn (array $country) => $term === '' || str_contains(strtolower(Str::ascii(implode(' ', $country))), $term)) ->values()->map(fn (array $country) => ['id' => $country['code'], 'value' => $country['code'], 'code' => $country['code'], 'name' => $country['name'], 'label' => $country['name']]); $page = (int) ($data['page'] ?? 1); $perPage = (int) ($data['per_page'] ?? 50); $result = new LengthAwarePaginator($items->forPage($page, $perPage)->values(), $items->count(), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()]); return response()->json([...$result->toArray(), 'meta' => ['catalog' => 'countries', 'source' => 'Local ISO 3166 catalogue', 'sha256' => hash_file('sha256', resource_path('data/iso3166.tab'))]]) ->header('Cache-Control', 'public, max-age=300'); } /** @param array $data */ private function authorities(string $versionId, array $data): Builder { $query = DB::table('authorities as a')->join('organizations as o', 'o.id', '=', 'a.organization_id') ->leftJoin('geo_locations as g', 'g.id', '=', 'a.jurisdiction_id') ->where('a.catalog_version_id', $versionId)->where('a.status', 'published') ->where('o.data_classification', 'public')->whereNull('o.deleted_at') ->where('a.valid_from', '<=', today()->toDateString()) ->where(fn (Builder $builder) => $builder->whereNull('a.valid_to')->orWhere('a.valid_to', '>', today()->toDateString())) ->select(['a.id', 'a.code', 'a.authority_type', 'a.label_pt_mz as name', 'a.organization_id', 'a.jurisdiction_id', 'g.name as jurisdiction_name', 'a.version_no']); if (! empty($data['jurisdiction_id'])) { $query->where('a.jurisdiction_id', $data['jurisdiction_id']); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['a.code', 'a.label_pt_mz', 'o.name']); } return $query; } /** @param array $data */ private function jurisdictions(array $data): Builder { $query = DB::table('geo_locations')->where('source_system', 'cartografia-workbook') ->where('data_classification', 'public')->whereNull('deleted_at')->where('location_type', $data['type']) ->select(['id', 'code', 'name', 'parent_id', 'location_type', 'source_code', 'version_no']); $parentType = ['province' => 'country', 'district' => 'province', 'admin_post' => 'district', 'locality' => 'admin_post'][$data['type']] ?? null; if ($data['type'] !== 'country' && ! empty($data['parent_id'])) { $validParent = DB::table('geo_locations')->where('id', $data['parent_id'])->where('location_type', $parentType) ->where('source_system', 'cartografia-workbook')->where('data_classification', 'public')->whereNull('deleted_at')->exists(); if (! $validParent) { throw ValidationException::withMessages(['parent_id' => 'Seleccione um território pai activo do nível anterior.']); } $query->where('parent_id', $data['parent_id']); } elseif (in_array($data['type'], ['district', 'admin_post', 'locality'], true)) { throw ValidationException::withMessages(['parent_id' => 'Seleccione primeiro o território pai.']); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['code', 'name', 'source_code']); } return $query; } /** @param array $data */ private function economicActivities(string $versionId, array $data): Builder { $query = DB::table('economic_activities')->where('catalog_version_id', $versionId)->where('status', 'published') ->where('valid_from', '<=', today()->toDateString()) ->where(fn (Builder $builder) => $builder->whereNull('valid_to')->orWhere('valid_to', '>', today()->toDateString())) ->select(['id', 'code', 'label_pt_mz as name', 'parent_id', 'sector_id', 'version_no']); if (empty($data['version'])) $query->where('id', '!=', \App\Services\SectorOrganization::LEGACY_ACTIVITY_ID); if (array_key_exists('parent_id', $data) && $data['parent_id'] !== null) { $query->where('parent_id', $data['parent_id']); } else { $query->whereNull('parent_id'); } if (! empty($data['sector_id'])) { $sector = \App\Models\Sector::find($data['sector_id']); $query->whereIn('sector_id', $sector ? \App\Models\Sector::whereIn('slug', \App\Services\SectorOrganization::slugs($sector->slug))->pluck('id') : []); } if (! empty($data['q'])) { $this->search($query, $data['q'], ['code', 'label_pt_mz']); } return $query; } /** @param list $columns */ private function search(Builder $query, string $search, array $columns): void { $term = '%'.addcslashes($search, '%_\\').'%'; $query->where(function (Builder $nested) use ($columns, $term): void { foreach ($columns as $index => $column) { if ($index === 0) { $nested->whereRaw($column.' ILIKE ?', [$term]); } else { $nested->orWhereRaw($column.' ILIKE ?', [$term]); } } }); } /** @return 'authorities'|'jurisdictions'|'economic-activities'|'countries' */ private function validatedCatalog(string $catalog): string { abort_unless(in_array($catalog, self::CATALOGS, true), 404); return $catalog; } /** * @param 'authorities'|'jurisdictions'|'economic-activities' $catalog * @param array $row * @return array */ private function present(string $catalog, array $row): array { return match ($catalog) { 'jurisdictions' => ['id' => $row['id'], 'value' => $row['id'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'type' => $row['location_type'], 'source_code' => $row['source_code'], 'version_no' => $row['version_no']], 'economic-activities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'parent_id' => $row['parent_id'], 'sector_id' => $row['sector_id'], 'version_no' => $row['version_no']], 'authorities' => ['id' => $row['id'], 'value' => $row['code'], 'code' => $row['code'], 'name' => $row['name'], 'label' => $row['name'], 'locale' => 'pt-MZ', 'authority_type' => $row['authority_type'], 'organization_id' => $row['organization_id'], 'jurisdiction_id' => $row['jurisdiction_id'], 'jurisdiction_name' => $row['jurisdiction_name'], 'version_no' => $row['version_no']], }; } }