771 lines
30 KiB
TypeScript
771 lines
30 KiB
TypeScript
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
||
export const minerVersion = 'miner-imports/1';
|
||
export const minerSource =
|
||
'Lei n.º2/2011,de11/01:Regime Aduaneiro Aplicável aos Mineiros na República da África do Sul;BR1-I,4.ºsuplemento,arts1–4PDF1(sem número impresso),art5PDF2/p110-(8). OriginalMCNet149:Lei-2-2011-BR-1-4-suplemento-MCNet.pdf;ficheiro conjunto,não regra da Lei do Voluntariado. Decreto3/2011,de16/03,arts1–8,BR11-I,PDF2–3/p152–153. CIVA12(2)b,2016PDF11/p1426(269),mantido2022/2025:IVA na proporção da redução de direitos. Âmbito demonstradoRSA/2026/MZN;sem direitos,base aduaneira ou dedução.';
|
||
type Choice = '' | 'yes' | 'no';
|
||
export const minerTreatments = [
|
||
{
|
||
id: 'baggage',
|
||
label: 'Bagagem sem fins comerciais',
|
||
article: 'Lei2/2011,2(1)a;Decreto3/2011,1b/3(1)a',
|
||
conditions: [
|
||
[
|
||
'personalBaggage',
|
||
'Objectos pessoais pertencentes ao mineiro durante a estadia na RSA,em quantidades razoáveis e sem fins comerciais',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'monthly',
|
||
label: 'Remessa mensal',
|
||
article: 'Lei2/2011,2(1)c;Decreto3/2011,1e/3(1)b',
|
||
conditions: [
|
||
[
|
||
'ownedRSA',
|
||
'Bens pertencentes ao mineiro,adquiridos na RSA,para uso próprio ou da família',
|
||
],
|
||
[
|
||
'route',
|
||
'Remessa por empresa licenciada ou por meios próprios quando acompanhada pelo mineiro',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'accumulated',
|
||
label: 'Remessas acumuladas até seis meses',
|
||
article: 'Decreto3/2011,4',
|
||
conditions: [
|
||
[
|
||
'ownedRSA',
|
||
'Bens pertencentes ao mineiro,adquiridos na RSA,para a família',
|
||
],
|
||
[
|
||
'licensedCompany',
|
||
'Remessa acumulada por empresa transportadora/distribuidora autorizada',
|
||
],
|
||
[
|
||
'unusedMonths',
|
||
'Meses e saldo indicados não foram usados noutras remessas;conferir o registo completo',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'appliance',
|
||
label: 'Um electrodoméstico de cada tipo por ano',
|
||
article: 'Lei2/2011,2(1)b;Decreto3/2011,3(1)c/8(3)',
|
||
conditions: [
|
||
[
|
||
'oneAppliance',
|
||
'Um único electrodoméstico do tipo identificado nesta linha',
|
||
],
|
||
[
|
||
'annualQuota',
|
||
'Não foi usado este direito para o mesmo tipo/ano,incluindo fora deste dossier',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'vehicle',
|
||
label: 'Veículo ou tractor e alfaias — direito em cinco anos',
|
||
article: 'Lei2/2011,2(1)d/2;Decreto3/2011,3(1)d/6(3)/(8)/(9)/8(3)',
|
||
conditions: [
|
||
[
|
||
'oneVehicle',
|
||
'Um veículo automóvel OU um tractor agrícola com respectivas alfaias,identificado nesta linha',
|
||
],
|
||
[
|
||
'vehicleQuota',
|
||
'Direito identificado disponível:um veículo e um tractor/alfaias em cada cinco anos,considerando benefícios usados fora deste dossier',
|
||
],
|
||
[
|
||
'noAlienation',
|
||
'Sem alienação antes de cinco anos da importação;incumprimento exige pagamento das imposições,não regularização automática pelo simulador',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'return_baggage',
|
||
label: 'Bagagem no fim do contrato e regresso definitivo',
|
||
article: 'Lei2/2011,2(3);Decreto3/2011,6(1)',
|
||
conditions: [
|
||
[
|
||
'endContract',
|
||
'Contrato de trabalho terminado e regresso definitivo ao País',
|
||
],
|
||
[
|
||
'personalBaggage',
|
||
'Bens abrangidos pelo conceito de bagagem,identificados e sem finalidade comercial',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'return_vehicle',
|
||
label: 'Veículo/tractor no regresso,como bagagem',
|
||
article: 'Decreto3/2011,6(1)–(3)/(5)–(9)',
|
||
conditions: [
|
||
['endContract', 'Contrato terminado e regresso definitivo'],
|
||
[
|
||
'oneVehicle',
|
||
'Um veículo OU tractor agrícola e alfaias,identificado como bagagem',
|
||
],
|
||
[
|
||
'vehicleQuota',
|
||
'Direito disponível,sem nova isenção/redução antes de cinco anos do benefício anterior aplicável',
|
||
],
|
||
[
|
||
'noAlienation',
|
||
'Respeitar a restrição de alienação durante cinco anos da importação',
|
||
],
|
||
],
|
||
},
|
||
{
|
||
id: 'separate_vehicle',
|
||
label: 'Veículo separado da bagagem — redução de 50%',
|
||
article: 'Decreto3/2011,6(3)–(9)',
|
||
conditions: [
|
||
['endContract', 'Contrato terminado e regresso definitivo'],
|
||
[
|
||
'oneVehicle',
|
||
'Veículo novo ou usado separado da bagagem identificado como substituição do benefício do regresso',
|
||
],
|
||
[
|
||
'substitution',
|
||
'Tratamento excepcional de separado de bagagem concedido para este veículo/âmbito,ou pressuposto expresso da projecção;pedido não equivale a concessão',
|
||
],
|
||
[
|
||
'vehicleQuota',
|
||
'Direito disponível,sem repetir benefício de veículo antes de cinco anos',
|
||
],
|
||
[
|
||
'noAlienation',
|
||
'Respeitar restrição de alienação durante cinco anos',
|
||
],
|
||
],
|
||
},
|
||
] as const;
|
||
export const minerCommon = [
|
||
['national', 'Beneficiário originário de nacionalidade moçambicana'],
|
||
[
|
||
'rsa',
|
||
'Serviço nas minas da República da África do Sul abrangido pelos acordos de trabalho migratório',
|
||
],
|
||
[
|
||
'contract',
|
||
'Contrato regular visado pelo Ministério do Trabalho de Moçambique,com referência identificada',
|
||
],
|
||
[
|
||
'noAlcoholTobacco',
|
||
'Linha não inclui bebidas alcoólicas nem tabaco;separar esses bens,excluídos pela Lei2/2011art3',
|
||
],
|
||
] as const;
|
||
type Condition =
|
||
| (typeof minerCommon)[number][0]
|
||
| (typeof minerTreatments)[number]['conditions'][number][0]
|
||
| 'heir'
|
||
| 'unusedRight'
|
||
| 'acquiredAlive'
|
||
| 'stayException'
|
||
| 'deadlineExtension';
|
||
export type MinerRow = {
|
||
id: string;
|
||
treatment: string;
|
||
description: string;
|
||
period: string;
|
||
currency: string;
|
||
origin: 'projected' | 'effective';
|
||
beneficiary: string;
|
||
contractRef: string;
|
||
documentRef: string;
|
||
documentDate: string;
|
||
scope: string;
|
||
evidence: string;
|
||
conditions: Partial<Record<Condition, Choice>>;
|
||
rightHolder: '' | 'miner' | 'heir';
|
||
deathDate: string;
|
||
requestDate: string;
|
||
heirRef: string;
|
||
importDate: string;
|
||
route: '' | 'accompanied' | 'company' | 'declaration';
|
||
customsRef: string;
|
||
customsScope: string;
|
||
quantity: string;
|
||
kind: string;
|
||
value: string;
|
||
periodTotal: string;
|
||
quotaRef: string;
|
||
accumulationStart: string;
|
||
accumulationMonths: string;
|
||
arrivalDate: string;
|
||
stayStart: string;
|
||
stayEnd: string;
|
||
exceptionRef: string;
|
||
extensionRef: string;
|
||
previousBenefit: '' | 'none' | 'known';
|
||
previousBenefitDate: string;
|
||
normalVat: string;
|
||
normalVatRef: string;
|
||
};
|
||
type Line = {
|
||
id: string;
|
||
status: 'exempt' | 'reduced' | 'not_applicable' | 'indeterminate';
|
||
importVat: string | null;
|
||
deduction: null;
|
||
source: string;
|
||
reason: string;
|
||
};
|
||
export type MinerCalculation = {
|
||
version: string;
|
||
revision: number;
|
||
calculatedAt: string;
|
||
opportunity: string;
|
||
sourceKey: string;
|
||
source: string;
|
||
inputs: MinerRow[];
|
||
rows: Line[];
|
||
};
|
||
export type MinerBook = {
|
||
rows: MinerRow[];
|
||
result?: MinerCalculation;
|
||
history?: MinerCalculation[];
|
||
};
|
||
type Store = ReturnType<typeof readAcquisitionStore> & {
|
||
minerImports?: Record<string, MinerBook>;
|
||
};
|
||
export function newMiner(id: string, currency: string): MinerRow {
|
||
return {
|
||
id,
|
||
treatment: '',
|
||
description: '',
|
||
period: '',
|
||
currency,
|
||
origin: 'projected',
|
||
beneficiary: '',
|
||
contractRef: '',
|
||
documentRef: '',
|
||
documentDate: '',
|
||
scope: '',
|
||
evidence: '',
|
||
conditions: {},
|
||
rightHolder: '',
|
||
deathDate: '',
|
||
requestDate: '',
|
||
heirRef: '',
|
||
importDate: '',
|
||
route: '',
|
||
customsRef: '',
|
||
customsScope: '',
|
||
quantity: '',
|
||
kind: '',
|
||
value: '',
|
||
periodTotal: '',
|
||
quotaRef: '',
|
||
accumulationStart: '',
|
||
accumulationMonths: '',
|
||
arrivalDate: '',
|
||
stayStart: '',
|
||
stayEnd: '',
|
||
exceptionRef: '',
|
||
extensionRef: '',
|
||
previousBenefit: '',
|
||
previousBenefitDate: '',
|
||
normalVat: '',
|
||
normalVatRef: '',
|
||
};
|
||
}
|
||
export function minerVehicle(id: string): boolean {
|
||
return ['vehicle', 'return_vehicle', 'separate_vehicle'].includes(id);
|
||
}
|
||
export function minerReturn(id: string): boolean {
|
||
return ['return_baggage', 'return_vehicle', 'separate_vehicle'].includes(
|
||
id,
|
||
);
|
||
}
|
||
function readStore(draft: Record<string, unknown>): Store {
|
||
const s = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
const fields = Object.keys(newMiner('', '')).filter(
|
||
(k) => k !== 'conditions',
|
||
);
|
||
if (
|
||
s.minerImports !== undefined &&
|
||
(!s.minerImports ||
|
||
typeof s.minerImports !== 'object' ||
|
||
Array.isArray(s.minerImports))
|
||
)
|
||
throw new Error(
|
||
'Importações dos mineiros não recuperáveis;originais preservados.',
|
||
);
|
||
for (const b of Object.values(s.minerImports ?? {}))
|
||
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 MinerRow] !== 'string',
|
||
) ||
|
||
!r.conditions ||
|
||
typeof r.conditions !== 'object' ||
|
||
Array.isArray(r.conditions),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Linhas dos mineiros não recuperáveis;sem substituição por lista vazia.',
|
||
);
|
||
return s;
|
||
}
|
||
export function minerBook(draft: Record<string, unknown>): MinerBook {
|
||
return (
|
||
readStore(draft).minerImports?.[acquisitionScope(draft)] ?? { rows: [] }
|
||
);
|
||
}
|
||
export function saveMinerBook(
|
||
draft: Record<string, unknown>,
|
||
book: MinerBook,
|
||
): string {
|
||
const s = readStore(draft),
|
||
encoded = JSON.stringify({
|
||
...s,
|
||
minerImports: {
|
||
...s.minerImports,
|
||
[acquisitionScope(draft)]: book,
|
||
},
|
||
});
|
||
readStore({ ...draft, modelo_a_snapshot: encoded });
|
||
return encoded;
|
||
}
|
||
function date(raw: string): Date | null {
|
||
const d = new Date(raw + 'T00:00:00Z');
|
||
return /^\d{4}-\d{2}-\d{2}$/.test(raw) &&
|
||
Number.isFinite(d.getTime()) &&
|
||
d.toISOString().slice(0, 10) === raw
|
||
? d
|
||
: null;
|
||
}
|
||
function anniversary(raw: string, years: number): string {
|
||
const d = date(raw)!;
|
||
d.setUTCFullYear(d.getUTCFullYear() + years);
|
||
return d.toISOString().slice(0, 10);
|
||
}
|
||
export function minerNeedsDeadlineExtension(row: MinerRow): boolean {
|
||
const a = date(row.arrivalDate),
|
||
r = date(row.requestDate);
|
||
return (
|
||
minerReturn(row.treatment) &&
|
||
row.rightHolder === 'miner' &&
|
||
!!a &&
|
||
!!r &&
|
||
(r.getTime() - a.getTime()) / 86400000 > 90
|
||
);
|
||
}
|
||
export function minerNeedsStayException(row: MinerRow): boolean {
|
||
return (
|
||
row.treatment === 'separate_vehicle' &&
|
||
!!date(row.stayStart) &&
|
||
!!date(row.stayEnd) &&
|
||
row.stayEnd < anniversary(row.stayStart, 1)
|
||
);
|
||
}
|
||
function decimal(raw: string): bigint | null {
|
||
if (!/^\d{1,15}(\.\d{1,6})?$/.test(raw)) return null;
|
||
const [w, f = ''] = raw.split('.');
|
||
return BigInt(w) * 1000000n + BigInt(f.padEnd(6, '0'));
|
||
}
|
||
function half(raw: bigint): string {
|
||
const n = raw * 5n;
|
||
return `${n / 10000000n}.${(n % 10000000n).toString().padStart(7, '0').replace(/0+$/, '').padEnd(2, '0')}`;
|
||
}
|
||
export function evaluateMiner(row: MinerRow, currency: unknown): Line {
|
||
const rule = minerTreatments.find((t) => t.id === row.treatment),
|
||
result: Line = {
|
||
id: row.id,
|
||
status: 'indeterminate',
|
||
importVat: null,
|
||
deduction: null,
|
||
source: minerSource + (rule ? ';' + rule.article : ''),
|
||
reason: '',
|
||
};
|
||
const stop = (
|
||
reason: string,
|
||
status: Line['status'] = 'indeterminate',
|
||
) => ({ ...result, status, reason });
|
||
if (!rule)
|
||
return stop(
|
||
'Escolha percurso documentado do regimeRSA;outros países,importações temporárias ou benefício diferente permanecem por enquadrar,não são automaticamente isentos.',
|
||
);
|
||
if (
|
||
currency !== 'MZN' ||
|
||
row.currency !== currency ||
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
|
||
!date(row.importDate) ||
|
||
row.importDate.slice(0, 7) !== row.period
|
||
)
|
||
return stop(
|
||
'Indique moedaMZN e data/período coerentes da importação2026;sem conversão ou datas presumidas.',
|
||
);
|
||
if (
|
||
!['projected', 'effective'].includes(row.origin) ||
|
||
![
|
||
row.beneficiary,
|
||
row.description,
|
||
row.contractRef,
|
||
row.documentRef,
|
||
row.scope,
|
||
row.evidence,
|
||
row.customsRef,
|
||
row.customsScope,
|
||
].every((v) => !!v.trim()) ||
|
||
!date(row.documentDate)
|
||
)
|
||
return stop(
|
||
'Identifique beneficiário,bens,contrato,documento/previsão,data,âmbito e referência/pressuposto aduaneiro.Reutilize documentos,não é exigido novo certificado.',
|
||
);
|
||
const conditions = [...minerCommon, ...rule.conditions];
|
||
const failed = conditions.filter(([k]) => row.conditions[k] === 'no');
|
||
if (failed.length)
|
||
return stop(
|
||
'Não se aplica este fundamento aos factos indicados: ' +
|
||
failed.map(([, label]) => label).join('; ') +
|
||
'. Outros enquadramentos permanecem por apurar.',
|
||
'not_applicable',
|
||
);
|
||
if (conditions.some(([k]) => row.conditions[k] !== 'yes'))
|
||
return stop(
|
||
'Condições do beneficiário,bens ou percurso por esclarecer;desconhecido não é IVA zero.',
|
||
);
|
||
const route = [
|
||
'appliance',
|
||
'vehicle',
|
||
'return_vehicle',
|
||
'separate_vehicle',
|
||
].includes(row.treatment)
|
||
? 'declaration'
|
||
: row.treatment === 'accumulated'
|
||
? 'company'
|
||
: row.route;
|
||
if (
|
||
!['accompanied', 'company', 'declaration'].includes(route) ||
|
||
(['monthly', 'accumulated', 'baggage', 'return_baggage'].includes(
|
||
row.treatment,
|
||
) &&
|
||
route === 'declaration')
|
||
)
|
||
return stop(
|
||
'Identifique via acompanhada ou empresa nos bens/bagagem;electrodomésticos e veículos seguem declaração aduaneira. Não exigir procedimentoKAWENA a outras vias.',
|
||
);
|
||
if (!['miner', 'heir'].includes(row.rightHolder))
|
||
return stop(
|
||
'Indique titular originário ou transmissão do direito a herdeiro legal.',
|
||
);
|
||
if (row.rightHolder === 'heir') {
|
||
if (
|
||
['heir', 'unusedRight', 'acquiredAlive'].some(
|
||
(k) => row.conditions[k as Condition] === 'no',
|
||
)
|
||
)
|
||
return stop(
|
||
'Transmissão a herdeiro não demonstrada:apenas direito não usado e bens adquiridos em vida pelo mineiro.',
|
||
'not_applicable',
|
||
);
|
||
if (
|
||
['heir', 'unusedRight', 'acquiredAlive'].some(
|
||
(k) => row.conditions[k as Condition] !== 'yes',
|
||
) ||
|
||
!row.heirRef.trim() ||
|
||
!date(row.deathDate) ||
|
||
!date(row.requestDate)
|
||
)
|
||
return stop(
|
||
'Identifique qualidade de herdeiro,direito não usado,bens adquiridos em vida,óbito e requerimento;sem conceder direito sucessório automaticamente.',
|
||
);
|
||
if (
|
||
row.requestDate < row.deathDate ||
|
||
row.requestDate > anniversary(row.deathDate, 1)
|
||
)
|
||
return stop(
|
||
'Requerimento do herdeiro fora do período de um ano contado do óbito,ou anterior ao mesmo;este fundamento não se aplica.',
|
||
'not_applicable',
|
||
);
|
||
}
|
||
if (['monthly', 'accumulated'].includes(row.treatment)) {
|
||
const amount = decimal(row.value),
|
||
total = decimal(row.periodTotal);
|
||
if (
|
||
amount === null ||
|
||
total === null ||
|
||
amount > total ||
|
||
!row.quotaRef.trim()
|
||
)
|
||
return stop(
|
||
'Indique valor desta remessa e total do âmbito mensal/acumulado,incluindo outras remessas e esta,com registo ou pressuposto expresso;vazio não significa zero.',
|
||
);
|
||
let months = 1;
|
||
if (row.treatment === 'accumulated') {
|
||
months = Number(row.accumulationMonths);
|
||
if (
|
||
!/^[1-6]$/.test(row.accumulationMonths) ||
|
||
!/^\d{4}-(0[1-9]|1[0-2])$/.test(row.accumulationStart)
|
||
)
|
||
return stop(
|
||
'Indique início e duração de um a seis meses acumulados;sem acumular indefinidamente.',
|
||
);
|
||
const start =
|
||
Number(row.accumulationStart.slice(0, 4)) * 12 +
|
||
Number(row.accumulationStart.slice(5)),
|
||
end =
|
||
Number(row.period.slice(0, 4)) * 12 +
|
||
Number(row.period.slice(5));
|
||
if (end - start + 1 !== months)
|
||
return stop(
|
||
'Meses indicados não correspondem ao início/fim do saldo acumulado;conserve origem e reveja a janela,sem reutilização automática.',
|
||
);
|
||
}
|
||
if (total > 15000000000n * BigInt(months))
|
||
return stop(
|
||
'Total excede a cobertura de15.000MZN por mês prevista nos exemplaresLei2/2011/Decreto3/2011.Não se concede benefício ao excesso nem se presume revisão do limite ao abrigo2(5);conferir disposição específica se invocada.',
|
||
);
|
||
}
|
||
if (
|
||
row.treatment === 'appliance' &&
|
||
(!row.kind.trim() || row.quantity !== '1' || !row.quotaRef.trim())
|
||
)
|
||
return stop(
|
||
'Identifique tipo de electrodoméstico,uma unidade e registo/pressuposto do direito anual,incluindo operações externas ao dossier.',
|
||
);
|
||
if (minerVehicle(row.treatment)) {
|
||
if (
|
||
!['vehicle', 'tractor'].includes(row.kind) ||
|
||
row.quantity !== '1' ||
|
||
!row.quotaRef.trim() ||
|
||
!['none', 'known'].includes(row.previousBenefit)
|
||
)
|
||
return stop(
|
||
'Identifique veículo ou tractor/alfaias,uma unidade e histórico do direito abrangido,incluindo benefícios fora do dossier;desconhecido não é ausência de benefício.',
|
||
);
|
||
if (row.previousBenefit === 'known') {
|
||
if (!date(row.previousBenefitDate))
|
||
return stop(
|
||
'Indique data do benefício anterior aplicável ao direito identificado.',
|
||
);
|
||
if (row.importDate < anniversary(row.previousBenefitDate, 5))
|
||
return stop(
|
||
'Ainda não decorreram cinco anos desde o benefício anterior aplicável indicado;sem nova isenção ou redução neste direito.',
|
||
'not_applicable',
|
||
);
|
||
}
|
||
}
|
||
if (minerReturn(row.treatment)) {
|
||
const arrival = date(row.arrivalDate),
|
||
request = date(row.requestDate);
|
||
if (!arrival || !request || row.requestDate < row.arrivalDate)
|
||
return stop(
|
||
'Indique chegada e requerimento do benefício no regresso;pedido não equivale a decisão.',
|
||
);
|
||
if (minerNeedsDeadlineExtension(row)) {
|
||
const max = new Date(arrival),
|
||
day = max.getUTCDate();
|
||
max.setUTCDate(1);
|
||
max.setUTCMonth(max.getUTCMonth() + 6);
|
||
const end = new Date(max);
|
||
end.setUTCMonth(end.getUTCMonth() + 1);
|
||
end.setUTCDate(0);
|
||
max.setUTCDate(Math.min(day, end.getUTCDate()));
|
||
if (
|
||
row.conditions.deadlineExtension !== 'yes' ||
|
||
!row.extensionRef.trim() ||
|
||
request > max
|
||
)
|
||
return stop(
|
||
'Prazo normal de90dias ultrapassado;identifique prorrogação excepcional justificada até seis meses,não presumida pelo simulador.',
|
||
);
|
||
}
|
||
if (minerVehicle(row.treatment)) {
|
||
if (
|
||
!date(row.stayStart) ||
|
||
!date(row.stayEnd) ||
|
||
row.stayEnd < row.stayStart ||
|
||
row.stayEnd > row.arrivalDate
|
||
)
|
||
return stop(
|
||
'Indique estadia naRSA para o benefício de veículo no regresso,coerente com a data da chegada.',
|
||
);
|
||
if (
|
||
row.stayEnd < anniversary(row.stayStart, 1) &&
|
||
!(
|
||
row.treatment === 'separate_vehicle' &&
|
||
row.conditions.stayException === 'yes' &&
|
||
row.exceptionRef.trim()
|
||
)
|
||
)
|
||
return stop(
|
||
'Estadia inferior a um ano;excepção6(7) só no separado de bagagem com autorização/âmbito identificados ou pressuposto expresso,não automática.',
|
||
);
|
||
}
|
||
}
|
||
const moment =
|
||
route === 'company'
|
||
? 'saída do armazém de regime para entrega às famílias'
|
||
: route === 'accompanied'
|
||
? 'entrada no território na estância aduaneira'
|
||
: 'desembaraço mediante Documento Único';
|
||
const context = `${rule.article};${row.origin === 'projected' ? 'pressupostos expressos de projecção' : 'factos efectivos declarados'}. Momento/procedimento8:${moment}. Sem despacho ou DU emitido pelo portal,direitos,base aduaneira ou dedução/campo11.`;
|
||
if (row.treatment === 'separate_vehicle') {
|
||
const normal = decimal(row.normalVat);
|
||
if (normal === null || !row.normalVatRef.trim())
|
||
return stop(
|
||
'Indique IVA normal desta importação e referência/pressuposto para aplicar redução proporcional de50%;sem presumir base ou taxa normal.',
|
||
);
|
||
return {
|
||
...result,
|
||
status: 'reduced',
|
||
importVat: half(normal),
|
||
reason: `Redução50% das imposições no cenário específico de veículo separado da bagagem6(4),não taxa deIVA nem benefício geral. IVA informado${row.normalVat}×50%=${half(normal)}MZN,aritmética exacta sem arredondamento fiscal presumido.${context}`,
|
||
};
|
||
}
|
||
return {
|
||
...result,
|
||
status: 'exempt',
|
||
importVat: '0.00',
|
||
reason: `Classificação de isenção no âmbito documentado de direitos/imposições,IVA12(2)b na proporção do benefício integral indicado. IVA importação:0.00MZN;sem concessão administrativa automática.${context}`,
|
||
};
|
||
}
|
||
function key(draft: Record<string, unknown>, b: MinerBook): string {
|
||
return JSON.stringify({
|
||
version: minerVersion,
|
||
scope: acquisitionScope(draft),
|
||
currency: draft.currency,
|
||
location: draft.selection_labels ?? {},
|
||
rows: b.rows,
|
||
});
|
||
}
|
||
export function minerCurrent(
|
||
draft: Record<string, unknown>,
|
||
b: MinerBook,
|
||
): boolean {
|
||
return !!b.result && b.result.sourceKey === key(draft, b);
|
||
}
|
||
export function recordMiners(
|
||
draft: Record<string, unknown>,
|
||
book: MinerBook,
|
||
): MinerBook {
|
||
if (
|
||
!book.rows.length ||
|
||
new Set(book.rows.map((r) => r.id)).size !== book.rows.length
|
||
)
|
||
throw new Error('Identifique linhas distintas;originais preservados.');
|
||
const rows = book.rows.map((r) => evaluateMiner(r, draft.currency));
|
||
// Declarative quotas cover external operations too; detect contradictions inside this saved scope.
|
||
book.rows.forEach((row, i) => {
|
||
if (!['exempt', 'reduced'].includes(rows[i].status)) return;
|
||
const peers = book.rows.filter(
|
||
(r) =>
|
||
r.beneficiary === row.beneficiary &&
|
||
r.origin === row.origin &&
|
||
evaluateMiner(r, draft.currency).status !== 'not_applicable',
|
||
);
|
||
let conflict = false;
|
||
if (row.treatment === 'appliance')
|
||
conflict =
|
||
peers.filter(
|
||
(r) =>
|
||
r.treatment === 'appliance' &&
|
||
r.kind === row.kind &&
|
||
r.period.slice(0, 4) === row.period.slice(0, 4),
|
||
).length > 1;
|
||
if (minerVehicle(row.treatment))
|
||
conflict =
|
||
peers.filter(
|
||
(r) =>
|
||
minerVehicle(r.treatment) &&
|
||
r.kind === row.kind &&
|
||
date(r.importDate) &&
|
||
(r.importDate <= row.importDate
|
||
? row.importDate < anniversary(r.importDate, 5)
|
||
: r.importDate < anniversary(row.importDate, 5)),
|
||
).length > 1;
|
||
if (['monthly', 'accumulated'].includes(row.treatment)) {
|
||
const start =
|
||
row.treatment === 'accumulated'
|
||
? row.accumulationStart
|
||
: row.period;
|
||
const relevant = peers.filter(
|
||
(r) =>
|
||
['monthly', 'accumulated'].includes(r.treatment) &&
|
||
(r.treatment === 'accumulated'
|
||
? r.accumulationStart
|
||
: r.period) <= row.period &&
|
||
r.period >= start,
|
||
);
|
||
if (
|
||
relevant.some(
|
||
(r) =>
|
||
r.treatment === 'accumulated' ||
|
||
row.treatment === 'accumulated',
|
||
) &&
|
||
relevant.length > 1
|
||
)
|
||
conflict = true;
|
||
if (relevant.some((r) => decimal(r.value) === null))
|
||
conflict = true;
|
||
const sum = relevant.reduce(
|
||
(s, r) => s + (decimal(r.value) ?? 0n),
|
||
0n,
|
||
),
|
||
total = decimal(row.periodTotal);
|
||
if (total !== null && sum > total) conflict = true;
|
||
}
|
||
if (conflict)
|
||
rows[i] = {
|
||
...rows[i],
|
||
status: 'indeterminate',
|
||
importVat: null,
|
||
reason: 'Linhas guardadas sobrepõem ou excedem o mesmo direito/período/saldo declarado;reveja o conjunto e o registo externo,sem dupla utilização nem exclusão silenciosa.',
|
||
};
|
||
});
|
||
const result: MinerCalculation = {
|
||
version: minerVersion,
|
||
revision: (book.result?.revision ?? 0) + 1,
|
||
calculatedAt: new Date().toISOString(),
|
||
opportunity: acquisitionScope(draft),
|
||
sourceKey: key(draft, book),
|
||
source: minerSource,
|
||
inputs: structuredClone(book.rows),
|
||
rows,
|
||
};
|
||
const next = {
|
||
...book,
|
||
result,
|
||
history: [
|
||
...(book.history ?? []),
|
||
...(book.result ? [book.result] : []),
|
||
],
|
||
};
|
||
saveMinerBook(draft, next);
|
||
return next;
|
||
}
|
||
export function minerReportRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
try {
|
||
const b = minerBook(draft),
|
||
r = b.result;
|
||
if (!r) return [];
|
||
return r.inputs.map((row, i) => [
|
||
'IVA mensal',
|
||
`P059 — ${row.period} — ${row.id}`,
|
||
`${row.description};${r.rows[i].reason};IVA:${r.rows[i].importVat ?? 'Indeterminado'};dedução:Não apurada`,
|
||
`${r.opportunity};${minerCurrent(draft, b) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${row.origin};beneficiário${row.beneficiary};contrato${row.contractRef};documento${row.documentRef},${row.documentDate};âmbito${row.scope};factos${row.evidence};condições${JSON.stringify(Object.fromEntries([...minerCommon, ...(minerTreatments.find((t) => t.id === row.treatment)?.conditions ?? [])].map(([k]) => [k, row.conditions[k] ?? 'Por esclarecer'])))};importação${row.importDate};aduaneiro${row.customsRef},${row.customsScope};titular${row.rightHolder}${row.rightHolder === 'heir' ? `;herança${row.heirRef};óbito${row.deathDate};pedido${row.requestDate}` : ''}${['monthly', 'accumulated'].includes(row.treatment) ? `;remessa${row.value};total${row.periodTotal};registo${row.quotaRef}` : ''}${row.treatment === 'accumulated' ? `;janela${row.accumulationStart}/${row.accumulationMonths}meses` : ''}${row.treatment === 'appliance' || minerVehicle(row.treatment) ? `;tipo${row.kind};quantidade${row.quantity};histórico${row.quotaRef}` : ''}${minerVehicle(row.treatment) ? `;benefício anterior${row.previousBenefit},${row.previousBenefit === 'known' ? row.previousBenefitDate : 'nenhum declarado'}` : ''}${minerReturn(row.treatment) ? `;chegada${row.arrivalDate};pedido${row.requestDate}${minerNeedsDeadlineExtension(row) ? `;prorrogação${row.extensionRef || 'Por identificar'}` : ''}` : ''}${row.treatment === 'return_vehicle' || row.treatment === 'separate_vehicle' ? `;estadia${row.stayStart}/${row.stayEnd}${minerNeedsStayException(row) ? `;excepção${row.exceptionRef || 'Por identificar'}` : ''}` : ''}${row.treatment === 'separate_vehicle' ? `;IVA normal${row.normalVat};referência${row.normalVatRef}` : ''};${r.rows[i].source};sem lançamento automático nos totaisModeloA/campo11.`,
|
||
]);
|
||
} catch {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'P059',
|
||
'Dados não recuperáveis',
|
||
'Originais preservados;sem substituição porzero.',
|
||
],
|
||
];
|
||
}
|
||
}
|