Files
Investidor-V2/resources/js/lib/iva-goods-location.ts
T

393 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
export const goodsLocationVersion = 'goods-territoriality/1';
export const goodsLocationSource =
'CIVA republicado Lei13/2016,6(1)/(2),PDF7/p1426(265):localização dos bens no território nacional ao iniciar transporte/expedição para o adquirente ou,sem transporte/expedição,ao pôr à sua disposição. Não obstante6(1),são também tributáveis transmissões pelo importador e subsequentes de bens transportados/expedidos do estrangeiro,antes da importação. Lei10/2025PDF12mantém6(1)/(2),altera6(7–9)quanto a digitais;recorte2026,bens corpóreos,não digitais. Endereço/nacionalidade do cliente não substituem localização dos bens. Classificação territorial apenas,não isenção/taxa/IVA/dedução/campo11/direitos aduaneiros ou totaisModeloA.';
export const goodsLocationTreatments = [
{
id: 'goods_with_transport',
label: 'Bens com transporte ou expedição para o adquirente',
},
{
id: 'goods_without_transport',
label: 'Bens sem transporte ou expedição',
},
{
id: 'goods_before_import',
label: 'Transmissão pelo importador ou subsequente,antes da importação',
},
] as const;
export const goodsLocationConditions = [
[
'goodsSupply',
'Está identificada uma transmissão de bens corpóreos,não uma prestação de serviços ou bens digitais abrangidos por regras próprias',
],
[
'factsCorrespond',
'Os factos,referências e âmbito correspondem a esta operação e período,não apenas ao endereço ou nacionalidade do adquirente',
],
] as const;
export const goodsOwnConditions = {
goods_with_transport: [
[
'transportToBuyer',
'Os bens são transportados ou expedidos para o adquirente',
],
[
'notBeforeImport',
'Não corresponde à transmissão pré-importação abrangida pelo artigo6(2)',
],
],
goods_without_transport: [
[
'noTransport',
'Não há expedição ou transporte dos bens para esta transmissão',
],
[
'notBeforeImport',
'Não corresponde à transmissão pré-importação abrangida pelo artigo6(2)',
],
],
goods_before_import: [
['fromAbroad', 'Os bens são transportados ou expedidos do estrangeiro'],
[
'nationalImport',
'Está identificada a importação desses bens para o território nacional',
],
[
'importerChain',
'A transmissão é feita pelo importador ou é subsequente na cadeia desses bens',
],
[
'beforeImport',
'Esta transmissão ocorre antes da importação identificada',
],
],
} as const;
export function ownGoodsConditions(
treatment: string,
): ReadonlyArray<readonly [string, string]> {
return (
goodsOwnConditions[treatment as keyof typeof goodsOwnConditions] ?? []
);
}
type Choice = '' | 'yes' | 'no';
export type GoodsLocationRow = {
id: string;
treatment: string;
description: string;
period: string;
origin: 'projected' | 'effective';
operationDate: string;
operationRef: string;
documentRef: string;
documentDate: string;
goodsPlace: string;
placeCountry: string;
placeRef: string;
transportRef: string;
importRef: string;
chainRef: string;
scope: string;
evidence: string;
conditions: Partial<Record<string, Choice>>;
};
type LineResult = {
id: string;
status: 'determined' | 'not_applicable' | 'indeterminate';
territory: 'national' | 'outside' | null;
baseAmount: null;
vat: null;
rate: null;
reason: string;
};
export type GoodsLocationCalculation = {
version: string;
revision: number;
calculatedAt: string;
opportunity: string;
location: string;
sourceKey: string;
source: string;
inputs: GoodsLocationRow[];
rows: LineResult[];
};
export type GoodsLocationBook = {
rows: GoodsLocationRow[];
result?: GoodsLocationCalculation;
history?: GoodsLocationCalculation[];
};
type Store = ReturnType<typeof readAcquisitionStore> & {
goodsLocations?: Record<string, GoodsLocationBook>;
};
export function newGoodsLocation(id: string): GoodsLocationRow {
return {
id,
treatment: '',
description: '',
period: '',
origin: 'projected',
operationDate: '',
operationRef: '',
documentRef: '',
documentDate: '',
goodsPlace: '',
placeCountry: '',
placeRef: '',
transportRef: '',
importRef: '',
chainRef: '',
scope: '',
evidence: '',
conditions: {},
};
}
export function switchGoodsTreatment(
row: GoodsLocationRow,
treatment: string,
): GoodsLocationRow {
return {
...row,
treatment,
conditions: Object.fromEntries(
goodsLocationConditions.map(([key]) => [
key,
row.conditions[key] ?? '',
]),
),
};
}
function readStore(draft: Record<string, unknown>): Store {
const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
if (
store.goodsLocations !== undefined &&
(!store.goodsLocations ||
typeof store.goodsLocations !== 'object' ||
Array.isArray(store.goodsLocations))
)
throw new Error(
'Bases de contraprestação não recuperáveis;originais preservados.',
);
const fields = Object.keys(newGoodsLocation('')).filter(
(key) => key !== 'conditions',
);
for (const book of Object.values(store.goodsLocations ?? {}))
if (
!book ||
!Array.isArray(book.rows) ||
(book.history !== undefined && !Array.isArray(book.history)) ||
book.rows.some(
(row) =>
!row ||
fields.some(
(key) =>
typeof row[key as keyof GoodsLocationRow] !==
'string',
) ||
!row.conditions ||
typeof row.conditions !== 'object' ||
Array.isArray(row.conditions),
)
)
throw new Error(
'Linhas de contraprestação não recuperáveis;originais preservados,sem substituição por lista vazia.',
);
return store;
}
export function goodsLocationBook(
draft: Record<string, unknown>,
): GoodsLocationBook {
return (
readStore(draft).goodsLocations?.[acquisitionScope(draft)] ?? {
rows: [],
}
);
}
export function saveGoodsLocationBook(
draft: Record<string, unknown>,
book: GoodsLocationBook,
): string {
const current = readStore(draft),
encoded = JSON.stringify({
...current,
goodsLocations: {
...current.goodsLocations,
[acquisitionScope(draft)]: book,
},
});
readStore({ ...draft, modelo_a_snapshot: encoded });
return encoded;
}
function validDate(value: string): boolean {
const d = new Date(value + 'T00:00:00Z');
return (
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
Number.isFinite(d.getTime()) &&
d.toISOString().slice(0, 10) === value
);
}
export function evaluateGoodsLocation(row: GoodsLocationRow): LineResult {
const result: LineResult = {
id: row.id,
status: 'indeterminate',
territory: null,
baseAmount: null,
vat: null,
rate: null,
reason: '',
};
const stop = (
reason: string,
status: LineResult['status'] = 'indeterminate',
) => ({ ...result, status, reason });
const rule = goodsLocationTreatments.find((t) => t.id === row.treatment);
if (!rule)
return stop(
'Identifique transporte,ausência de transporte ou transmissão antes da importação;desconhecido não determina território.',
);
if (
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
!['projected', 'effective'].includes(row.origin) ||
!validDate(row.operationDate) ||
row.operationDate.slice(0, 7) !== row.period
)
return stop(
'Identifique mês em2026 e data da operação correspondentes,com dados efectivos declarados ou pressupostos expressos de projecção.',
);
const required = [
...goodsLocationConditions,
...ownGoodsConditions(row.treatment),
];
const failed = required.filter(([key]) => row.conditions[key] === 'no');
if (failed.length)
return stop(
'Este percurso não se aplica: ' +
failed.map(([, label]) => label).join('; ') +
'. Rever o percurso,não classificar automaticamente como fora do território ou isento.',
'not_applicable',
);
if (required.some(([key]) => row.conditions[key] !== 'yes'))
return stop(
'Confirme os factos próprios deste percurso;desconhecido não é fora do território,isenção ou IVA zero.',
);
if (
!row.description.trim() ||
!row.operationRef.trim() ||
!row.documentRef.trim() ||
!validDate(row.documentDate) ||
!row.scope.trim() ||
!row.evidence.trim()
)
return stop(
'Identifique bens/operação,documento/data,âmbito e factos ou pressupostos expressos;reutilize referências existentes,sem certificado/aprovador novo.',
);
if (row.treatment === 'goods_before_import') {
if (
!row.transportRef.trim() ||
!row.importRef.trim() ||
!row.chainRef.trim()
)
return stop(
'Identifique transporte/expedição do estrangeiro,importação nacional e posição da transmissão na cadeia,ou pressupostos expressos;não inferir a condição pelo endereço do adquirente.',
);
return {
...result,
status: 'determined',
territory: 'national',
reason: `Art6(2):transmissão pelo importador ou subsequente de bens transportados/expedidos do estrangeiro,antes da importação nacional identificada;abrangida territorialmente emMoçambique,${row.origin === 'projected' ? 'nas condições expressamente assumidas na projecção' : 'nos dados efectivos declarados'}. A localização física anterior no estrangeiro não afasta por si esta regra. Classificação não determina isenção,base,taxa,IVA,dedução/campo11,direitos ou totaisModeloA.`,
};
}
if (
!row.goodsPlace.trim() ||
!row.placeRef.trim() ||
!['MZ', 'foreign'].includes(row.placeCountry)
)
return stop(
'Identifique onde os bens se encontram no momento relevante e respectiva referência:início do transporte/expedição ou colocação à disposição sem transporte;endereço do cliente não substitui esse facto.',
);
if (row.treatment === 'goods_with_transport' && !row.transportRef.trim())
return stop(
'Identifique o transporte/expedição correspondente ao início referido,ou pressuposto expresso.',
);
const territory = row.placeCountry === 'MZ' ? 'national' : 'outside';
return {
...result,
status: 'determined',
territory,
reason: `Art6(1):bens ${territory === 'national' ? 'situados emMoçambique' : 'situados fora deMoçambique'} ${row.treatment === 'goods_with_transport' ? 'ao iniciar o transporte/expedição para o adquirente' : 'ao serem postos à disposição do adquirente,sem transporte/expedição'},${row.origin === 'projected' ? 'nas condições expressamente assumidas na projecção' : 'nos dados efectivos declarados'}. Factos de6(2)conferidos como não aplicáveis a este percurso. ${territory === 'outside' ? 'Fora deste âmbito territorial não equivale a isenção ou taxa/IVA zero.' : 'Abrangência territorial não confirma isenção,taxa ou IVA.'} Classificação não determina base,dedução/campo11,direitos ou totaisModeloA.`,
};
}
function key(draft: Record<string, unknown>, book: GoodsLocationBook): string {
return JSON.stringify({
version: goodsLocationVersion,
scope: acquisitionScope(draft),
location: draft.selection_labels ?? {},
rows: book.rows,
});
}
export function goodsLocationCurrent(
draft: Record<string, unknown>,
book: GoodsLocationBook,
): boolean {
return !!book.result && book.result.sourceKey === key(draft, book);
}
export function recordGoodsLocations(
draft: Record<string, unknown>,
book: GoodsLocationBook,
): GoodsLocationBook {
if (
!book.rows.length ||
new Set(book.rows.map((row) => row.id)).size !== book.rows.length
)
throw new Error(
'Indique operações com identificadores distintos;originais conservados.',
);
const result: GoodsLocationCalculation = {
version: goodsLocationVersion,
revision: (book.result?.revision ?? 0) + 1,
calculatedAt: new Date().toISOString(),
opportunity: acquisitionScope(draft),
location: JSON.stringify(draft.selection_labels ?? {}),
sourceKey: key(draft, book),
source: goodsLocationSource,
inputs: structuredClone(book.rows),
rows: book.rows.map((row) => evaluateGoodsLocation(row)),
};
const next = {
...book,
result,
history: [
...(book.history ?? []),
...(book.result ? [book.result] : []),
],
};
saveGoodsLocationBook(draft, next);
return next;
}
export function goodsLocationReportRows(
draft: Record<string, unknown>,
): Array<[string, string, string, string]> {
try {
const book = goodsLocationBook(draft),
result = book.result;
if (!result) return [];
return result.inputs.map((row, index) => [
'IVA mensal',
`Localização P105 — ${row.period} — ${row.id}`,
`${row.description};classificação territorial: ${result.rows[index].territory === 'national' ? 'Moçambique' : result.rows[index].territory === 'outside' ? 'Fora do território nacional' : 'Indeterminada'};IVA: Indeterminado;${result.rows[index].reason}`,
`${result.opportunity};${result.location};${goodsLocationCurrent(draft, book) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${result.source};${row.origin === 'projected' ? 'Projecção' : 'Dados efectivos declarados'};${goodsLocationTreatments.find((t) => t.id === row.treatment)?.label ?? 'Percurso por indicar'};operação ${row.operationRef},${row.operationDate};${row.treatment === 'goods_before_import' ? `transporte ${row.transportRef};importação ${row.importRef};cadeia ${row.chainRef}` : `local dos bens ${row.goodsPlace},${row.placeCountry};referência ${row.placeRef}${row.treatment === 'goods_with_transport' ? `;transporte ${row.transportRef}` : ''}`};âmbito ${row.scope};documento ${row.documentRef},${row.documentDate};condições ${[...goodsLocationConditions, ...ownGoodsConditions(row.treatment)].map(([key, label]) => label + ': ' + (row.conditions[key] === 'yes' ? 'Sim' : row.conditions[key] === 'no' ? 'Não' : 'Por indicar')).join('; ')};fundamento factual ${row.evidence};não determina isenção/IVA/campo11/totaisModeloA.`,
]);
} catch {
return [
[
'IVA mensal',
'Localização de bens',
'Dados não recuperáveis',
'Originais preservados;sem classificar por defeito.',
],
];
}
}