Files
Investidor-V2/resources/js/lib/iva-own-name.ts
T

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.',
],
];
}
}