Files
Investidor-V2/resources/js/lib/investor-context.ts
T
2026-11-02 20:31:15 +02:00

251 lines
8.2 KiB
TypeScript

import type {
Preparation,
PreparationDocument,
} from '@/components/private-investment-preparation';
function csrfToken(): string {
try {
return decodeURIComponent(
document.cookie
.split(';')
.map((value) => value.trim())
.find((c) => c.startsWith('XSRF-TOKEN='))
?.slice('XSRF-TOKEN='.length) ?? '',
);
} catch {
throw new Error(
'A sessão não pôde ser validada. Actualize a página e tente novamente.',
);
}
}
export type ContextDocument = {
resource_id: number;
document_id: string;
metadata_version: number;
document_version: number;
edition: string | null;
sha256: string;
source_reference: string;
title: string;
support_id: string;
support_version: number;
section: string | null;
opportunity_id: number;
download_url: string;
};
export type ContextReference = {
key: string;
kind: string;
id: string;
title: string;
href: string;
version: number | string | null;
sha256: string;
fields: Record<string, string>;
analysis?: {
documented_sector?: string | null;
documented_location?: string | null;
[key: string]: unknown;
};
documents?: ContextDocument[];
visibility?: 'private' | 'public';
state?: string | null;
};
export type RequirementsAssessment = {
version: string;
fiscal_notice: string;
requirements: {
opportunity_key: string;
opportunity_title: string;
code: string;
title: string;
state: string;
step: string;
authority: string;
documents: string[];
basis: string;
source_url: string;
decision: string;
}[];
};
export type OpportunityChoice = {
park?: ContextReference | null;
portfolio_origins?: {
id: string;
title: string;
kind: string;
href: string;
version: number;
sha256: string;
}[];
corridor_key?: string | null;
corridor_name?: string | null;
allowed_provinces?: import('@/components/investment-territory').TerritoryOption[];
activities?: string[];
territory_path?: import('@/components/investment-territory').TerritoryOption[];
confirm_location_change?: boolean;
opportunity: ContextReference;
territory: ContextReference | null;
location_reason: string;
location_state: string;
coverage?: string | null;
source?: string | null;
source_sha256?: string | null;
};
export type DossierContext = {
complements?: import('@/components/investment-complements').Complement[];
requirements_assessment?: RequirementsAssessment;
opportunity_choices?: OpportunityChoice[];
revision: number;
selection: Partial<
Record<
'portfolio' | 'opportunity' | 'sector' | 'territory' | 'park',
ContextReference
>
>;
fields: Record<string, string>;
provenance: Record<
string,
{
kind?: string;
title?: string;
id?: string;
version?: number | string;
}
>;
updated_at?: string;
dependency_hash?: string;
};
export type ContextView = {
private_investment?: Preparation | null;
preparation_documents?: PreparationDocument[];
simulator_context_revision?: number | null;
case: { id: string; title: string; version: number };
context: DossierContext;
can_write: boolean;
next: { code: string; title: string } | null;
steps: { code: string; title: string; state: string }[];
results: { id: string; engine: string; context_state?: string }[];
requests: {
id: string;
status: string;
protocol: string | null;
context_state?: string;
}[];
prefill: Record<string, Record<string, unknown>>;
notice: string;
};
export type ContextPlan = {
complements?: import('@/components/investment-complements').Complement[];
requirements_assessment?: RequirementsAssessment;
opportunity_choices?: OpportunityChoice[];
private_investment?: { before: Preparation; after: Preparation } | null;
preview_hash: string;
version: number;
revision: number;
fields: Record<string, string>;
selection: DossierContext['selection'];
updated: { field: string; before: string | null; after: string }[];
maintained: { field: string; value: string; proposed: string }[];
dependent_results: { id: string; engine: string }[];
requests: {
id: string;
status: string;
protocol: string | null;
version: number;
}[];
review_steps: string[];
documents_preserved: number;
notice: string;
};
// Leave headroom below the browser's shared 64 KiB keepalive budget.
const contextKeepaliveBudget = 60000;
let contextKeepaliveBytes = 0;
export async function contextApi<T>(
url: string,
body?: unknown,
method = 'POST',
keepalive = false,
): Promise<T> {
const serializedBody =
body === undefined ? undefined : JSON.stringify(body);
const bytes = serializedBody
? new TextEncoder().encode(serializedBody).byteLength
: 0;
const useKeepalive =
keepalive && contextKeepaliveBytes + bytes < contextKeepaliveBudget;
if (useKeepalive) contextKeepaliveBytes += bytes;
try {
const response = await fetch(url, {
keepalive: useKeepalive,
method: body === undefined ? 'GET' : method,
credentials: 'same-origin',
headers: {
Accept: 'application/json',
...(body === undefined
? {}
: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken(),
}),
},
...(serializedBody === undefined ? {} : { body: serializedBody }),
});
const data = await response.json().catch(() => null);
if (!response.ok || data === null)
throw new Error(
data?.message ||
`Não foi possível recuperar ou guardar o contexto (HTTP ${response.status}).`,
);
return data as T;
} finally {
if (useKeepalive) contextKeepaliveBytes -= bytes;
}
}
export const contextFieldNames: Record<string, string> = {
investment_form: 'Tipo do investimento',
investment_preparation: 'Preparação do pedido',
investment_activity_scope: 'Natureza da actividade',
implementation_mode: 'Modalidade da empresa',
capital_origin: 'Origem dos capitais',
site_tenure: 'Utilização das instalações',
project_concession: 'PPP ou concessão empresarial',
significant_implications: 'Implicações significativas',
land_extension: 'Extensão de terra',
forest_concession: 'Concessão florestal',
forest_concession_hectares: 'Área florestal em hectares',
total_investment: 'Valor total do investimento',
area_required: 'Área de terra requerida',
area_unit: 'Unidade da área',
title: 'Título do investimento',
territory_id: 'Localização documentada',
location_source: 'Origem da localização',
description: 'Descrição',
project_title: 'Título do investimento',
project_summary: 'Descrição do investimento',
sector_id: 'Sector (identificador)',
sector: 'Sector',
province_id: 'Província (identificador)',
district_id: 'Distrito (identificador)',
admin_post_id: 'Posto administrativo',
locality_id: 'Localidade',
province: 'Província',
district: 'Distrito',
location: 'Localização',
currency: 'Moeda do projecto',
person_type: 'Tipo de pessoa',
fiscal_period: 'Ano fiscal',
fiscal_periodicity: 'Periodicidade',
fiscal_month: 'Mês fiscal',
fiscal_residency_country: 'País de residência fiscal declarado',
activity_id: 'Actividade',
project_type: 'Modalidade do investimento',
maturity_stage: 'Fase do investimento',
expected_start_date: 'Início previsto',
investor_origin: 'Origem do investidor',
entity_stage: 'Situação da entidade',
tax_residency_country: 'Residência fiscal declarada',
investment_regime: 'Enquadramento territorial consultado',
special_zone_id: 'ZEE, ZFI ou parque',
};