varias alteracoes
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-06 14:50:12 +02:00
parent 4f35313d06
commit e65cb26901
333 changed files with 54656 additions and 2058 deletions
@@ -133,21 +133,41 @@ class PortalManagementController extends Controller
public function withdrawProponentSharingConsent(Request $request, InterestRequest $interest): RedirectResponse|JsonResponse
{
$data = $request->validate(['reason' => ['required', 'string', 'min:10', 'max:2000']]);
DB::transaction(function () use ($request, $interest, $data) {
$consent = DB::table('consents')->where('interest_request_id', $interest->id)
->where('purpose', 'share_contact_with_proponent')->where('status', 'granted')->lockForUpdate()->first();
$this->withdrawConsent($request, $interest, $data['reason'], 'share_contact_with_proponent');
return $request->expectsJson() ? response()->json(['reference' => $interest->reference, 'sharing_consent' => 'withdrawn'])
: back()->with('success', 'Consentimento de partilha revogado.');
}
public function withdrawContactConsent(Request $request, InterestRequest $interest): RedirectResponse|JsonResponse
{
$data = $request->validate(['reason' => ['required', 'string', 'min:10', 'max:2000']]);
$this->withdrawConsent($request, $interest, $data['reason'], 'respond_to_interest');
return $request->expectsJson() ? response()->json(['reference' => $interest->reference, 'contact_consent' => 'withdrawn'])
: back()->with('success', 'Consentimento de contacto revogado.');
}
private function withdrawConsent(Request $request, InterestRequest $interest, string $reason, string $purpose): void
{
DB::transaction(function () use ($request, $interest, $reason, $purpose) {
$lockedInterest = InterestRequest::query()->lockForUpdate()->findOrFail($interest->id);
$consent = DB::table('consents')->where('interest_request_id', $lockedInterest->id)
->where('purpose', $purpose)->where('status', 'granted')->whereNull('deleted_at')
->lockForUpdate()->first();
if (! $consent) {
return;
}
DB::table('consents')->where('id', $consent->id)->update(['status' => 'withdrawn', 'withdrawn_at' => now(), 'updated_at' => now()]);
$correlationId = (string) Str::uuid();
DB::table('business_workflow_transitions')->insert([
'id' => (string) Str::uuid(), 'interest_request_id' => $interest->id,
'from_status' => $interest->status, 'to_status' => $interest->status,
'actor_id' => $request->user()->id, 'reason' => Crypt::encryptString($data['reason']),
'id' => (string) Str::uuid(), 'interest_request_id' => $lockedInterest->id,
'from_status' => $lockedInterest->status, 'to_status' => $lockedInterest->status,
'actor_id' => $request->user()->id, 'reason' => Crypt::encryptString($reason),
'correlation_id' => $correlationId,
'metadata' => json_encode(['source' => 'management-interest', 'action' => 'consent_withdrawn',
'purpose' => 'share_contact_with_proponent'], JSON_THROW_ON_ERROR),
'purpose' => $purpose], JSON_THROW_ON_ERROR),
'created_at' => now(),
]);
DB::table('outbox_events')->insertOrIgnore([
@@ -156,14 +176,12 @@ class PortalManagementController extends Controller
'event_type' => 'InterestConsentWithdrawn', 'data_classification' => 'confidential',
'correlation_id' => $correlationId, 'status' => 'pending',
'payload' => json_encode(['schema_version' => 1, 'consent_id' => $consent->id,
'interest_id' => $interest->integration_id, 'purpose' => 'share_contact_with_proponent'], JSON_THROW_ON_ERROR),
'interest_id' => $lockedInterest->integration_id, 'purpose' => $purpose], JSON_THROW_ON_ERROR),
]);
});
return $request->expectsJson() ? response()->json(['reference' => $interest->reference, 'sharing_consent' => 'withdrawn'])
: back()->with('success', 'Consentimento de partilha revogado.');
}
/** @return Builder<User> */
private function interestAssignees(): Builder
{
$emails = array_unique(array_merge(config('portal.managers', []), ...array_map(
@@ -181,9 +199,16 @@ class PortalManagementController extends Controller
'q' => ['nullable', 'string', 'max:150'],
'status' => ['nullable', Rule::in($resource === 'sectors' ? ['active', 'inactive'] : ['draft', 'published', 'archived'])],
'sector_id' => ['nullable', 'integer', Rule::exists('sectors', 'id')->whereNull('deleted_at')],
'geography_scope' => ['nullable', Rule::in(['unspecified', 'specific', 'multiple', 'national', 'digital'])],
'page' => ['nullable', 'integer', 'min:1'],
]);
$query = $model::query();
if ($resource === 'opportunities') {
$query->with('primaryTerritory:id,name,location_type,source_system,source_code');
if (isset($filters['geography_scope'])) {
$query->where('geography_scope', $filters['geography_scope']);
}
}
if ($term = trim($filters['q'] ?? '')) {
$query->where(function ($query) use ($term, $resource) {
$query->whereLike($resource === 'sectors' ? 'name' : 'title', "%{$term}%")
@@ -211,6 +236,9 @@ class PortalManagementController extends Controller
'records' => $query->orderByDesc('id')->paginate(20)->withQueryString(),
'filters' => $filters,
'sectors' => $resource === 'opportunities' ? Sector::orderBy('name')->get(['id', 'name']) : [],
'geographyReviewCount' => $resource === 'opportunities'
? Opportunity::where('geography_scope', 'unspecified')->count()
: 0,
]);
}
@@ -258,6 +286,7 @@ class PortalManagementController extends Controller
'is_featured' => ['required', 'boolean'],
];
if ($resource === 'opportunities') {
$rules['geography_scope'] = ['nullable', Rule::in(['unspecified', 'specific', 'multiple', 'national', 'digital'])];
foreach (InvestmentGeography::FIELDS as $field => $type) {
$rules[$field] = ['nullable', 'uuid'];
}
@@ -285,8 +314,14 @@ class PortalManagementController extends Controller
abort_unless((int) $current->territory_version === (int) $data['territory_version'], 409, 'Localização alterada. Recarregue a ficha.');
}
$path = app(InvestmentGeography::class)->selection($data, true);
if (($data['geography_scope'] ?? null) === 'national' && $path !== []) {
throw ValidationException::withMessages(['geography_scope' => 'O âmbito nacional não pode ter uma localização territorial específica.']);
}
$data = array_diff_key($data, InvestmentGeography::FIELDS);
$data['primary_territory_id'] = $path === [] ? null : $path[array_key_last($path)]->id;
if ($path !== [] && empty($data['geography_scope'])) {
$data['geography_scope'] = 'specific';
}
$data['territory_version'] = (int) ($data['territory_version'] ?? 0) + 1;
$data['province'] = $path[0]->name ?? null;
$data['district'] = $path[1]->name ?? null;