356 lines
13 KiB
TypeScript
356 lines
13 KiB
TypeScript
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
|
import {
|
|
operationTreatments,
|
|
newSalesOperation,
|
|
evaluateSalesOperation,
|
|
salesBook,
|
|
type SalesOperation,
|
|
} from './iva-operations.ts';
|
|
export const objectiveImportVersion = 'objective-imports/1';
|
|
export const objectiveImportSource =
|
|
'CIVA12(1)a,Lei10/2025,BR248-I/suplemento,PDF12/p2180(12),efeitos01/01/2026:importações definitivas de bens cuja transmissão interna beneficie de isenção objectiva;enumeração exemplificativa. Correspondências9(1)b/c/e/f/g,9(9)b,9(10),9(11),9(12)g/h/i;catálogo interno e anexosLei22/2022 já conferidos. Sem dedução/campo11,direitos ou alteração dos livros internos.';
|
|
const fixed = [
|
|
'health_assistive',
|
|
'human_materials',
|
|
'mosquito_nets',
|
|
'medicines',
|
|
'pharma_manufacturing_inputs',
|
|
'cultural_publications',
|
|
'official_military_supplies',
|
|
'industrial_feed_inputs',
|
|
'annexI_goods',
|
|
'veterinary_goods',
|
|
];
|
|
export const objectiveImportTreatments = operationTreatments.filter(
|
|
(t) => fixed.includes(t.id) || t.id.startsWith('listed_'),
|
|
);
|
|
type Choice = '' | 'yes' | 'no';
|
|
export type ObjectiveImportRow = {
|
|
id: string;
|
|
description: string;
|
|
period: string;
|
|
currency: string;
|
|
origin: 'projected' | 'effective';
|
|
importer: string;
|
|
documentRef: string;
|
|
documentDate: string;
|
|
scope: string;
|
|
evidence: string;
|
|
definitive: Choice;
|
|
goods: Choice;
|
|
sameGoods: Choice;
|
|
internalApplicable: Choice;
|
|
reference: SalesOperation;
|
|
reusedFrom: string;
|
|
};
|
|
type Line = {
|
|
id: string;
|
|
status: 'exempt' | 'not_applicable' | 'indeterminate';
|
|
importVat: string | null;
|
|
deduction: null;
|
|
source: string;
|
|
reason: string;
|
|
};
|
|
export type ObjectiveImportCalculation = {
|
|
version: string;
|
|
revision: number;
|
|
calculatedAt: string;
|
|
opportunity: string;
|
|
sourceKey: string;
|
|
source: string;
|
|
inputs: ObjectiveImportRow[];
|
|
rows: Line[];
|
|
};
|
|
export type ObjectiveImportBook = {
|
|
rows: ObjectiveImportRow[];
|
|
result?: ObjectiveImportCalculation;
|
|
history?: ObjectiveImportCalculation[];
|
|
};
|
|
type Store = ReturnType<typeof readAcquisitionStore> & {
|
|
objectiveImports?: Record<string, ObjectiveImportBook>;
|
|
};
|
|
export function newObjectiveImport(
|
|
id: string,
|
|
currency: string,
|
|
): ObjectiveImportRow {
|
|
return {
|
|
id,
|
|
description: '',
|
|
period: '',
|
|
currency,
|
|
origin: 'projected',
|
|
importer: '',
|
|
documentRef: '',
|
|
documentDate: '',
|
|
scope: '',
|
|
evidence: '',
|
|
definitive: '',
|
|
goods: '',
|
|
sameGoods: '',
|
|
internalApplicable: '',
|
|
reference: newSalesOperation(id + '-reference', '', currency),
|
|
reusedFrom: '',
|
|
};
|
|
}
|
|
export function objectiveImportSources(
|
|
draft: Record<string, unknown>,
|
|
): SalesOperation[] {
|
|
return salesBook(draft).rows.filter((r) =>
|
|
objectiveImportTreatments.some((t) => t.id === r.treatment),
|
|
);
|
|
}
|
|
export function reuseObjectiveImport(
|
|
row: ObjectiveImportRow,
|
|
source: SalesOperation,
|
|
): ObjectiveImportRow {
|
|
const reference = { ...row.reference };
|
|
// This explicit action fills missing values only; confirmations belong to this import.
|
|
if (!reference.treatment || reference.treatment === 'other')
|
|
reference.treatment = source.treatment;
|
|
if (reference.treatment === source.treatment) {
|
|
reference.annexIItem ||= source.annexIItem;
|
|
reference.pharmaAnnexItem ||= source.pharmaAnnexItem;
|
|
reference.annexIOnly ??= source.annexIOnly;
|
|
}
|
|
return {
|
|
...row,
|
|
description: row.description || source.description,
|
|
period: row.period || source.period,
|
|
evidence: row.evidence || source.evidence,
|
|
reference,
|
|
reusedFrom: source.id,
|
|
};
|
|
}
|
|
function readStore(draft: Record<string, unknown>): Store {
|
|
const s = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
|
const books = s.objectiveImports;
|
|
if (
|
|
books !== undefined &&
|
|
(!books || typeof books !== 'object' || Array.isArray(books))
|
|
)
|
|
throw new Error(
|
|
'Importações objectivas não recuperáveis;originais preservados.',
|
|
);
|
|
const fields = Object.keys(newObjectiveImport('', 'MZN')).filter(
|
|
(k) => k !== 'reference',
|
|
);
|
|
for (const b of Object.values(books ?? {}))
|
|
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 ObjectiveImportRow] !==
|
|
'string',
|
|
) ||
|
|
!r.reference ||
|
|
typeof r.reference.treatment !== 'string' ||
|
|
!Array.isArray(r.reference.confirmed),
|
|
)
|
|
)
|
|
throw new Error(
|
|
'Linhas de importação objectiva não recuperáveis;sem substituição por lista vazia.',
|
|
);
|
|
return s;
|
|
}
|
|
export function objectiveImportBook(
|
|
draft: Record<string, unknown>,
|
|
): ObjectiveImportBook {
|
|
return (
|
|
readStore(draft).objectiveImports?.[acquisitionScope(draft)] ?? {
|
|
rows: [],
|
|
}
|
|
);
|
|
}
|
|
export function saveObjectiveImportBook(
|
|
draft: Record<string, unknown>,
|
|
book: ObjectiveImportBook,
|
|
): string {
|
|
const s = readStore(draft),
|
|
encoded = JSON.stringify({
|
|
...s,
|
|
objectiveImports: {
|
|
...s.objectiveImports,
|
|
[acquisitionScope(draft)]: book,
|
|
},
|
|
});
|
|
readStore({ ...draft, modelo_a_snapshot: encoded });
|
|
return encoded;
|
|
}
|
|
function date(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 evaluateObjectiveImport(
|
|
row: ObjectiveImportRow,
|
|
currency: unknown,
|
|
): Line {
|
|
const rule = objectiveImportTreatments.find(
|
|
(t) => t.id === row.reference.treatment,
|
|
),
|
|
result: Line = {
|
|
id: row.id,
|
|
status: 'indeterminate',
|
|
importVat: null,
|
|
deduction: null,
|
|
source: objectiveImportSource + (rule ? ';' + rule.source : ''),
|
|
reason: '',
|
|
};
|
|
const stop = (
|
|
reason: string,
|
|
status: Line['status'] = 'indeterminate',
|
|
) => ({ ...result, status, reason });
|
|
if (
|
|
currency !== 'MZN' ||
|
|
row.currency !== currency ||
|
|
!/^2026-(0[1-9]|1[0-2])$/.test(row.period)
|
|
)
|
|
return stop(
|
|
'Indique período2026 e moedaMZN coerente com o projecto;sem conversão presumida.',
|
|
);
|
|
if (
|
|
!['projected', 'effective'].includes(row.origin) ||
|
|
![
|
|
row.description,
|
|
row.importer,
|
|
row.documentRef,
|
|
row.scope,
|
|
row.evidence,
|
|
].every((v) => !!v.trim()) ||
|
|
!date(row.documentDate)
|
|
)
|
|
return stop(
|
|
'Identifique bens,importador,documento/previsão,data,âmbito e factos/pressupostos.Reutilize os elementos disponíveis.',
|
|
);
|
|
if (
|
|
[row.definitive, row.goods, row.sameGoods, row.internalApplicable].some(
|
|
(v) => v === 'no',
|
|
)
|
|
)
|
|
return stop(
|
|
'Este fundamento12(1)a não se aplica aos factos indicados;IVA fora deste fundamento permanece por enquadrar,sem taxa presumida.',
|
|
'not_applicable',
|
|
);
|
|
if (
|
|
[row.definitive, row.goods, row.sameGoods, row.internalApplicable].some(
|
|
(v) => v !== 'yes',
|
|
)
|
|
)
|
|
return stop(
|
|
'Confirme importação definitiva de bens,a correspondência dos mesmos bens e a aplicabilidade da isenção objectiva interna.Desconhecido não significa IVA zero.',
|
|
);
|
|
if (!rule)
|
|
return stop(
|
|
'Escolha correspondência interna documentada.A lista implementada cobre as remissões expressas12(1)a;é exemplificativa e não declara excluídos outros fundamentos.Não basta descrição,isenção de serviços,exportação,taxa reduzida ou benefício terminado.',
|
|
);
|
|
// Evaluate the legal hypothetical domestic transmission, never create a domestic sale or use its amount/totals.
|
|
const reference = {
|
|
...row.reference,
|
|
id: row.id + '-reference',
|
|
description: row.description,
|
|
period: row.period,
|
|
sourcePeriod: row.period,
|
|
sourceCurrency: row.currency,
|
|
sourceAmount: null,
|
|
establishment: row.importer,
|
|
amount: '0',
|
|
basis: 'net' as const,
|
|
evidence: row.evidence,
|
|
};
|
|
const v = evaluateSalesOperation(reference, row.currency);
|
|
result.source = objectiveImportSource + ';' + v.source;
|
|
if (v.status !== 'exempt')
|
|
return stop(
|
|
'Correspondência interna ainda não demonstrada: ' + v.reason,
|
|
);
|
|
return {
|
|
...result,
|
|
status: 'exempt',
|
|
importVat: '0.00',
|
|
reason: `Isenção12(1)a por correspondência a${rule.article},nas condições ${row.origin === 'projected' ? 'expressamente assumidas da projecção' : 'efectivas declaradas'}. IVA na importação:0.00MZN. Hipótese de transmissão interna,não venda criada nem montante somado.Sem direitos,base aduaneira,dedução/campo11 ou concessão administrativa automática.`,
|
|
};
|
|
}
|
|
function key(
|
|
draft: Record<string, unknown>,
|
|
book: ObjectiveImportBook,
|
|
): string {
|
|
return JSON.stringify({
|
|
version: objectiveImportVersion,
|
|
catalog: objectiveImportTreatments,
|
|
scope: acquisitionScope(draft),
|
|
currency: draft.currency,
|
|
location: draft.selection_labels ?? {},
|
|
rows: book.rows,
|
|
});
|
|
}
|
|
export function objectiveImportCurrent(
|
|
draft: Record<string, unknown>,
|
|
book: ObjectiveImportBook,
|
|
): boolean {
|
|
return !!book.result && book.result.sourceKey === key(draft, book);
|
|
}
|
|
export function recordObjectiveImports(
|
|
draft: Record<string, unknown>,
|
|
book: ObjectiveImportBook,
|
|
): ObjectiveImportBook {
|
|
if (
|
|
!book.rows.length ||
|
|
new Set(book.rows.map((r) => r.id)).size !== book.rows.length
|
|
)
|
|
throw new Error(
|
|
'Indique linhas com identidades distintas;originais conservados.',
|
|
);
|
|
const result: ObjectiveImportCalculation = {
|
|
version: objectiveImportVersion,
|
|
revision: (book.result?.revision ?? 0) + 1,
|
|
calculatedAt: new Date().toISOString(),
|
|
opportunity: acquisitionScope(draft),
|
|
sourceKey: key(draft, book),
|
|
source: objectiveImportSource,
|
|
inputs: structuredClone(book.rows),
|
|
rows: book.rows.map((r) => evaluateObjectiveImport(r, draft.currency)),
|
|
};
|
|
const next = {
|
|
...book,
|
|
result,
|
|
history: [
|
|
...(book.history ?? []),
|
|
...(book.result ? [book.result] : []),
|
|
],
|
|
};
|
|
saveObjectiveImportBook(draft, next);
|
|
return next;
|
|
}
|
|
export function objectiveImportReportRows(
|
|
draft: Record<string, unknown>,
|
|
): Array<[string, string, string, string]> {
|
|
try {
|
|
const b = objectiveImportBook(draft),
|
|
r = b.result;
|
|
if (!r) return [];
|
|
return r.inputs.map((row, i) => [
|
|
'IVA mensal',
|
|
`P046 — ${row.period} — ${row.id}`,
|
|
`${row.description};${r.rows[i].reason};IVA importação:${r.rows[i].importVat ?? 'Indeterminado'};dedução:Não apurada`,
|
|
`${r.opportunity};${objectiveImportCurrent(draft, b) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${row.origin};importador${row.importer};documento${row.documentRef},${row.documentDate};âmbito${row.scope};factos${row.evidence};reutilização${row.reusedFrom || 'Introdução directa'};correspondência${row.reference.treatment};condições internas confirmadas${row.reference.confirmed.join(',')}${row.reference.treatment === 'annexI_goods' ? `;AnexoI${row.reference.annexIItem ?? ''},apenasAnexoI${String(row.reference.annexIOnly ?? 'Por esclarecer')}` : row.reference.treatment === 'pharma_manufacturing_inputs' ? `;AnexoIII${row.reference.pharmaAnnexItem ?? ''}` : ''};${r.rows[i].source};sem alteração dos livros internos,ModeloA,campo11 ou totais.`,
|
|
]);
|
|
} catch {
|
|
return [
|
|
[
|
|
'IVA mensal',
|
|
'P046',
|
|
'Dados não recuperáveis',
|
|
'Originais preservados;sem classificação por defeito.',
|
|
],
|
|
];
|
|
}
|
|
}
|