1347 lines
52 KiB
TypeScript
1347 lines
52 KiB
TypeScript
import { inputText } from './input-text.ts';
|
||
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
|
||
import {
|
||
monthlyIvaInput,
|
||
monthlyIvaKey,
|
||
readMonthlyIvaRecord,
|
||
readMonthlyIvaSettings,
|
||
} from './monthly-iva.ts';
|
||
import { restoreBusinessPlanSnapshot } from './business-financial-plan.ts';
|
||
import {
|
||
salesBook,
|
||
salesCurrent as salesOperationsCurrent,
|
||
} from './iva-operations.ts';
|
||
import {
|
||
evaluateAcquisitionAllocation,
|
||
validAcquisitionAllocation,
|
||
allocationSourceKey,
|
||
allocationHasUnconsolidatedOutputs,
|
||
acquisitionAllocationVersion,
|
||
acquisitionAllocationSource,
|
||
type AcquisitionAllocation,
|
||
} from './iva-acquisition-allocation.ts';
|
||
import {
|
||
evaluateAcquisitionBC,
|
||
validAcquisitionBC,
|
||
acquisitionBCSource,
|
||
acquisitionBCSourceKey,
|
||
acquisitionBCDetails,
|
||
expenseOptions,
|
||
type AcquisitionBC,
|
||
} from './iva-acquisition-bc.ts';
|
||
import {
|
||
linkedOperationRight,
|
||
validAcquisitionRightLink,
|
||
acquisitionRightVersion,
|
||
acquisitionRightSource,
|
||
type AcquisitionRightLink,
|
||
type AcquisitionEligibility,
|
||
} from './iva-acquisition-right.ts';
|
||
import {
|
||
acquisitionTreatmentIssue,
|
||
acquisitionContinuityVersion,
|
||
formerExplorationSource,
|
||
type AcquisitionTreatment,
|
||
} from './iva-acquisition-continuity.ts';
|
||
import {
|
||
addFractions,
|
||
decimalFraction,
|
||
fractionLabel,
|
||
fractionPresentation,
|
||
positiveFraction,
|
||
subtractFractions,
|
||
type FiscalFraction,
|
||
} from './fiscal-rational.ts';
|
||
import {
|
||
regularizationFields,
|
||
regularizationSourceKey,
|
||
regularizationReportRows,
|
||
frozenAssetRows,
|
||
validateRegularizationStore,
|
||
} from './iva-regularizations.ts';
|
||
|
||
import { beverageParcelReserved } from './iva-beverage-parcels.ts';
|
||
export const acquisitionVersion = 'modelo-a-internal-acquisitions/1';
|
||
export const acquisitionBasis =
|
||
'CIVA arts.18(1)(a),18(2) (Lei10/2025),18(3–5),19(1)(a),19(2),20 (Lei22/2022),21(1–3); âmbito interno, afectação exclusiva, mesmo período, sem exclusões ou dedução parcial.';
|
||
export const acquisitionSources =
|
||
'Modelo A DOCX 62c198e9 / v1 63d45fa8; VAT Return.pdf SHA256 9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741, p.1; CIVA republicado Lei13/2016, PDF14–16; Lei22/2022, art.20; Lei10/2025, arts.18/19, PDF13.';
|
||
type Choice = '' | 'yes' | 'no';
|
||
export type Acquisition = {
|
||
id: string;
|
||
field: '08' | '09' | '10' | '11';
|
||
period: string;
|
||
origin: 'projected' | 'effective';
|
||
sourceMonth: number | null;
|
||
sourcePeriod?: string;
|
||
sourceField?: 'inventory' | 'asset' | 'operating';
|
||
sourceAmount: string | null;
|
||
sourceCurrency: string;
|
||
sourceBasis: '' | 'net' | 'gross';
|
||
net: string;
|
||
vat: string;
|
||
gross: string;
|
||
documentType: '' | 'invoice' | 'equivalent' | 'customs';
|
||
documentRef: string;
|
||
documentDate: string;
|
||
documentLine: string;
|
||
supplier: string;
|
||
supplierRegistered: Choice;
|
||
legalDocument: Choice;
|
||
held: Choice;
|
||
domestic: Choice;
|
||
allocation: '' | 'taxable_only' | 'mixed' | 'no_right';
|
||
exclusion: '' | 'none' | 'excluded' | 'special';
|
||
integrity: Choice;
|
||
description: string;
|
||
acquisitionTreatment?: AcquisitionTreatment;
|
||
deductionLink?: AcquisitionRightLink;
|
||
bc?: AcquisitionBC;
|
||
d?: AcquisitionAllocation;
|
||
investmentAsset?: boolean;
|
||
acquisitionTreatmentOrigin?: {
|
||
id: 'former_exploration_exemption';
|
||
source: string;
|
||
};
|
||
};
|
||
export type AcquisitionResult = {
|
||
id: string;
|
||
net: string | null;
|
||
vat: string | null;
|
||
gross: string | null;
|
||
deductible: string | null;
|
||
reason: string;
|
||
eligibility?: AcquisitionEligibility;
|
||
deductibleExact?: FiscalFraction;
|
||
nonDeductibleExact?: FiscalFraction;
|
||
allocationCalculation?: {
|
||
version: string;
|
||
ratio: FiscalFraction;
|
||
rawRatio: FiscalFraction;
|
||
before: FiscalFraction;
|
||
source: string;
|
||
};
|
||
};
|
||
type PeriodInput = {
|
||
period: string;
|
||
complete: boolean;
|
||
absent: string[];
|
||
importsComplete?: boolean;
|
||
};
|
||
export type AcquisitionBook = {
|
||
currency: string;
|
||
rightVersion?: string;
|
||
taxpayerRegistered?: Choice;
|
||
rows: Acquisition[];
|
||
periods: PeriodInput[];
|
||
result?: AcquisitionCalculation;
|
||
history?: AcquisitionCalculation[];
|
||
};
|
||
export type AcquisitionCalculation = {
|
||
version: string;
|
||
revision: number;
|
||
calculatedAt: string;
|
||
opportunity: string;
|
||
location: string;
|
||
currency: string;
|
||
sourceKey: string;
|
||
basis: string;
|
||
sources: string;
|
||
declarations?: { taxpayerRegistered: Choice; periods: PeriodInput[] };
|
||
rows: AcquisitionResult[];
|
||
inputs: Acquisition[];
|
||
periods: Array<{
|
||
period: string;
|
||
fields: Record<string, string | null>;
|
||
exactFields?: Record<string, FiscalFraction | null>;
|
||
reasons: string[];
|
||
}>;
|
||
};
|
||
type Store = { version: 1; books: Record<string, AcquisitionBook> };
|
||
const excluded = ['03', '04', '05', '06', '07', '11', '12', '13'];
|
||
export const acquisitionExcludedFields = excluded;
|
||
export function acquisitionScope(draft: Record<string, unknown>): string {
|
||
if (!draft.context_opportunity) {
|
||
try {
|
||
const context = JSON.parse(
|
||
inputText(draft.context_applicability_snapshot ?? ''),
|
||
);
|
||
if (context.opportunity_choices?.length > 1)
|
||
throw new Error(
|
||
'Escolha a oportunidade em tratamento antes de preparar as compras.',
|
||
);
|
||
} catch (cause) {
|
||
if (cause instanceof Error && cause.message.startsWith('Escolha'))
|
||
throw cause;
|
||
}
|
||
}
|
||
return inputText(draft.context_opportunity || 'general');
|
||
}
|
||
export function readAcquisitionStore(raw: unknown): Store {
|
||
if (!raw) return { version: 1, books: {} };
|
||
const value = JSON.parse(inputText(raw));
|
||
if (
|
||
value?.version !== 1 ||
|
||
!value.books ||
|
||
typeof value.books !== 'object' ||
|
||
Array.isArray(value.books)
|
||
)
|
||
throw new Error(
|
||
'Compras fiscais guardadas não recuperáveis; dados preservados.',
|
||
);
|
||
validateRegularizationStore(value);
|
||
for (const book of Object.values(value.books) as AcquisitionBook[]) {
|
||
if (
|
||
book?.rows?.some(
|
||
(row) =>
|
||
row.investmentAsset !== undefined &&
|
||
typeof row.investmentAsset !== 'boolean',
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Classificação do activo não recuperável;originais preservados.',
|
||
);
|
||
if (
|
||
book?.rows?.some(
|
||
(row) =>
|
||
row.d !== undefined && !validAcquisitionAllocation(row.d),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'AfectaçãoD não recuperável;originais preservados.',
|
||
);
|
||
if (
|
||
book?.rows?.some(
|
||
(row) => row.bc !== undefined && !validAcquisitionBC(row.bc),
|
||
) ||
|
||
book?.periods?.some(
|
||
(p) =>
|
||
p.importsComplete !== undefined &&
|
||
typeof p.importsComplete !== 'boolean',
|
||
)
|
||
)
|
||
throw new Error(
|
||
'CondiçõesB/C ou cobertura de importações inválidas;originais preservados.',
|
||
);
|
||
if (
|
||
book?.rightVersion !== undefined &&
|
||
book.rightVersion !== acquisitionRightVersion
|
||
)
|
||
throw new Error(
|
||
'Versão do direito da aquisição não recuperável; original preservado.',
|
||
);
|
||
if (
|
||
book?.rows?.some(
|
||
(row) =>
|
||
row.deductionLink !== undefined &&
|
||
!validAcquisitionRightLink(row.deductionLink),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Vínculo da aquisição não recuperável; original preservado.',
|
||
);
|
||
if (book?.history !== undefined && !Array.isArray(book.history))
|
||
throw new Error(
|
||
'Histórico de aquisições não recuperável; originais preservados, sem substituição.',
|
||
);
|
||
if (
|
||
!book ||
|
||
!Array.isArray(book.rows) ||
|
||
!Array.isArray(book.periods) ||
|
||
typeof book.currency !== 'string'
|
||
)
|
||
throw new Error(
|
||
'Formato de compras não recuperável; sem substituição.',
|
||
);
|
||
const stringKeys = Object.keys(newAcquisition('', '', '')).filter(
|
||
(key) => !['sourceMonth', 'sourceAmount'].includes(key),
|
||
);
|
||
if (
|
||
book.rows.some(
|
||
(row) =>
|
||
!row ||
|
||
stringKeys.some(
|
||
(key) =>
|
||
typeof row[key as keyof Acquisition] !== 'string',
|
||
) ||
|
||
!(
|
||
row.sourceMonth === null ||
|
||
Number.isInteger(row.sourceMonth)
|
||
) ||
|
||
!(
|
||
row.sourceAmount === null ||
|
||
typeof row.sourceAmount === 'string'
|
||
),
|
||
) ||
|
||
book.periods.some(
|
||
(p) =>
|
||
!p ||
|
||
typeof p.period !== 'string' ||
|
||
typeof p.complete !== 'boolean' ||
|
||
!Array.isArray(p.absent) ||
|
||
p.absent.some((f) => typeof f !== 'string'),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Linhas ou períodos de compras não recuperáveis; os dados guardados foram preservados.',
|
||
);
|
||
}
|
||
return value;
|
||
}
|
||
export function acquisitionBook(
|
||
draft: Record<string, unknown>,
|
||
): AcquisitionBook {
|
||
return (
|
||
readAcquisitionStore(draft.modelo_a_snapshot).books[
|
||
acquisitionScope(draft)
|
||
] ?? {
|
||
currency: inputText(draft.currency ?? ''),
|
||
rows: [],
|
||
periods: [],
|
||
}
|
||
);
|
||
}
|
||
export function saveAcquisitionBook(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
): string {
|
||
const store = readAcquisitionStore(draft.modelo_a_snapshot);
|
||
return JSON.stringify({
|
||
...store,
|
||
books: { ...store.books, [acquisitionScope(draft)]: book },
|
||
});
|
||
}
|
||
export function newAcquisition(
|
||
id: string,
|
||
period: string,
|
||
currency: string,
|
||
): Acquisition {
|
||
return {
|
||
id,
|
||
field: '09',
|
||
period,
|
||
origin: 'projected',
|
||
sourceMonth: null,
|
||
sourceAmount: null,
|
||
sourceCurrency: currency,
|
||
sourceBasis: '',
|
||
net: '',
|
||
vat: '',
|
||
gross: '',
|
||
documentType: '',
|
||
documentRef: '',
|
||
documentDate: '',
|
||
documentLine: '',
|
||
supplier: '',
|
||
supplierRegistered: '',
|
||
legalDocument: '',
|
||
held: '',
|
||
domestic: '',
|
||
allocation: '',
|
||
exclusion: '',
|
||
integrity: '',
|
||
description: '',
|
||
};
|
||
}
|
||
function periods(draft: Record<string, unknown>): string[] {
|
||
try {
|
||
return monthlyIvaInput(
|
||
draft,
|
||
readMonthlyIvaSettings(inputText(draft.monthly_iva_settings ?? '')),
|
||
).monthly_sales.map((r) => r.date.slice(0, 7));
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
export function acquisitionPlanReferences(
|
||
draft: Record<string, unknown>,
|
||
): Array<{
|
||
key: string;
|
||
kind: 'inventory' | 'asset' | 'operating';
|
||
month: number;
|
||
period: string;
|
||
amount: string | null;
|
||
currency: string;
|
||
label: string;
|
||
}> {
|
||
const dates = periods(draft),
|
||
plan = restoreBusinessPlanSnapshot(
|
||
inputText(draft.business_plan_snapshot ?? ''),
|
||
);
|
||
if (!plan || !dates.length) return [];
|
||
if (plan.currency !== draft.currency)
|
||
throw new Error(
|
||
'Moeda do plano divergente; não reutilizar como moeda do projecto.',
|
||
);
|
||
const inventory = modeloAPurchaseReferences(draft).map((r) => ({
|
||
key: `inventory:${r.month}`,
|
||
kind: 'inventory' as const,
|
||
month: r.month,
|
||
period: dates[r.month - 1],
|
||
amount: r.declared,
|
||
currency: plan.currency,
|
||
label: `Compras/encomendas — mês ${r.month}`,
|
||
}));
|
||
const asset = {
|
||
key: 'asset:1',
|
||
kind: 'asset' as const,
|
||
month: 1,
|
||
period: dates[0],
|
||
amount: plan.assetInvestment?.trim() || null,
|
||
currency: plan.currency,
|
||
label: 'Orçamento de investimento em activos — período a confirmar',
|
||
};
|
||
const operating = dates.map((period, index) => ({
|
||
key: `operating:${index + 1}`,
|
||
kind: 'operating' as const,
|
||
month: index + 1,
|
||
period,
|
||
amount:
|
||
(plan.monthlyOperatingExpensesByPeriod
|
||
? plan.monthlyOperatingExpensesByPeriod[index]
|
||
: plan.monthlyOperatingExpenses
|
||
)?.trim() || null,
|
||
currency: plan.currency,
|
||
label: `Orçamento de despesas operacionais — mês ${index + 1}`,
|
||
}));
|
||
return [...inventory, asset, ...operating];
|
||
}
|
||
export function reuseAcquisitions(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
): AcquisitionBook {
|
||
const dates = periods(draft);
|
||
if (!dates.length)
|
||
throw new Error(
|
||
'Defina o período mensal e resolva a moeda da projecção antes de reutilizar compras.',
|
||
);
|
||
const rows = [...book.rows];
|
||
for (const reference of modeloAPurchaseReferences(draft)) {
|
||
if (rows.some((r) => r.sourceMonth === reference.month)) continue;
|
||
rows.push({
|
||
...newAcquisition(
|
||
`plan-${reference.month}`,
|
||
dates[reference.month - 1],
|
||
inputText(draft.currency ?? ''),
|
||
),
|
||
sourceMonth: reference.month,
|
||
sourcePeriod: dates[reference.month - 1],
|
||
sourceAmount: reference.declared,
|
||
description: `Compras/encomendas do plano — mês ${reference.month}`,
|
||
});
|
||
}
|
||
return {
|
||
...book,
|
||
rows,
|
||
periods: dates.map(
|
||
(period) =>
|
||
book.periods.find((p) => p.period === period) ?? {
|
||
period,
|
||
complete: false,
|
||
absent: [],
|
||
},
|
||
),
|
||
};
|
||
}
|
||
// Decimal arithmetic for aggregation only. No binary rounding and no invented purchase tax rate.
|
||
function amount(raw: unknown): bigint | null {
|
||
if (typeof raw !== 'string' || !/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim()))
|
||
return null;
|
||
const [a, b = ''] = raw.trim().split('.');
|
||
return BigInt(a) * 1000000n + BigInt(b.padEnd(6, '0'));
|
||
}
|
||
function money(value: bigint): string {
|
||
const a = value / 1000000n,
|
||
b = (value % 1000000n)
|
||
.toString()
|
||
.padStart(6, '0')
|
||
.replace(/0+$/, '')
|
||
.padEnd(2, '0');
|
||
return `${a}.${b}`;
|
||
}
|
||
function sum(values: Array<string | null>): string | null {
|
||
const parsed = values.map(amount);
|
||
return parsed.some((v) => v === null)
|
||
? null
|
||
: money((parsed as bigint[]).reduce((a, b) => a + b, 0n));
|
||
}
|
||
function dateValid(raw: string): boolean {
|
||
const date = new Date(`${raw}T00:00:00Z`);
|
||
return (
|
||
/^\d{4}-\d{2}-\d{2}$/.test(raw) &&
|
||
Number.isFinite(date.getTime()) &&
|
||
date.toISOString().slice(0, 10) === raw
|
||
);
|
||
}
|
||
function evaluateAcquisitionBase(
|
||
row: Acquisition,
|
||
currency: string,
|
||
taxpayerRegistered: Choice = '',
|
||
): AcquisitionResult {
|
||
let n = amount(row.net),
|
||
v = amount(row.vat),
|
||
g = amount(row.gross);
|
||
if (n !== null && v !== null && g === null && !row.gross.trim()) g = n + v;
|
||
if (g !== null && v !== null && n === null && !row.net.trim() && g >= v)
|
||
n = g - v;
|
||
if (n !== null && g !== null && v === null && !row.vat.trim() && g >= n)
|
||
v = g - n;
|
||
const result: AcquisitionResult = {
|
||
id: row.id,
|
||
net: n === null ? null : money(n),
|
||
vat: v === null ? null : money(v),
|
||
gross: g === null ? null : money(g),
|
||
deductible: null,
|
||
reason: '',
|
||
};
|
||
const stop = (reason: string, deductible: string | null = null) => ({
|
||
...result,
|
||
reason,
|
||
deductible,
|
||
});
|
||
if (currency !== 'MZN' || row.sourceCurrency !== currency)
|
||
return stop('Divergência ou moeda fora de MZN; sem conversão.');
|
||
if (!/^2026-(0[1-9]|1[0-2])$/.test(row.period))
|
||
return stop('Período fora do âmbito 2026 ou em falta.');
|
||
const treatmentIssue = acquisitionTreatmentIssue(row.acquisitionTreatment);
|
||
if (treatmentIssue) return stop(treatmentIssue);
|
||
if (n === null || v === null || g === null || n + v !== g)
|
||
return stop(
|
||
'Indique dois valores válidos; líquido + IVA deve corresponder ao bruto.',
|
||
);
|
||
if (
|
||
row.sourceMonth !== null &&
|
||
(!row.sourceBasis ||
|
||
amount(row.sourceAmount) !== (row.sourceBasis === 'net' ? n : g))
|
||
)
|
||
return stop(
|
||
'Classifique ou resolva a divergência do valor de origem; não foi reinterpretado.',
|
||
);
|
||
if (v === 0n && !row.deductionLink)
|
||
return stop(
|
||
'IVA explicitamente indicado como zero; não existe imposto a deduzir nesta aquisição.',
|
||
'0.00',
|
||
);
|
||
if (taxpayerRegistered !== 'yes')
|
||
return stop(
|
||
'Confirme o enquadramento do adquirente como sujeito passivo de IVA no regime normal; não é inferido da organização.',
|
||
);
|
||
if (
|
||
row.allocation === 'no_right' ||
|
||
row.exclusion === 'excluded' ||
|
||
row.supplierRegistered === 'no' ||
|
||
row.legalDocument === 'no' ||
|
||
row.integrity === 'no'
|
||
)
|
||
return stop(
|
||
'Sem dedução no âmbito declarado: afectação, exclusão, fornecedor ou documento não elegível.',
|
||
'0.00',
|
||
);
|
||
if (row.domestic !== 'yes')
|
||
return stop(
|
||
'Aquisição interna por confirmar; importações fora deste lote.',
|
||
);
|
||
if (row.allocation !== 'taxable_only' || row.exclusion !== 'none')
|
||
return stop(
|
||
'Confirme afectação exclusiva a operações tributadas e ausência de exclusões; casos mistos/especiais não calculados.',
|
||
);
|
||
if (
|
||
!['invoice', 'equivalent'].includes(row.documentType) ||
|
||
!row.documentRef.trim() ||
|
||
!row.supplier.trim() ||
|
||
row.supplierRegistered !== 'yes' ||
|
||
row.legalDocument !== 'yes' ||
|
||
row.held !== 'yes' ||
|
||
row.integrity !== 'yes'
|
||
)
|
||
return stop(
|
||
'Documento, fornecedor inscrito, posse e condições de dedução ainda incompletos.',
|
||
);
|
||
if (
|
||
!dateValid(row.documentDate) ||
|
||
row.documentDate.slice(0, 7) !== row.period
|
||
)
|
||
return stop(
|
||
'Data do documento e período de dedução devem coincidir neste lote; dedução posterior não presumida.',
|
||
);
|
||
return stop(
|
||
row.origin === 'projected'
|
||
? 'Dedução projectada sob pressupostos explícitos; não certifica factura ou declaração.'
|
||
: 'Dedução segundo dados e condições declarados; sem submissão ou certificação pela AT.',
|
||
money(v),
|
||
);
|
||
}
|
||
export function evaluateAcquisition(
|
||
row: Acquisition,
|
||
currency: string,
|
||
taxpayerRegistered: Choice = '',
|
||
draft?: Record<string, unknown>,
|
||
): AcquisitionResult {
|
||
const result = evaluateAcquisitionBase(row, currency, taxpayerRegistered);
|
||
if (row.d)
|
||
return evaluateAcquisitionAllocation(
|
||
row,
|
||
currency,
|
||
taxpayerRegistered,
|
||
draft,
|
||
result,
|
||
);
|
||
if (row.bc)
|
||
return evaluateAcquisitionBC(
|
||
row,
|
||
currency,
|
||
taxpayerRegistered,
|
||
draft,
|
||
result,
|
||
);
|
||
if (!row.deductionLink) return result;
|
||
const right = draft
|
||
? linkedOperationRight(draft, row.deductionLink)
|
||
: {
|
||
status: 'indeterminate' as const,
|
||
reason: 'Contexto da operação em falta.',
|
||
source: acquisitionRightSource,
|
||
};
|
||
const finish = (
|
||
status: AcquisitionEligibility['status'],
|
||
reason: string,
|
||
deductible: string | null = null,
|
||
): AcquisitionResult => ({
|
||
...result,
|
||
deductible,
|
||
reason,
|
||
eligibility: {
|
||
...right,
|
||
status,
|
||
reason,
|
||
source: right.source + '; ' + acquisitionBasis,
|
||
},
|
||
});
|
||
if (
|
||
currency !== 'MZN' ||
|
||
row.sourceCurrency !== currency ||
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
|
||
acquisitionTreatmentIssue(row.acquisitionTreatment) ||
|
||
result.net === null ||
|
||
result.vat === null ||
|
||
result.gross === null ||
|
||
amount(result.net)! + amount(result.vat)! !== amount(result.gross)
|
||
)
|
||
return finish('indeterminate', result.reason);
|
||
if (
|
||
row.deductionLink.scopeConfirmed !== 'yes' ||
|
||
!row.deductionLink.allocationRef.trim()
|
||
)
|
||
return finish(
|
||
'indeterminate',
|
||
'Confirme a afectação exclusiva à operação ligada e indique o motivo/referência; não inferida da oportunidade.',
|
||
);
|
||
if (
|
||
row.domestic !== 'yes' ||
|
||
row.allocation === 'mixed' ||
|
||
row.exclusion === 'special'
|
||
)
|
||
return finish(
|
||
'indeterminate',
|
||
'Importação, afectação parcial ou despesa especial: enquadramento específico B/C/D ainda necessário; sem dedução integral presumida.',
|
||
);
|
||
if (right.status === 'indeterminate')
|
||
return finish('indeterminate', right.reason);
|
||
if (right.status === 'no_right')
|
||
return finish('no_right', right.reason, '0.00');
|
||
if (
|
||
row.allocation === 'no_right' ||
|
||
row.exclusion === 'excluded' ||
|
||
row.supplierRegistered === 'no' ||
|
||
row.legalDocument === 'no' ||
|
||
row.integrity === 'no'
|
||
)
|
||
return finish('no_right', result.reason, '0.00');
|
||
if (row.deductionLink.noExcludedPayments === 'no')
|
||
return finish(
|
||
'no_right',
|
||
'Afectação a pagamentos do artigo15(6)(c): exclusão19(2).',
|
||
'0.00',
|
||
);
|
||
if (row.deductionLink.noExcludedPayments !== 'yes')
|
||
return finish(
|
||
'indeterminate',
|
||
'Indique se a aquisição está fora dos pagamentos excluídos15(6)(c)/19(2).',
|
||
);
|
||
if (result.deductible === null)
|
||
return finish('indeterminate', result.reason);
|
||
return finish(
|
||
'eligible',
|
||
right.reason + ' ' + result.reason,
|
||
result.deductible,
|
||
);
|
||
}
|
||
function sourceKey(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
): string {
|
||
const raw = JSON.stringify([
|
||
acquisitionScope(draft),
|
||
draft.currency ?? '',
|
||
draft.province_id ?? '',
|
||
draft.district_id ?? '',
|
||
draft.business_plan_snapshot,
|
||
draft.monthly_iva_settings,
|
||
draft.monthly_iva_snapshot,
|
||
book.currency,
|
||
book.taxpayerRegistered,
|
||
book.rows,
|
||
book.periods,
|
||
]);
|
||
// Change detection only, never an authorization or authenticity token.
|
||
let hash = 14695981039346656037n;
|
||
for (let i = 0; i < raw.length; i++)
|
||
hash = BigInt.asUintN(
|
||
64,
|
||
(hash ^ BigInt(raw.charCodeAt(i))) * 1099511628211n,
|
||
);
|
||
const operations = (
|
||
readAcquisitionStore(draft.modelo_a_snapshot) as Store & {
|
||
salesOperations?: Record<string, unknown>;
|
||
}
|
||
).salesOperations?.[acquisitionScope(draft)];
|
||
const e = regularizationSourceKey(draft);
|
||
return `${raw.length}:${hash.toString(16)}${operations ? `:${JSON.stringify(operations)}` : ''}${book.rows.some((r) => r.acquisitionTreatment || r.acquisitionTreatmentOrigin) ? `:${acquisitionContinuityVersion}` : ''}${book.rightVersion || book.rows.some((r) => r.deductionLink) ? `:${acquisitionRightVersion}` : ''}${book.rows.some((r) => r.bc) ? `:${acquisitionBCSourceKey(draft, book.rows)}` : ''}${book.rows.some((r) => r.d) ? `:${acquisitionAllocationVersion}:${allocationSourceKey(draft, book.rows)}` : ''}${e ? `:E:${e}` : ''}`;
|
||
}
|
||
function location(draft: Record<string, unknown>): string {
|
||
const labels = draft.selection_labels as
|
||
| Record<string, unknown>
|
||
| undefined;
|
||
return ['province_id', 'district_id']
|
||
.filter((key) => draft[key])
|
||
.map((key) =>
|
||
String(draft[`${key}_label`] ?? labels?.[key] ?? draft[key]),
|
||
)
|
||
.join(' / ');
|
||
}
|
||
export function acquisitionCurrent(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
): boolean {
|
||
return (
|
||
!!book.result &&
|
||
book.result.version === acquisitionVersion &&
|
||
book.result.opportunity === acquisitionScope(draft) &&
|
||
book.result.sourceKey === sourceKey(draft, book)
|
||
);
|
||
}
|
||
export function calculateAcquisitions(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
now = new Date().toISOString(),
|
||
): AcquisitionCalculation {
|
||
const scope = acquisitionScope(draft),
|
||
dates = book.periods.map((p) => p.period);
|
||
if (book.currency !== 'MZN' || draft.currency !== book.currency)
|
||
throw new Error(
|
||
'Resolva a moeda de origem das compras; valores preservados sem conversão.',
|
||
);
|
||
if (
|
||
!dates.length ||
|
||
dates.some((period) => !/^2026-(0[1-9]|1[0-2])$/.test(period)) ||
|
||
new Set(dates).size !== dates.length
|
||
)
|
||
throw new Error(
|
||
'Indique os meses aplicáveis às aquisições em2026,sem períodos duplicados; não inferidos do orçamento ou das vendas.',
|
||
);
|
||
if (new Set(book.rows.map((r) => r.id)).size !== book.rows.length)
|
||
throw new Error('Existem compras com identidade repetida.');
|
||
const documents = book.rows
|
||
.filter((r) => r.documentRef.trim() && r.supplier.trim())
|
||
.map((r) =>
|
||
JSON.stringify([
|
||
r.origin,
|
||
r.supplier.trim().toLowerCase(),
|
||
r.documentRef.trim().toLowerCase(),
|
||
r.documentDate,
|
||
r.documentLine?.trim() ?? '',
|
||
]),
|
||
);
|
||
if (new Set(documents).size !== documents.length)
|
||
throw new Error(
|
||
'Documento/linha repetido: discrimine linhas distintas ou remova a duplicação antes de deduzir.',
|
||
);
|
||
if (
|
||
book.rows.some(
|
||
(r) =>
|
||
!['08', '09', '10', '11'].includes(r.field) ||
|
||
(r.field === '11' && !r.bc) ||
|
||
!dates.includes(r.period),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Classifique cada aquisição num campo08–10 ou importaçãoB no campo11 e num mês incluído na cobertura declarada.',
|
||
);
|
||
const references = acquisitionPlanReferences(draft);
|
||
for (const row of book.rows.filter((r) => r.sourceMonth !== null)) {
|
||
const current = references.find(
|
||
(p) =>
|
||
p.month === row.sourceMonth &&
|
||
p.kind === (row.sourceField ?? 'inventory'),
|
||
);
|
||
if (
|
||
!current ||
|
||
current.amount !== row.sourceAmount ||
|
||
(current.kind !== 'asset' &&
|
||
row.sourcePeriod &&
|
||
current.period !== row.sourcePeriod)
|
||
)
|
||
throw new Error(
|
||
'As compras ou orçamentos do plano mudaram. Resolva a divergência de origem antes de calcular; histórico preservado.',
|
||
);
|
||
}
|
||
if (book.rightVersion || book.rows.some((r) => r.deductionLink || r.bc)) {
|
||
const identities = book.rows
|
||
.filter(
|
||
(r) =>
|
||
r.documentRef.trim() &&
|
||
(r.supplier.trim() || r.field === '11'),
|
||
)
|
||
.map((r) =>
|
||
JSON.stringify([
|
||
r.field === '11'
|
||
? 'customs'
|
||
: r.supplier.trim().toLowerCase(),
|
||
r.documentRef.trim().toLowerCase(),
|
||
r.documentDate,
|
||
r.documentLine.trim(),
|
||
]),
|
||
);
|
||
if (new Set(identities).size !== identities.length)
|
||
throw new Error(
|
||
'Documento/linha repetido entre projecção e efectivo; sem dupla dedução.',
|
||
);
|
||
}
|
||
const rows = book.rows.map((row) => {
|
||
const result = evaluateAcquisition(
|
||
row,
|
||
book.currency,
|
||
book.taxpayerRegistered,
|
||
draft,
|
||
);
|
||
if (
|
||
result.deductible !== null &&
|
||
(result.deductibleExact
|
||
? BigInt(result.deductibleExact.numerator) > 0n
|
||
: Number(result.deductible) > 0) &&
|
||
beverageParcelReserved(draft, row)
|
||
)
|
||
return {
|
||
...result,
|
||
deductible: null,
|
||
deductibleExact: undefined,
|
||
nonDeductibleExact: undefined,
|
||
reason: 'Parcela ligada a pedido/decisão/recebimento de restituição13(2);sem a voltar a deduzir. Reconciliar o procedimento conservando o histórico.',
|
||
eligibility: {
|
||
status: 'indeterminate' as const,
|
||
reason: 'Parcela reservada para restituição13(2).',
|
||
source: acquisitionBCSource,
|
||
},
|
||
};
|
||
if (
|
||
!row.deductionLink &&
|
||
!row.d &&
|
||
(book.rightVersion || book.rows.some((r) => r.deductionLink))
|
||
)
|
||
return {
|
||
...result,
|
||
deductible: null,
|
||
reason: 'Aquisição sem vínculo à operação de destino; direito indeterminado.',
|
||
eligibility: {
|
||
status: 'indeterminate' as const,
|
||
reason: 'Aquisição sem vínculo à operação de destino; direito indeterminado.',
|
||
source: acquisitionRightSource,
|
||
},
|
||
};
|
||
return result;
|
||
});
|
||
const record = readMonthlyIvaRecord(
|
||
inputText(draft.monthly_iva_snapshot ?? ''),
|
||
);
|
||
let salesCurrent = false;
|
||
try {
|
||
salesCurrent =
|
||
!!record &&
|
||
record.execution.status === 'completed' &&
|
||
monthlyIvaKey(record.input) ===
|
||
monthlyIvaKey(
|
||
monthlyIvaInput(
|
||
draft,
|
||
readMonthlyIvaSettings(
|
||
inputText(draft.monthly_iva_settings ?? ''),
|
||
),
|
||
),
|
||
);
|
||
} catch {
|
||
/* Preserve history, never fabricate 01/02. */
|
||
}
|
||
const projection = record?.execution.result_snapshot?.monthly_projection as
|
||
| {
|
||
rows?: Array<{
|
||
date: string;
|
||
net_sales: string | null;
|
||
vat: string | null;
|
||
}>;
|
||
}
|
||
| undefined;
|
||
const calculated = dates.map((period) => {
|
||
const input = book.periods.find((p) => p.period === period),
|
||
reasons: string[] = [],
|
||
fields: Record<string, string | null> = {};
|
||
const declaredAmount = (key: string) => {
|
||
const value = amount(inputText(draft[key] ?? ''));
|
||
return value !== null && value > 0n;
|
||
};
|
||
if (
|
||
input?.absent.includes('11') &&
|
||
(declaredAmount('imports') ||
|
||
book.rows.some(
|
||
(r) => r.period === period && r.domestic === 'no',
|
||
))
|
||
)
|
||
throw new Error(
|
||
'Existem importações declaradas. Não confirme ausência no campo11; enquadramento fora deste lote.',
|
||
);
|
||
if (input?.absent.includes('07') && declaredAmount('sales_exempt'))
|
||
throw new Error(
|
||
'Existem vendas isentas declaradas. Resolva o período e a classificação antes de confirmar ausência no campo07.',
|
||
);
|
||
if (
|
||
input?.absent.includes('05') &&
|
||
(declaredAmount('exports') || declaredAmount('sales_zero_rate'))
|
||
)
|
||
throw new Error(
|
||
'Existem exportações ou vendas de outro enquadramento. Não confirme ausência no campo05 sem resolver a classificação.',
|
||
);
|
||
const sales = salesCurrent
|
||
? projection?.rows?.find((r) => r.date.slice(0, 7) === period)
|
||
: null;
|
||
fields['01'] = sales?.net_sales ?? null;
|
||
fields['02'] = sales?.vat ?? null;
|
||
if (
|
||
(!sales || sales.net_sales === null || sales.vat === null) &&
|
||
!(
|
||
salesOperationsCurrent(draft, salesBook(draft)) &&
|
||
salesBook(draft).result?.periods.some(
|
||
(p) =>
|
||
p.period === period &&
|
||
p.fields['01'] != null &&
|
||
p.fields['02'] != null,
|
||
)
|
||
)
|
||
)
|
||
reasons.push(
|
||
'01/02: vendas ou IVA mensal em falta, execução incompleta ou desactualizada.',
|
||
);
|
||
for (const field of excluded)
|
||
fields[field] = input?.absent.includes(field) ? '0.00' : null;
|
||
const operations = salesBook(draft);
|
||
if (operations.rows.length || operations.result) {
|
||
const classified = salesOperationsCurrent(draft, operations)
|
||
? operations.result?.periods.find((p) => p.period === period)
|
||
: null;
|
||
for (const field of ['01', '02', '03', '04', '05', '06', '07'])
|
||
fields[field] = classified?.fields[field] ?? null;
|
||
if (!classified)
|
||
reasons.push(
|
||
'Operações L1 alteradas ou sem resultado correspondente: recalcule a classificação; não usar01/02 antigos nem ausência presumida.',
|
||
);
|
||
else reasons.push(...classified.reasons);
|
||
}
|
||
for (const field of ['08', '09', '10']) {
|
||
const matching = book.rows.filter(
|
||
(r) => r.period === period && r.field === field,
|
||
);
|
||
fields[field] = input?.complete
|
||
? sum(
|
||
matching.map(
|
||
(r) => rows.find((x) => x.id === r.id)!.deductible,
|
||
),
|
||
)
|
||
: null;
|
||
if (
|
||
(operations.rows.length || operations.result) &&
|
||
operations.rows.some(
|
||
(r) =>
|
||
r.period === period &&
|
||
!['general', 'export_goods'].includes(r.treatment),
|
||
) &&
|
||
matching.some(
|
||
(r) =>
|
||
!r.deductionLink &&
|
||
!r.d &&
|
||
rows.find((x) => x.id === r.id)!.deductible !== '0.00',
|
||
)
|
||
) {
|
||
fields[field] = null;
|
||
reasons.push(
|
||
`Campo${field}: operações reduzidas/isentas/outras exigem afectar cada aquisição; não reutilizar dedução exclusiva geral como se fosse universal (L2).`,
|
||
);
|
||
}
|
||
}
|
||
if (
|
||
book.rows.some((r) => r.bc) ||
|
||
input?.importsComplete !== undefined
|
||
) {
|
||
const imported = book.rows.filter(
|
||
(r) => r.period === period && r.field === '11',
|
||
);
|
||
if (imported.length || input?.importsComplete)
|
||
fields['11'] = input?.importsComplete
|
||
? sum(
|
||
imported.map(
|
||
(r) =>
|
||
rows.find((x) => x.id === r.id)!.deductible,
|
||
),
|
||
)
|
||
: null;
|
||
if (imported.length && !input?.importsComplete)
|
||
reasons.push(
|
||
'Campo11:confirme a cobertura de todas as importações do período,distinta das compras08–10.',
|
||
);
|
||
}
|
||
if (!input?.complete)
|
||
reasons.push(
|
||
'Aquisições08–10: confirme que todas as compras do período foram incluídas, mesmo quando não existam.',
|
||
);
|
||
for (const row of book.rows.filter((r) => r.period === period)) {
|
||
const r = rows.find((x) => x.id === row.id)!;
|
||
if (r.deductible === null)
|
||
reasons.push(`${row.description || row.id}: ${r.reason}`);
|
||
}
|
||
const adjustments = regularizationFields(draft, period, book.rows);
|
||
if (adjustments) {
|
||
if (input?.absent.some((f) => f === '12' || f === '13'))
|
||
throw Error(
|
||
'Existem regularizaçõesE neste período. Retire a declaração de ausência12/13 e confirme a coberturaE,sem apagar o histórico.',
|
||
);
|
||
for (const f of ['12', '13'])
|
||
fields[f] = adjustments.fields[f]
|
||
? fractionPresentation(adjustments.fields[f]!).decimal
|
||
: null;
|
||
reasons.push(...adjustments.reasons);
|
||
}
|
||
for (const field of excluded)
|
||
if (fields[field] === null)
|
||
reasons.push(
|
||
`Campo${field}: sem apuramento correspondente ou declaração expressa de inexistência de operações.`,
|
||
);
|
||
fields['14'] = sum(
|
||
['01', '03', '05', '06', '07'].map((f) => fields[f]),
|
||
);
|
||
fields['15'] = sum(
|
||
['08', '09', '10', '11', '12'].map((f) => fields[f]),
|
||
);
|
||
fields['16'] = sum(['02', '04', '13'].map((f) => fields[f]));
|
||
const state = amount(fields['16']),
|
||
taxpayer = amount(fields['15']);
|
||
fields['17'] =
|
||
state === null || taxpayer === null
|
||
? null
|
||
: money(state > taxpayer ? state - taxpayer : 0n);
|
||
fields['18'] =
|
||
state === null || taxpayer === null
|
||
? null
|
||
: money(taxpayer > state ? taxpayer - state : 0n);
|
||
const classified = salesOperationsCurrent(draft, operations)
|
||
? operations.result?.periods.find((p) => p.period === period)
|
||
: null;
|
||
if (
|
||
classified?.exactFields ||
|
||
book.rows.some((r) => r.bc || r.d) ||
|
||
adjustments
|
||
) {
|
||
const exactFields: Record<string, FiscalFraction | null> = {};
|
||
for (const f of [
|
||
'01',
|
||
'02',
|
||
'03',
|
||
'04',
|
||
'05',
|
||
'06',
|
||
'07',
|
||
'08',
|
||
'09',
|
||
'10',
|
||
'11',
|
||
'12',
|
||
'13',
|
||
])
|
||
exactFields[f] =
|
||
classified?.exactFields?.[f] ??
|
||
(fields[f] === null ? null : decimalFraction(fields[f]!));
|
||
for (const f of ['08', '09', '10', '11'])
|
||
if (fields[f] !== null) {
|
||
const matching = book.rows
|
||
.filter((r) => r.period === period && r.field === f)
|
||
.map((r) => rows.find((v) => v.id === r.id)!);
|
||
exactFields[f] = addFractions(
|
||
matching.map(
|
||
(r) =>
|
||
r.deductibleExact ??
|
||
decimalFraction(r.deductible!),
|
||
),
|
||
);
|
||
}
|
||
if (adjustments)
|
||
for (const f of ['12', '13'])
|
||
exactFields[f] = adjustments.fields[f];
|
||
for (const [f, parts] of [
|
||
['14', ['01', '03', '05', '06', '07']],
|
||
['15', ['08', '09', '10', '11', '12']],
|
||
['16', ['02', '04', '13']],
|
||
] as const)
|
||
exactFields[f] = parts.some((k) => exactFields[k] === null)
|
||
? null
|
||
: addFractions(parts.map((k) => exactFields[k]!));
|
||
exactFields['17'] =
|
||
exactFields['16'] && exactFields['15']
|
||
? positiveFraction(
|
||
subtractFractions(
|
||
exactFields['16'],
|
||
exactFields['15'],
|
||
),
|
||
)
|
||
: null;
|
||
exactFields['18'] =
|
||
exactFields['16'] && exactFields['15']
|
||
? positiveFraction(
|
||
subtractFractions(
|
||
exactFields['15'],
|
||
exactFields['16'],
|
||
),
|
||
)
|
||
: null;
|
||
if (
|
||
(book.rows.some((r) => r.d) || adjustments) &&
|
||
allocationHasUnconsolidatedOutputs(draft, period)
|
||
) {
|
||
for (const f of ['14', '16', '17', '18']) exactFields[f] = null;
|
||
reasons.push(
|
||
'Origem18(6) identifica base/proporção,sem IVA liquidado consolidado no livro de operações. Não presumir impostozero nem duplicar a base:totais14/16/17/18 permanecem indeterminados;dedução15 conserva apuramento próprio.',
|
||
);
|
||
}
|
||
for (const f of Object.keys(exactFields))
|
||
fields[f] = exactFields[f]
|
||
? fractionPresentation(exactFields[f]!).decimal
|
||
: null;
|
||
reasons.push(
|
||
'Totais derivados das fracções exactas, sem somar aproximações das linhas; decimais a6casas são apresentação técnica, não arredondamento declarativo.',
|
||
);
|
||
return { period, fields, exactFields, reasons };
|
||
}
|
||
return { period, fields, reasons };
|
||
});
|
||
return {
|
||
version: acquisitionVersion,
|
||
revision: (book.result?.revision ?? 0) + 1,
|
||
calculatedAt: now,
|
||
opportunity: scope,
|
||
location: location(draft),
|
||
currency: book.currency,
|
||
sourceKey: sourceKey(draft, book),
|
||
basis: book.rows.some((r) => r.d)
|
||
? acquisitionAllocationSource
|
||
: book.rows.some((r) => r.bc)
|
||
? acquisitionBCSource
|
||
: acquisitionBasis,
|
||
sources:
|
||
acquisitionSources +
|
||
(book.rows.some(
|
||
(r) =>
|
||
r.acquisitionTreatment === 'former_exploration_exemption' ||
|
||
r.acquisitionTreatmentOrigin,
|
||
)
|
||
? `; ${formerExplorationSource}`
|
||
: ''),
|
||
declarations: {
|
||
taxpayerRegistered: book.taxpayerRegistered ?? '',
|
||
periods: structuredClone(book.periods),
|
||
},
|
||
rows,
|
||
inputs: structuredClone(book.rows),
|
||
periods: calculated,
|
||
};
|
||
}
|
||
export function recordAcquisitions(
|
||
draft: Record<string, unknown>,
|
||
book: AcquisitionBook,
|
||
now = new Date().toISOString(),
|
||
): AcquisitionBook {
|
||
const result = calculateAcquisitions(draft, book, now);
|
||
return {
|
||
...book,
|
||
...(book.result
|
||
? {
|
||
history: [
|
||
...(book.history ?? []),
|
||
structuredClone(book.result),
|
||
],
|
||
}
|
||
: {}),
|
||
result,
|
||
};
|
||
}
|
||
export function acquisitionReportRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
if (!draft.modelo_a_snapshot) return [];
|
||
try {
|
||
const book = acquisitionBook(draft),
|
||
result = book.result;
|
||
if (!result)
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'Compras — Modelo A',
|
||
'Sem cálculo guardado para a oportunidade escolhida',
|
||
'Sem dedução presumida',
|
||
],
|
||
];
|
||
const context = `${result.currency}; oportunidade ${result.opportunity}; localização ${result.location || 'não indicada'}; ${result.version}; revisão ${result.revision}; ${result.calculatedAt}`;
|
||
const out: Array<[string, string, string, string]> = [
|
||
[
|
||
'IVA mensal',
|
||
'Apuramento parcial Modelo A',
|
||
acquisitionCurrent(draft, book)
|
||
? 'Versão correspondente às entradas'
|
||
: 'Histórico desactualizado; não aplicar à projecção actual',
|
||
context,
|
||
],
|
||
[
|
||
'IVA mensal',
|
||
'Fundamento das aquisições',
|
||
result.basis,
|
||
result.sources,
|
||
],
|
||
];
|
||
for (const row of result.inputs) {
|
||
const r = result.rows.find((x) => x.id === row.id)!;
|
||
if (row.d)
|
||
out.push([
|
||
'IVA mensal',
|
||
`AfectaçãoD — ${row.id}`,
|
||
`${{ actual: 'Afectação real22(2)', pro_rata: 'Pro rata provisório22', reduced: 'Proporção específica18(6)', '': 'Método por identificar' }[row.d.method]};âmbito${row.d.scopeRef};quociente${r.allocationCalculation ? fractionLabel(r.allocationCalculation.rawRatio) : 'Indeterminado'};proporção aplicada${r.allocationCalculation ? fractionLabel(r.allocationCalculation.ratio) : 'Indeterminada'};IVA elegível antesD${r.allocationCalculation ? fractionLabel(r.allocationCalculation.before) : 'Indeterminado'};dedutível${r.deductibleExact ? fractionLabel(r.deductibleExact) : 'Indeterminado'};não dedutível${r.nonDeductibleExact ? fractionLabel(r.nonDeductibleExact) : 'Indeterminado'}`,
|
||
`${row.d.source};${row.d.method === 'reduced' ? 'origem ' + row.d.reducedId : 'bases ' + row.d.numerator + '/' + row.d.denominator + ' ' + (row.d.method === 'actual' ? row.d.unit : 'MZN') + ';ano ' + (row.d.method === 'pro_rata' ? row.d.referenceYear : row.period.slice(0, 4)) + ';comunicação ' + row.d.communicationRef + ';declaração ' + row.d.declarationRef};${r.allocationCalculation?.source ?? r.eligibility?.source ?? ''}`,
|
||
]);
|
||
if (row.bc)
|
||
out.push([
|
||
'IVA mensal',
|
||
`CondiçõesB/C — ${row.id}`,
|
||
`${row.field === '11' ? 'Bilhete de despacho de importação;importador ' + row.bc.importer + ';bens ' + row.bc.goodsRef + ';IVA ' + { due: 'devido identificado', exempt: 'isento', zero: 'zero expresso', unknown: 'desconhecido', '': 'por indicar' }[row.bc.vatBasis] + ';fonte ' + row.bc.vatRef : 'Despesa ' + (expenseOptions.find(([id]) => id === row.bc!.expense)?.[1] ?? 'por identificar') + ';IVA suportado17-A:' + { yes: 'Sim', no: 'Não', '': 'Por indicar' }[row.bc.reducedSupply]};dedutível exacto:${r.deductibleExact ? fractionLabel(r.deductibleExact) : 'Indeterminado'};não dedutível:${r.nonDeductibleExact ? fractionLabel(r.nonDeductibleExact) : 'Indeterminado'}`,
|
||
`${row.bc.evidence};${acquisitionBCDetails(row.bc)};origem${row.bc.source ? 'registo de importaçãoL1 ' + row.bc.source.id : 'introdução directa'};${acquisitionBCSource}`,
|
||
]);
|
||
if (r.eligibility)
|
||
out.push([
|
||
'IVA mensal',
|
||
`Direito da aquisição ${row.id}`,
|
||
`${{ eligible: 'Elegível', no_right: 'Sem direito', indeterminate: 'Indeterminado' }[r.eligibility.status]}: ${r.eligibility.reason}`,
|
||
`${r.eligibility.source}; ${row.d ? 'âmbitoD ' + row.d.scopeRef : 'operação ' + (r.eligibility.operationId ?? 'por identificar') + '; ' + (r.eligibility.operationPeriod ?? '') + '; ' + (r.eligibility.operationArticle ?? '') + '; afectação ' + (row.deductionLink?.allocationRef ?? '')}`,
|
||
]);
|
||
const source =
|
||
row.sourceMonth === null
|
||
? 'Introduzido pelo utilizador'
|
||
: `Plano (${row.sourceField === 'asset' ? 'orçamento de activos' : row.sourceField === 'operating' ? 'orçamento operacional' : 'compras/encomendas'}), mês ${row.sourceMonth}, ${row.sourcePeriod ?? 'período de origem não indicado'}, ${row.sourceAmount ?? 'valor em falta'} ${row.sourceCurrency}; ${row.sourceBasis === 'net' ? 'sem IVA' : row.sourceBasis === 'gross' ? 'com IVA' : 'base por classificar'}`;
|
||
const allocation = {
|
||
taxable_only: row.deductionLink
|
||
? 'exclusiva à operação ligada'
|
||
: 'exclusiva a operações tributadas',
|
||
mixed: 'mista',
|
||
no_right: 'sem direito à dedução',
|
||
'': 'por indicar',
|
||
}[row.allocation];
|
||
out.push([
|
||
'IVA mensal',
|
||
`Aquisição ${row.id} — campo${row.field} — ${row.period}`,
|
||
`${row.description}; líquido ${r.net ?? 'Indeterminado'}; IVA ${r.vat ?? 'Indeterminado'}; bruto ${r.gross ?? 'Indeterminado'}; dedutível ${r.deductible ?? 'Indeterminado'}`,
|
||
`${context}; ${row.origin === 'projected' ? 'Projecção' : 'Dados efectivos declarados'}; ${source}; ${row.documentType === 'invoice' ? 'Factura' : row.documentType === 'equivalent' ? 'Documento equivalente' : row.documentType === 'customs' ? 'Bilhete de despacho de importação' : 'Documento por indicar'} ${row.documentRef}, ${row.documentDate}, linha ${row.documentLine || 'única'}; fornecedor ${row.supplier}; afectação ${allocation}; ${r.reason}`,
|
||
]);
|
||
if (
|
||
row.acquisitionTreatment === 'former_exploration_exemption' ||
|
||
row.acquisitionTreatmentOrigin
|
||
)
|
||
out.push([
|
||
'IVA mensal',
|
||
`Continuidade14(2)(c) — ${row.id}`,
|
||
row.acquisitionTreatment === 'former_exploration_exemption'
|
||
? 'Antiga isenção eliminada; dedução indeterminada por esta referência'
|
||
: 'Continuidade normal expressamente escolhida; montantes e documento conservados; condições de dedução da aquisição, sem aplicar antiga isenção',
|
||
`${context}; ${formerExplorationSource}; origem conservada ${row.acquisitionTreatmentOrigin?.id ?? 'former_exploration_exemption'}`,
|
||
]);
|
||
}
|
||
for (const period of result.periods) {
|
||
const entries = Object.entries(period.fields).sort(
|
||
([a], [b]) => Number(a) - Number(b),
|
||
);
|
||
const declaration = result.declarations?.periods.find(
|
||
(p) => p.period === period.period,
|
||
);
|
||
const declarationOrigin = declaration
|
||
? `Aquisições08–10 ${declaration.complete ? 'declaradas completas' : 'por completar'};${declaration.importsComplete !== undefined ? `importações11 ${declaration.importsComplete ? 'declaradas completas' : 'por completar'};` : ''} ausência de operações expressamente declarada nos campos ${declaration.absent.join(', ') || 'nenhum'}.`
|
||
: 'Declarações detalhadas não recuperáveis nesta versão histórica.';
|
||
out.push([
|
||
'IVA mensal',
|
||
`Modelo A — ${period.period} — parcelas01–13`,
|
||
entries
|
||
.filter(([field]) => Number(field) <= 13)
|
||
.map(
|
||
([field, value]) =>
|
||
`${field}: ${value ?? 'Indeterminado'}`,
|
||
)
|
||
.join('; '),
|
||
`${context}; ${declarationOrigin}`,
|
||
]);
|
||
out.push([
|
||
'IVA mensal',
|
||
`Modelo A — ${period.period} — totais14–18`,
|
||
entries
|
||
.filter(([field]) => Number(field) >= 14)
|
||
.map(
|
||
([field, value]) =>
|
||
`${field}: ${value ?? 'Indeterminado'}`,
|
||
)
|
||
.join('; '),
|
||
context,
|
||
]);
|
||
if (period.exactFields)
|
||
out.push([
|
||
'IVA mensal',
|
||
`Modelo A — ${period.period} — precisão exacta`,
|
||
Object.entries(period.exactFields)
|
||
.map(
|
||
([f, v]) =>
|
||
`${f}: ${v ? fractionLabel(v) : 'Indeterminado'}`,
|
||
)
|
||
.join('; '),
|
||
'Fracções em MZN; apresentação decimal técnica, sem regra declarativa presumida.',
|
||
]);
|
||
if (period.reasons.length)
|
||
out.push([
|
||
'IVA mensal',
|
||
`Modelo A — ${period.period} — pendências`,
|
||
period.reasons.join(' '),
|
||
result.sources,
|
||
]);
|
||
}
|
||
return [
|
||
...out,
|
||
...regularizationReportRows(draft),
|
||
...frozenAssetRows(draft),
|
||
];
|
||
} catch {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'Compras — Modelo A',
|
||
'Não foi possível recuperar os dados guardados',
|
||
'Preservados, sem substituição por zero',
|
||
],
|
||
];
|
||
}
|
||
}
|