Files
Investidor-V2/resources/js/lib/iva-remission-imports.ts
T

755 lines
29 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';
import { ippItems } from './iva-remission-catalog.ts';
export { ippItems };
export const remissionVersion = 'remission-imports/1';
export const remissionSource =
'P047 — CIVA12(1)b, redacção Lei22/2022 PDF2/p2252(26), mantida Lei10/2025 PDF13. Lei7/91 art15(1)a/2, exemplar Assembleia/IFES PDF11/p9; Lei14/92 BR42 suplemento PDF23–24 não altera15. Lei4/94 art7 PDF2 e Decreto29/98 BR22-I 5.º suplemento PDF7–9 arts1–7,10–12. Decreto22/2021 BR70-I PDF1–2/p425–426 arts2–9,11–14, revoga3/83. IPP Lei17/2022 BR250 PDF4–5/p2238–2239 art22; Lei8/2025 BR248 suplemento PDF2–3 acrescenta30; Decreto37/2023 arts12(3),27/28 e QuadroV; Decreto27/2026 altera competências desde31/07/2026,30dias após publicação01/07,art3/PDF4/p1012(4). Classificação IVA2026/MZN baseada em isenção de direitos demonstrada; não concede benefício aduaneiro, não calcula direitos, ICE, base ou dedução/campo11.';
export const remissionFamilies = [
['party', 'Partido político — equipamento para funcionamento próprio'],
['patronage', 'Mecenato — livros/insumos ou fundamento específico'],
['diplomatic', 'Veículos diplomáticos/consulares — regime próprio'],
['ipp', 'Instruções Preliminares da Pauta — fundamento e âmbito próprios'],
] as const;
export const diplomaticRoles = [
['mission', 'Missão diplomática ou consular'],
['head', 'Chefe de missão'],
['agent', 'Agente diplomático/cônsul de carreira ou equiparado'],
['honorary', 'Cônsul honorário acreditado'],
['technical', 'Pessoal administrativo ou técnico'],
['agency', 'Agência especializada das Nações Unidas'],
['organisation', 'Organização equiparada ao nível de privilégios'],
] as const;
type Choice = '' | 'yes' | 'no';
export type RemissionRow = {
id: string;
family: string;
description: string;
importer: string;
period: string;
currency: string;
origin: 'projected' | 'effective';
importDate: string;
scope: string;
evidence: string;
documentRef: string;
rights: '' | 'exempt' | 'reduced' | 'zero' | 'none';
rightsRef: string;
decision:
| ''
| 'not_required'
| 'planned'
| 'requested'
| 'approved'
| 'denied';
decisionRef: string;
decisionDate: string;
requestRef: string;
specialRef: string;
ippItem: string;
ippArticle: string;
patronagePath: '' | 'book' | 'donation' | 'specific';
role: string;
quantity: string;
existingQuantity: string;
limit: string;
limitRef: string;
history: '' | 'none' | 'known';
previousDate: string;
historyRef: string;
agreementRef: string;
conditions: Record<string, Choice>;
};
export type RemissionLine = {
id: string;
status: 'exempt' | 'not_applicable' | 'indeterminate';
importVat: string | null;
deduction: null;
source: string;
reason: string;
};
type Result = {
version: string;
revision: number;
calculatedAt: string;
sourceKey: string;
inputs: RemissionRow[];
rows: RemissionLine[];
};
export type RemissionBook = {
rows: RemissionRow[];
result?: Result;
history?: Result[];
};
export function newRemission(id: string, currency: string): RemissionRow {
return {
id,
currency,
family: '',
description: '',
importer: '',
period: '',
origin: 'projected',
importDate: '',
scope: '',
evidence: '',
documentRef: '',
rights: '',
rightsRef: '',
decision: '',
decisionRef: '',
decisionDate: '',
requestRef: '',
specialRef: '',
ippItem: '',
ippArticle: '',
patronagePath: '',
role: '',
quantity: '',
existingQuantity: '',
limit: '',
limitRef: '',
history: '',
previousDate: '',
historyRef: '',
agreementRef: '',
conditions: {},
};
}
function store(draft: Record<string, unknown>) {
const s = readAcquisitionStore(draft.modelo_a_snapshot) as ReturnType<
typeof readAcquisitionStore
> & { remissionImports?: Record<string, RemissionBook> };
const fields = Object.keys(newRemission('', '')).filter(
(k) => k !== 'conditions',
);
if (
s.remissionImports !== undefined &&
(!s.remissionImports ||
typeof s.remissionImports !== 'object' ||
Array.isArray(s.remissionImports))
)
throw Error(
'Importações por remissão não recuperáveis; originais preservados.',
);
for (const b of Object.values(s.remissionImports ?? {}))
if (
!b ||
!Array.isArray(b.rows) ||
(b.history !== undefined && !Array.isArray(b.history)) ||
b.rows.some(
(r) =>
!r ||
fields.some(
(k) => typeof r[k as keyof RemissionRow] !== 'string',
) ||
!r.conditions ||
typeof r.conditions !== 'object' ||
Array.isArray(r.conditions) ||
Object.values(r.conditions).some(
(v) => !['', 'yes', 'no'].includes(v),
),
)
)
throw Error(
'Linhas por remissão não recuperáveis; originais preservados.',
);
return s;
}
export function remissionBook(draft: Record<string, unknown>): RemissionBook {
return (
store(draft).remissionImports?.[acquisitionScope(draft)] ?? { rows: [] }
);
}
export function saveRemissionBook(
draft: Record<string, unknown>,
book: RemissionBook,
): string {
const s = store(draft),
value = JSON.stringify({
...s,
remissionImports: {
...s.remissionImports,
[acquisitionScope(draft)]: book,
},
});
store({ ...draft, modelo_a_snapshot: value });
return value;
}
type Condition = readonly [string, string];
export function remissionConditions(r: RemissionRow): Condition[] {
const common: Condition[] = [
['goods', 'Importação de bens abrangidos pelo fundamento identificado'],
[
'scope',
'Beneficiário, bens, operação, período e condições coincidem com o âmbito da isenção de direitos',
],
];
if (r.family === 'party')
return [
...common,
[
'party',
'Importador é o partido político identificado e legalmente constituído',
],
[
'equipment',
'Bens de equipamento necessários ao funcionamento próprio do partido — art15(1)a',
],
[
'nonBusiness',
'Não se destinam a actividades económicas de natureza empresarial — art15(2)',
],
];
if (r.family === 'patronage')
return [
...common,
...(r.patronagePath === 'specific'
? [
[
'specific',
'A disposição expressa identificada concede isenção de direitos a estes bens; não basta doação ou dedução ao rendimento',
] as Condition,
]
: [
[
'books',
'Livros culturais, científicos ou escolares, ou insumos para a sua produção nacional — Lei4/94 art7',
] as Condition,
]),
...(r.patronagePath === 'donation'
? ([
[
'eligible',
'Acção/liberalidade elegível no art4 do Regulamento e destinatário nos termos da Lei4/94 art2',
],
[
'nonProselytism',
'Sem proselitismo confessional ou partidário nem proveito pecuniário/patrimonial directo',
],
[
'exclusive',
'Bens exclusivamente afectos à finalidade elegível — Lei4/94 art4',
],
[
'valuation',
'Apoio não monetário avaliado pela factura ou preço normal — Lei4/94 art5',
],
[
'communications',
'Comunicações aos beneficiários, tutela e Finanças nos termos do art5 do Regulamento',
],
[
'handover',
'Termo de entrega rubricado pelo representante da tutela, nos termos do art6; ou pressuposto expresso',
],
[
'unrelated',
'Sem pessoas vinculadas nos termos do art7, incluindo janela de12meses',
],
[
'irreversible',
'Liberalidade expressamente irreversível — art10(3)',
],
[
'confirmation',
'Confirmação de Finanças e entidade da área abrangida — art11; ou pressuposto expresso da projecção',
],
] as Condition[])
: []),
];
if (r.family === 'diplomatic')
return [
...common,
[
'accredited',
'Beneficiário acreditado e abrangido pelos arts2/3 do Decreto22/2021',
],
[
'vehicle',
'Veículo automóvel para uso abrangido pelo regime diplomático identificado',
],
...(['agency', 'organisation'].includes(r.role)
? [
[
'agreement',
'Acordo de Sede/específico cobre a isenção e as condições desta importação — arts11/12',
] as Condition,
]
: ([
[
'reciprocity',
'Reciprocidade e razoabilidade verificadas, incluindo restrições do Estado acreditante — arts4/7',
],
[
'register',
'Registo no MINEC e na AT para controlo quantitativo — art5, ou pressuposto expresso',
],
[
'caseRequest',
'Isenção requerida caso a caso através do MINEC — art7(2), ou pressuposto expresso',
],
...(['agent', 'technical'].includes(r.role)
? [
[
'foreignNonResident',
'Não é nacional nem residente permanente em Moçambique — art3(3)',
] as Condition,
]
: []),
] as Condition[])),
];
if (r.family === 'ipp')
return [
...common,
[
'ippGoods',
ippItems.find((i) => i.id === r.ippItem)?.label ??
'Disposição IPP específica identificada e aplicável',
],
[
'ownConditions',
'Condições, limites e legislação específica referida neste item demonstrados; não basta seleccionar a categoria',
],
[
'priorRequest',
'Requerimento prévio à entidade competente — Decreto37/2023 art27(2)',
],
[
'use',
'Uso exclusivo para o fim/beneficiário; compromisso de uso art28(1–3) identificado ou previsto',
],
];
return [];
}
export function ippAuthority(item: string, date: string) {
const n = Number(item),
current = date >= '2026-07-31';
if (n === 30 && !current)
return 'Competência específica anterior a31/07/2026 por identificar; não retroagir Decreto27/2026';
const groups = current
? [
[[9, 11, 15, 16, 21, 22, 28, 29], 'Ministro das Finanças'],
[
[2, 8, 12, 14, 20, 23, 24, 25, 27, 30],
'Presidente da Autoridade Tributária',
],
[[1, 3, 4, 7, 10, 26], 'Director-Geral das Alfândegas'],
[[5, 6, 13, 17, 18, 19], 'Chefes das Estâncias Aduaneiras'],
]
: [
[[8, 9, 11, 15, 16, 21, 22, 23, 28, 29], 'Ministro das Finanças'],
[
[2, 4, 12, 14, 20, 24, 25, 27],
'Presidente da Autoridade Tributária',
],
[[1, 7, 26], 'Director-Geral das Alfândegas'],
[[3, 10], 'Director Regional'],
[[5, 6, 13, 17, 18, 19], 'Chefes das Estâncias Aduaneiras'],
];
return (
(groups.find(([ids]) =>
(ids as number[]).includes(n),
)?.[1] as string) ||
'Competência segundo disposição específica identificada'
);
}
function date(v: string) {
const d = new Date(v + 'T00:00:00Z');
return (
/^\d{4}-\d{2}-\d{2}$/.test(v) &&
Number.isFinite(d.getTime()) &&
d.toISOString().slice(0, 10) === v
);
}
function years(v: string, n: number) {
const d = new Date(v + 'T00:00:00Z'),
m = d.getUTCMonth();
d.setUTCFullYear(d.getUTCFullYear() + n);
if (d.getUTCMonth() !== m) d.setUTCDate(0);
return d.toISOString().slice(0, 10);
}
const positive = (v: string) => /^[1-9]\d{0,5}$/.test(v),
nonnegative = (v: string) => /^(0|[1-9]\d{0,5})$/.test(v);
export function evaluateRemission(
r: RemissionRow,
currency: unknown,
): RemissionLine {
const result: RemissionLine = {
id: r.id,
status: 'indeterminate',
importVat: null,
deduction: null,
source: remissionSource,
reason: '',
};
const stop = (
reason: string,
status: RemissionLine['status'] = 'indeterminate',
) => ({ ...result, status, reason });
if (!remissionFamilies.some(([k]) => k === r.family))
return stop('Escolha uma das quatro famílias documentadas.');
if (
currency !== 'MZN' ||
r.currency !== currency ||
!/^2026-(0[1-9]|1[0-2])$/.test(r.period) ||
!date(r.importDate) ||
r.importDate.slice(0, 7) !== r.period
)
return stop(
'Indique moedaMZN e data/período2026 coerentes; não se presume conversão.',
);
if (
!['projected', 'effective'].includes(r.origin) ||
![
r.description,
r.importer,
r.scope,
r.evidence,
r.documentRef,
r.rightsRef,
].every((v) => v.trim())
)
return stop(
'Identifique bens, importador, âmbito, documentos e fundamento da isenção de direitos ou pressupostos expressos.',
);
if (['reduced', 'zero', 'none'].includes(r.rights))
return stop(
'Não demonstrada isenção integral de direitos nesta remissão. Redução, taxa pautal0% e não incidência são distintas de isenção — Decreto37/2023 art12(3). Outros fundamentos e eventual regime proporcional permanecem separados.',
'not_applicable',
);
if (r.rights !== 'exempt')
return stop(
'Situação dos direitos indeterminada; não equivale a IVAzero.',
);
const conditions = remissionConditions(r);
if (conditions.some(([k]) => r.conditions[k] === 'no'))
return stop(
'Condição própria desta família não satisfeita; outros fundamentos não são excluídos.',
'not_applicable',
);
if (conditions.some(([k]) => r.conditions[k] !== 'yes'))
return stop(
'Condições próprias por esclarecer; nenhuma confirmação é inferida de outra família.',
);
if (
r.family === 'patronage' &&
!['book', 'donation', 'specific'].includes(r.patronagePath)
)
return stop(
'Escolha livro/insumo, liberalidade ou disposição específica; doação genérica não concede isenção de importação.',
);
if (
r.family === 'patronage' &&
['donation', 'specific'].includes(r.patronagePath) &&
!r.specialRef.trim()
)
return stop(
'Identifique acção/liberalidade e confirmação regulamentar ou disposição específica e âmbito.',
);
if (r.family === 'ipp') {
if (
!ippItems.some((i) => i.id === r.ippItem) &&
!(r.ippItem === 'specific' && r.ippArticle.trim())
)
return stop(
'Escolha itemIPP ou identifique artigo específico; lista não classifica automaticamente os bens.',
);
if (!date(r.decisionDate))
return stop(
'Indique data da decisão ou previsão para aplicar a competência vigente nesse acto; a data de importação não a substitui.',
);
if (
r.ippItem === '30' &&
r.decisionDate < '2026-07-31' &&
!r.ippArticle.trim()
)
return stop(
'Item30 antes de31/07/2026:identifique a disposição de competência aplicável; não retroagir Decreto27/2026.',
);
if (!r.requestRef.trim() || !r.specialRef.trim())
return stop(
'Identifique requerimento prévio, condições/remissões e compromisso de uso. Confirmação de tutela apenas quando prevista no item.',
);
result.source +=
';IPP22/item' +
r.ippItem +
';' +
r.ippArticle +
';entidade segundo data:' +
ippAuthority(r.ippItem, r.decisionDate);
}
if (r.family === 'diplomatic') {
if (!diplomaticRoles.some(([k]) => k === r.role))
return stop(
'Identifique categoria diplomática; os limites não são iguais.',
);
if (['agency', 'organisation'].includes(r.role)) {
if (!r.agreementRef.trim() || !r.specialRef.trim())
return stop(
'Identifique o Acordo de Sede/específico, cláusula e condições; não aplicar automaticamente quotas de pessoas às organizações.',
);
} else {
if (
!r.requestRef.trim() ||
!r.historyRef.trim() ||
!r.limitRef.trim() ||
!positive(r.quantity) ||
!nonnegative(r.existingQuantity)
)
return stop(
'Identifique pedido viaMINEC, histórico externo e reciprocidade/limite; informe quantidades inteiras.',
);
const statutory = (
{ head: 3, agent: 2, honorary: 1, technical: 1 } as Record<
string,
number
>
)[r.role];
const limit = positive(r.limit)
? r.role === 'mission'
? Number(r.limit)
: Math.min(Number(r.limit), statutory)
: null;
if (limit === null || limit === undefined)
return stop(
'Limite da missão fixado caso a caso pelo MINEC; sem limite numérico presumido.',
);
if (Number(r.quantity) + Number(r.existingQuantity) > limit)
return stop(
'Quantidade excede o limite desta categoria/reciprocidade — art9; separar bens abrangidos e excedentes, sem isentar toda a linha.',
'not_applicable',
);
if (
r.history === 'known' &&
(!date(r.previousDate) ||
r.importDate < years(r.previousDate, 5))
)
return stop(
'Nova importação antes de decorridos cinco anos ou data anterior por esclarecer — art9(3).',
);
if (!['none', 'known'].includes(r.history))
return stop(
'Histórico por esclarecer; dossier vazio não demonstra ausência de importação anterior.',
);
}
}
const required =
r.family === 'ipp' ||
r.family === 'diplomatic' ||
(r.family === 'patronage' && r.patronagePath === 'donation');
if (r.decision === 'denied')
return stop(
'Pedido indeferido neste âmbito; não há isenção concedida.',
'not_applicable',
);
if (r.decision === 'requested')
return stop(
'Pedido apresentado conserva benefício solicitado; não é concessão.',
);
if (
required &&
!(
(r.decision === 'approved' && r.decisionRef.trim()) ||
(r.origin === 'projected' &&
r.decision === 'planned' &&
r.decisionRef.trim())
)
)
return stop(
'Indique decisão/confirmação abrangente ou pressuposto expresso de deferimento na projecção; efectivo não usa deferimento previsto.',
);
if (
!required &&
!['not_required', 'approved', 'planned'].includes(r.decision)
)
return stop(
'Registe aplicação legal directa ou a decisão/pressuposto identificado; não é criada nova aprovação.',
);
if (
!required &&
['approved', 'planned'].includes(r.decision) &&
(!r.decisionRef.trim() ||
(r.origin === 'effective' && r.decision === 'planned'))
)
return stop(
'Decisão/pressuposto deve ter referência e natureza coerentes.',
);
return {
...result,
status: 'exempt',
importVat: '0.00',
reason:
'Isenção de IVA12(1)b: ' +
(remissionFamilies.find(([k]) => k === r.family)?.[1] ?? r.family) +
', apenas no âmbito da isenção de direitos demonstrada. ' +
(r.origin === 'projected'
? 'Cenário condicionado aos pressupostos expressos; não benefício concedido.'
: 'Factos efectivos e âmbito declarados; o portal não emite concessão.') +
' Sem direitos, ICE, dedução/campo11, soma aos livros internos ou cálculo de alienação; restrições de uso e alterações de destino mantêm o seu regime próprio.',
};
}
function key(draft: Record<string, unknown>, book: RemissionBook) {
return JSON.stringify({
version: remissionVersion,
scope: acquisitionScope(draft),
currency: draft.currency,
location: draft.selection_labels ?? {},
rows: book.rows,
});
}
export function remissionCurrent(
draft: Record<string, unknown>,
book: RemissionBook,
) {
return !!book.result && book.result.sourceKey === key(draft, book);
}
export function recordRemissions(
draft: Record<string, unknown>,
book: RemissionBook,
): RemissionBook {
if (
!book.rows.length ||
new Set(book.rows.map((r) => r.id)).size !== book.rows.length
)
throw Error('Identifique linhas distintas; originais preservados.');
const result: Result = {
version: remissionVersion,
revision: (book.result?.revision ?? 0) + 1,
calculatedAt: new Date().toISOString(),
sourceKey: key(draft, book),
inputs: structuredClone(book.rows),
rows: book.rows.map((r) => evaluateRemission(r, draft.currency)),
};
for (let i = 0; i < book.rows.length; i++) {
const r = book.rows[i];
if (
r.family !== 'diplomatic' ||
['agency', 'organisation'].includes(r.role) ||
result.rows[i].status !== 'exempt'
)
continue;
const peers = book.rows.filter(
(p, j) =>
j !== i &&
p.family === 'diplomatic' &&
p.role === r.role &&
p.importer === r.importer &&
p.origin === r.origin &&
evaluateRemission(p, draft.currency).status === 'exempt',
);
const statutory =
(
{ head: 3, agent: 2, honorary: 1, technical: 1 } as Record<
string,
number
>
)[r.role] ?? Number(r.limit);
const limit = Math.min(
statutory,
Number(r.limit),
...peers.map((p) => Number(p.limit)),
);
const quantity =
Number(r.quantity) +
peers.reduce((sum, p) => sum + Number(p.quantity), 0) +
Math.max(
Number(r.existingQuantity),
...peers.map((p) => Number(p.existingQuantity)),
);
if (
quantity > limit ||
peers.some(
(p) =>
p.importDate !== r.importDate &&
Math.max(
Date.parse(p.importDate),
Date.parse(r.importDate),
) <
Date.parse(
years(
p.importDate < r.importDate
? p.importDate
: r.importDate,
5,
),
),
)
)
result.rows[i] = {
...result.rows[i],
status: 'indeterminate',
importVat: null,
reason: 'Linhas do mesmo beneficiário/categoria excedem o limite conjunto ou repetem importação em cinco anos; conferir histórico e bens abrangidos, sem dupla utilização.',
};
}
const next = {
...book,
result,
history: [
...(book.history ?? []),
...(book.result ? [book.result] : []),
],
};
saveRemissionBook(draft, next);
return next;
}
export function remissionReportRows(
draft: Record<string, unknown>,
): Array<[string, string, string, string]> {
try {
const b = remissionBook(draft),
v = b.result;
if (!v) return [];
return v.inputs.map((r, i) => {
const context = [
`Importador:${r.importer}`,
`natureza:${r.origin === 'projected' ? 'projecção condicionada' : 'factos efectivos'}`,
`data:${r.importDate}`,
`documentos:${r.documentRef}`,
`âmbito:${r.scope}`,
`factos:${r.evidence}`,
`direitos:${r.rights};fundamento:${r.rightsRef}`,
`decisão:${r.decision};data:${r.decisionDate};referência:${r.decisionRef}`,
];
if (r.family === 'ipp')
context.push(
`IPP:${r.ippItem};artigo:${r.ippArticle};condições e uso:${r.specialRef};requerimento:${r.requestRef}`,
);
if (r.family === 'patronage')
context.push(
`Mecenato:${r.patronagePath};referência:${r.specialRef}`,
);
if (r.family === 'diplomatic')
context.push(
`Categoria:${diplomaticRoles.find(([k]) => k === r.role)?.[1] ?? r.role}`,
`acordo:${r.agreementRef}`,
`pedido:${r.requestRef}`,
`condições:${r.specialRef}`,
`quantidade:${r.quantity};já abrangidos:${r.existingQuantity};limite:${r.limit};referência:${r.limitRef}`,
`histórico:${r.historyRef};anterior:${r.previousDate || r.history}`,
);
context.push(
...remissionConditions(r).map(
([k, label]) =>
`${label}:${r.conditions[k] === 'yes' ? 'Sim' : r.conditions[k] === 'no' ? 'Não' : 'Por esclarecer'}`,
),
);
return [
'IVA mensal',
`P047 — ${r.period} — ${r.id}`,
`${r.description};${v.rows[i].reason};IVA:${v.rows[i].importVat ?? 'Indeterminado'};dedução:Não apurada`,
`${v.rows[i].source};${remissionCurrent(draft, b) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${v.revision};${v.calculatedAt};${context.join(';')}`,
];
});
} catch (e) {
return [
[
'IVA mensal',
'P047 — recuperação',
String(e),
'Originais preservados',
],
];
}
}