755 lines
29 KiB
TypeScript
755 lines
29 KiB
TypeScript
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',
|
||
],
|
||
];
|
||
}
|
||
}
|