251 lines
8.2 KiB
TypeScript
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',
|
|
};
|