436 lines
16 KiB
TypeScript
436 lines
16 KiB
TypeScript
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
|
|
|
export const ownNameVersion = 'own-name-chains/1';
|
|
export const ownNameSource =
|
|
'CIVA3(3)c,Lei13/2016PDF6/p1426(264):transferências em execução de contrato de comissão,incluindo consignação;comissário considerado comprador na comissão de venda,comitente na de compra. Art4(3),PDF7/p1426(265):mandatário emnome próprio é adquirente eprestador do serviço sucessivamente. Lei10/2025PDF12mantém3(3)c e não altera4. Base de bens15(2)e/P091já entregue,não recalculada ou somada neste percurso. Recorte2026:circuito/contraprestações originais separados,sem localização,isenção,taxa,IVA,dedução/campo11 ou totaisModeloA presumidos.';
|
|
export const ownNameTreatments = [
|
|
{
|
|
id: 'goods_sale_commission',
|
|
branch: 'P122',
|
|
label: 'P122 — Comissão de venda de bens',
|
|
kind: 'goods',
|
|
direction: 'sale',
|
|
},
|
|
{
|
|
id: 'goods_purchase_commission',
|
|
branch: 'P122',
|
|
label: 'P122 — Comissão de compra de bens',
|
|
kind: 'goods',
|
|
direction: 'purchase',
|
|
},
|
|
{
|
|
id: 'service_sale_mandate',
|
|
branch: 'P128',
|
|
label: 'P128 — Mandato de venda/prestação de serviços',
|
|
kind: 'services',
|
|
direction: 'sale',
|
|
},
|
|
{
|
|
id: 'service_purchase_mandate',
|
|
branch: 'P128',
|
|
label: 'P128 — Mandato de aquisição de serviços',
|
|
kind: 'services',
|
|
direction: 'purchase',
|
|
},
|
|
] as const;
|
|
export const ownNameConditions = [
|
|
[
|
|
'ownName',
|
|
'O comissário/mandatário actua emnome próprio,não apenas emnome do mandante',
|
|
],
|
|
[
|
|
'forAnother',
|
|
'Actua por conta de outrem identificado ecom relação documentada ou pressuposto expresso',
|
|
],
|
|
[
|
|
'factsCorrespond',
|
|
'Objecto,partes,referências eperíodo correspondem à operação identificada',
|
|
],
|
|
] as const;
|
|
const goodsConditions = [
|
|
[
|
|
'goodsNature',
|
|
'São transferências de bens,não serviço autónomo de intermediação',
|
|
],
|
|
[
|
|
'commissionContract',
|
|
'As transferências executam contrato de comissão identificado,conforme3(3)c,incluindo consignação quando aplicável',
|
|
],
|
|
[
|
|
'directionConfirmed',
|
|
'Papéis e duas relações correspondem ao sentido de venda ou compra escolhido',
|
|
],
|
|
] as const;
|
|
const serviceConditions = [
|
|
[
|
|
'serviceNature',
|
|
'Está identificada uma prestação de serviços,não transferência de bens',
|
|
],
|
|
[
|
|
'mandateContract',
|
|
'A prestação é efectuada por intervenção de mandatário emnome próprio,com mandato identificado4(3)',
|
|
],
|
|
[
|
|
'successiveService',
|
|
'Mandatário é adquirente eprestador do serviço sucessivamente;referências distinguem as duas prestações',
|
|
],
|
|
[
|
|
'directionConfirmed',
|
|
'Papéis e duas relações correspondem ao sentido de venda/prestação ou aquisição escolhido',
|
|
],
|
|
] as const;
|
|
export function ownNameSpecificConditions(
|
|
treatment: string,
|
|
): ReadonlyArray<readonly [string, string]> {
|
|
const rule = ownNameTreatments.find((t) => t.id === treatment);
|
|
return rule?.kind === 'goods'
|
|
? goodsConditions
|
|
: rule?.kind === 'services'
|
|
? serviceConditions
|
|
: [];
|
|
}
|
|
type Choice = '' | 'yes' | 'no';
|
|
export type OwnNameRow = {
|
|
id: string;
|
|
treatment: string;
|
|
description: string;
|
|
period: string;
|
|
origin: 'projected' | 'effective';
|
|
operationDate: string;
|
|
operationRef: string;
|
|
documentRef: string;
|
|
documentDate: string;
|
|
principalRef: string;
|
|
agentRef: string;
|
|
thirdPartyRef: string;
|
|
contractRef: string;
|
|
subjectRef: string;
|
|
currency: string;
|
|
firstAmount: string;
|
|
secondAmount: string;
|
|
firstLegRef: string;
|
|
secondLegRef: string;
|
|
existingBaseRef: string;
|
|
scope: string;
|
|
evidence: string;
|
|
conditions: Partial<Record<string, Choice>>;
|
|
};
|
|
type Leg = {
|
|
supplier: string;
|
|
acquirer: string;
|
|
consideration: string;
|
|
currency: string;
|
|
reference: string;
|
|
};
|
|
type LineResult = {
|
|
id: string;
|
|
status: 'determined' | 'not_applicable' | 'indeterminate';
|
|
legs: Leg[];
|
|
baseAmount: null;
|
|
vat: null;
|
|
rate: null;
|
|
reason: string;
|
|
};
|
|
export type OwnNameCalculation = {
|
|
version: string;
|
|
revision: number;
|
|
calculatedAt: string;
|
|
opportunity: string;
|
|
location: string;
|
|
sourceKey: string;
|
|
source: string;
|
|
inputs: OwnNameRow[];
|
|
rows: LineResult[];
|
|
};
|
|
export type OwnNameBook = {
|
|
rows: OwnNameRow[];
|
|
result?: OwnNameCalculation;
|
|
history?: OwnNameCalculation[];
|
|
};
|
|
type Store = ReturnType<typeof readAcquisitionStore> & {
|
|
ownNames?: Record<string, OwnNameBook>;
|
|
};
|
|
export function newOwnName(id: string, currency = ''): OwnNameRow {
|
|
return {
|
|
id,
|
|
treatment: '',
|
|
description: '',
|
|
period: '',
|
|
origin: 'projected',
|
|
operationDate: '',
|
|
operationRef: '',
|
|
documentRef: '',
|
|
documentDate: '',
|
|
principalRef: '',
|
|
agentRef: '',
|
|
thirdPartyRef: '',
|
|
contractRef: '',
|
|
subjectRef: '',
|
|
currency,
|
|
firstAmount: '',
|
|
secondAmount: '',
|
|
firstLegRef: '',
|
|
secondLegRef: '',
|
|
existingBaseRef: '',
|
|
scope: '',
|
|
evidence: '',
|
|
conditions: {},
|
|
};
|
|
}
|
|
export function switchOwnNameTreatment(
|
|
row: OwnNameRow,
|
|
treatment: string,
|
|
): OwnNameRow {
|
|
return {
|
|
...row,
|
|
treatment,
|
|
conditions: Object.fromEntries(
|
|
ownNameConditions.map(([key]) => [key, row.conditions[key] ?? '']),
|
|
),
|
|
};
|
|
}
|
|
|
|
function readStore(draft: Record<string, unknown>): Store {
|
|
const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
|
if (
|
|
store.ownNames !== undefined &&
|
|
(!store.ownNames ||
|
|
typeof store.ownNames !== 'object' ||
|
|
Array.isArray(store.ownNames))
|
|
)
|
|
throw new Error(
|
|
'Circuitos emnome próprio não recuperáveis;originais preservados.',
|
|
);
|
|
const fields = Object.keys(newOwnName('')).filter(
|
|
(key) => key !== 'conditions',
|
|
);
|
|
for (const book of Object.values(store.ownNames ?? {}))
|
|
if (
|
|
!book ||
|
|
!Array.isArray(book.rows) ||
|
|
(book.history !== undefined && !Array.isArray(book.history)) ||
|
|
book.rows.some(
|
|
(row) =>
|
|
!row ||
|
|
fields.some(
|
|
(key) =>
|
|
typeof row[key as keyof OwnNameRow] !== 'string',
|
|
) ||
|
|
!row.conditions ||
|
|
typeof row.conditions !== 'object' ||
|
|
Array.isArray(row.conditions),
|
|
)
|
|
)
|
|
throw new Error(
|
|
'Linhas de circuitos emnome próprio não recuperáveis;originais preservados,sem substituição por lista vazia.',
|
|
);
|
|
return store;
|
|
}
|
|
export function ownNameBook(draft: Record<string, unknown>): OwnNameBook {
|
|
return readStore(draft).ownNames?.[acquisitionScope(draft)] ?? { rows: [] };
|
|
}
|
|
export function saveOwnNameBook(
|
|
draft: Record<string, unknown>,
|
|
book: OwnNameBook,
|
|
): string {
|
|
const current = readStore(draft),
|
|
encoded = JSON.stringify({
|
|
...current,
|
|
ownNames: { ...current.ownNames, [acquisitionScope(draft)]: book },
|
|
});
|
|
readStore({ ...draft, modelo_a_snapshot: encoded });
|
|
return encoded;
|
|
}
|
|
function validDate(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 evaluateOwnName(
|
|
row: OwnNameRow,
|
|
currency: unknown,
|
|
): LineResult {
|
|
const result: LineResult = {
|
|
id: row.id,
|
|
status: 'indeterminate',
|
|
legs: [],
|
|
baseAmount: null,
|
|
vat: null,
|
|
rate: null,
|
|
reason: '',
|
|
};
|
|
const stop = (
|
|
reason: string,
|
|
status: LineResult['status'] = 'indeterminate',
|
|
) => ({ ...result, status, reason });
|
|
const rule = ownNameTreatments.find((t) => t.id === row.treatment);
|
|
if (!rule)
|
|
return stop(
|
|
'Identifique bens/serviços e o sentido da comissão/mandato;sem inferir pelo pagamento de uma comissão.',
|
|
);
|
|
if (
|
|
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
|
|
!['projected', 'effective'].includes(row.origin) ||
|
|
!validDate(row.operationDate) ||
|
|
row.operationDate.slice(0, 7) !== row.period
|
|
)
|
|
return stop(
|
|
'Identifique mês2026,data correspondente e dados efectivos ou pressupostos expressos.',
|
|
);
|
|
const required = [
|
|
...ownNameConditions,
|
|
...ownNameSpecificConditions(row.treatment),
|
|
];
|
|
if (required.some(([key]) => row.conditions[key] === 'no'))
|
|
return stop(
|
|
'Este circuito emnome próprio não se aplica aos factos declarados;rever mandato/comissão/natureza/papéis,sem classificar mera intermediação como duas operações ou presumir isenção/IVAzero.',
|
|
'not_applicable',
|
|
);
|
|
if (required.some(([key]) => row.conditions[key] !== 'yes'))
|
|
return stop(
|
|
'Natureza,actuação emnome próprio ou papéis por esclarecer;desconhecido não é não aplicável ou zero.',
|
|
);
|
|
if (
|
|
!row.description.trim() ||
|
|
!row.operationRef.trim() ||
|
|
!row.documentRef.trim() ||
|
|
!validDate(row.documentDate) ||
|
|
!row.scope.trim() ||
|
|
!row.evidence.trim() ||
|
|
!row.subjectRef.trim() ||
|
|
!row.contractRef.trim() ||
|
|
![
|
|
row.principalRef,
|
|
row.agentRef,
|
|
row.thirdPartyRef,
|
|
row.firstLegRef,
|
|
row.secondLegRef,
|
|
].every((v) => !!v.trim())
|
|
)
|
|
return stop(
|
|
'Identifique objecto,mandante/comitente,comissário/mandatário,terceiro,contrato,duas relações,operação/documento/data eâmbito;reutilize referências ou pressupostos expressos,sem certificado/aprovador novo.',
|
|
);
|
|
if (
|
|
typeof currency !== 'string' ||
|
|
!/^[A-Z]{3}$/.test(currency) ||
|
|
row.currency !== currency
|
|
)
|
|
return stop(
|
|
'Identifique moeda original coerente com o contexto;sem converter ou reinterpretarUSDcomoMZN.',
|
|
);
|
|
const amount = (v: string): string | null =>
|
|
/^\d{1,15}(\.\d{1,6})?$/.test(v.trim()) ? v.trim() : null;
|
|
const first = amount(row.firstAmount),
|
|
second = amount(row.secondAmount);
|
|
if (first === null || second === null)
|
|
return stop(
|
|
'Identifique separadamente as contraprestações originais das duas relações;desconhecido não é zero. Não inferir uma delas da comissão ou somá-las como uma única base.',
|
|
);
|
|
const before =
|
|
rule.direction === 'sale' ? row.principalRef : row.thirdPartyRef,
|
|
after =
|
|
rule.direction === 'sale' ? row.thirdPartyRef : row.principalRef;
|
|
const legs = [
|
|
{
|
|
supplier: before,
|
|
acquirer: row.agentRef,
|
|
consideration: first,
|
|
currency: row.currency,
|
|
reference: row.firstLegRef,
|
|
},
|
|
{
|
|
supplier: row.agentRef,
|
|
acquirer: after,
|
|
consideration: second,
|
|
currency: row.currency,
|
|
reference: row.secondLegRef,
|
|
},
|
|
];
|
|
return {
|
|
...result,
|
|
status: 'determined',
|
|
legs,
|
|
reason: `${rule.branch}:${rule.kind === 'goods' ? `art3(3)c,comissão de${rule.direction === 'sale' ? 'venda:comissário considerado comprador' : 'compra:comitente considerado comprador'}` : 'art4(3),mandatário emnome próprio considerado adquirente eprestador do serviço sucessivamente'}. ${row.origin === 'projected' ? 'Circuito sob pressupostos expressos.' : 'Circuito segundo dados efectivos declarados.'} Duas relações com objecto/partes/contraprestações originais separadas,sem soma ou compensação automática. ${rule.kind === 'goods' ? 'BaseP091/15(2)e já entregue permanece separada e não é recalculada.' : 'Não transformar este circuito emcomissão autónoma ou aplicar15(2)e de bens aos serviços.'} Sem determinar localização,isenção,base,taxa,IVA,dedução/campo11 ou totaisModeloA.`,
|
|
};
|
|
}
|
|
|
|
function key(draft: Record<string, unknown>, book: OwnNameBook): string {
|
|
return JSON.stringify({
|
|
version: ownNameVersion,
|
|
scope: acquisitionScope(draft),
|
|
location: draft.selection_labels ?? {},
|
|
currency: draft.currency,
|
|
rows: book.rows,
|
|
});
|
|
}
|
|
export function ownNameCurrent(
|
|
draft: Record<string, unknown>,
|
|
book: OwnNameBook,
|
|
): boolean {
|
|
return !!book.result && book.result.sourceKey === key(draft, book);
|
|
}
|
|
export function recordOwnNames(
|
|
draft: Record<string, unknown>,
|
|
book: OwnNameBook,
|
|
): OwnNameBook {
|
|
if (
|
|
!book.rows.length ||
|
|
new Set(book.rows.map((row) => row.id)).size !== book.rows.length
|
|
)
|
|
throw new Error(
|
|
'Indique operações com identificadores distintos;originais conservados.',
|
|
);
|
|
const result: OwnNameCalculation = {
|
|
version: ownNameVersion,
|
|
revision: (book.result?.revision ?? 0) + 1,
|
|
calculatedAt: new Date().toISOString(),
|
|
opportunity: acquisitionScope(draft),
|
|
location: JSON.stringify(draft.selection_labels ?? {}),
|
|
sourceKey: key(draft, book),
|
|
source: ownNameSource,
|
|
inputs: structuredClone(book.rows),
|
|
rows: book.rows.map((row) => evaluateOwnName(row, draft.currency)),
|
|
};
|
|
const next = {
|
|
...book,
|
|
result,
|
|
history: [
|
|
...(book.history ?? []),
|
|
...(book.result ? [book.result] : []),
|
|
],
|
|
};
|
|
saveOwnNameBook(draft, next);
|
|
return next;
|
|
}
|
|
export function ownNameReportRows(
|
|
draft: Record<string, unknown>,
|
|
): Array<[string, string, string, string]> {
|
|
try {
|
|
const book = ownNameBook(draft),
|
|
result = book.result;
|
|
if (!result) return [];
|
|
return result.inputs.map((row, index) => {
|
|
const line = result.rows[index],
|
|
rule = ownNameTreatments.find((t) => t.id === row.treatment);
|
|
return [
|
|
'IVA mensal',
|
|
`Circuito ${rule?.branch ?? 'emnome próprio'} — ${row.period} — ${row.id}`,
|
|
`${row.description};${line.status === 'determined' ? 'Circuito identificado' : line.status === 'not_applicable' ? 'Circuito não aplicável' : 'Circuito indeterminado'};IVA: Indeterminado;${line.reason};${line.legs.map((leg, i) => `Relação${i + 1}:${leg.supplier} → ${leg.acquirer},contraprestação original ${leg.consideration} ${leg.currency},referência ${leg.reference}`).join('; ')};sem soma das duas relações`,
|
|
`${result.opportunity};${result.location};${ownNameCurrent(draft, book) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${result.source};${row.origin === 'projected' ? 'Projecção' : 'Dados efectivos declarados'};${rule?.label ?? 'Percurso por indicar'};operação ${row.operationRef},${row.operationDate};objecto ${row.subjectRef};contrato ${row.contractRef};mandante/comitente ${row.principalRef};agente ${row.agentRef};terceiro ${row.thirdPartyRef};relações ${row.firstLegRef}/${row.secondLegRef};${rule?.kind === 'goods' ? `referênciaP091existente ${row.existingBaseRef || 'não indicada;sem nova base calculada'};` : ''}âmbito ${row.scope};documento ${row.documentRef},${row.documentDate};condições ${[...ownNameConditions, ...ownNameSpecificConditions(row.treatment)].map(([key, label]) => label + ': ' + (row.conditions[key] === 'yes' ? 'Sim' : row.conditions[key] === 'no' ? 'Não' : 'Por indicar')).join('; ')};fundamento ${row.evidence};sem IVA/dedução/campo11/totaisModeloA.`,
|
|
];
|
|
});
|
|
} catch {
|
|
return [
|
|
[
|
|
'IVA mensal',
|
|
'Circuito emnome próprio',
|
|
'Dados não recuperáveis',
|
|
'Originais preservados;sem circuito ou valor por defeito.',
|
|
],
|
|
];
|
|
}
|
|
}
|