Files
Investidor-V2/resources/js/lib/irpc-advance-calendar.ts
T

216 lines
7.2 KiB
TypeScript

export type AdvancePeriodDeclaration = {
version: 1;
mode: '' | 'annual_non_civil';
subjectNuit: string;
year: string;
currentFrom: string;
currentTo: string;
currentReference: string;
previousFrom: string;
previousTo: string;
previousReference: string;
ppcBasisKey: string;
pecBasisKey: string;
confirmed: '' | 'yes';
};
export type AdvanceFiscalPeriod = {
from?: string | null;
to?: string | null;
subjectNuit?: string;
ppcBasisKey?: string;
pecBasisKey?: string;
declaration?: AdvancePeriodDeclaration;
} | null;
export const advanceCalendarVersion = 'IRPC-annual-noncivil-calendar/1';
const iso = (s?: string | null): s is string =>
Boolean(
s &&
/^20\d{2}-\d{2}-\d{2}$/.test(s) &&
Number.isFinite(Date.parse(s)) &&
new Date(s).toISOString().slice(0, 10) === s,
);
const index = (s: string) =>
Number(s.slice(0, 4)) * 12 + Number(s.slice(5, 7)) - 1;
const month = (from: string, offset: number) =>
new Date(
Date.UTC(
Number(from.slice(0, 4)),
Number(from.slice(5, 7)) - 1 + offset,
1,
),
)
.toISOString()
.slice(0, 7);
const end = (s: string) =>
new Date(Date.UTC(Number(s.slice(0, 4)), Number(s.slice(5, 7)), 0))
.toISOString()
.slice(0, 10);
const previousDay = (s: string) =>
new Date(Date.parse(s) - 86400000).toISOString().slice(0, 10);
const annual = (from?: string | null, to?: string | null) =>
iso(from) &&
iso(to) &&
from.endsWith('-01') &&
to === end(to) &&
index(to) - index(from) === 11;
export function newAdvancePeriodDeclaration(
subjectNuit: string,
year: string,
period: AdvanceFiscalPeriod,
): AdvancePeriodDeclaration {
const from = period?.from ?? '',
to = period?.to ?? '';
return {
version: 1,
mode: 'annual_non_civil',
subjectNuit,
year,
currentFrom: from,
currentTo: to,
currentReference: '',
previousFrom: iso(from) ? month(from, -12) + '-01' : '',
previousTo: iso(from) ? previousDay(from) : '',
previousReference: '',
ppcBasisKey: '',
pecBasisKey: '',
confirmed: '',
};
}
export function validateAdvancePeriodDeclaration(d: AdvancePeriodDeclaration) {
if (
!d ||
d.version !== 1 ||
!['', 'annual_non_civil'].includes(d.mode) ||
!['', 'yes'].includes(d.confirmed) ||
[
'subjectNuit',
'year',
'currentFrom',
'currentTo',
'currentReference',
'previousFrom',
'previousTo',
'previousReference',
'ppcBasisKey',
'pecBasisKey',
].some(
(k) =>
typeof (d as unknown as Record<string, unknown>)[k] !==
'string',
)
)
throw Error(
'Correspondência de períodos não recuperável;original preservado.',
);
}
export function advanceCalendar(
period: AdvanceFiscalPeriod | undefined,
year: string,
subjectNuit?: string,
) {
const from = period?.from ?? '',
to = period?.to ?? '';
if (!/^20\d{2}$/.test(year))
return {
version: advanceCalendarVersion,
mode: 'unavailable' as const,
candidate: false,
pending: ['exercício das estimativas por confirmar'],
from,
to,
ppcMonths: [],
pecMonths: [],
previousFrom: '',
previousTo: '',
source: '',
};
const civil =
!period || (from === year + '-01-01' && to === year + '-12-31');
const candidate = annual(from, to) && to.slice(0, 4) === year && !civil;
let mode: 'civil' | 'annual_non_civil' | 'unavailable' = civil
? 'civil'
: 'unavailable';
let pending: string[] = [];
const d = period?.declaration;
if (!civil) {
if (d?.mode === 'annual_non_civil') {
validateAdvancePeriodDeclaration(d);
const issues: string[] = [];
if (!candidate)
issues.push(
'o período L6 deve conter doze meses civis completos com termo no exercício indicado',
);
if (
d.subjectNuit !== (subjectNuit ?? period?.subjectNuit) ||
d.year !== year ||
d.currentFrom !== from ||
d.currentTo !== to
)
issues.push(
'titular,exercício ou datas declaradas divergem de L6',
);
if (
!annual(d.previousFrom, d.previousTo) ||
!iso(from) ||
d.previousTo !== previousDay(from) ||
d.previousTo.slice(0, 4) !== String(Number(year) - 1)
)
issues.push(
'o período anterior deve ser anual completo e imediatamente anterior ao período L6',
);
if (
!d.currentReference.trim() ||
!d.previousReference.trim() ||
d.confirmed !== 'yes'
)
issues.push(
'faltam fontes e confirmação da correspondência dos dois períodos',
);
if (issues.length)
pending = [
'período anual não civil: ' +
issues.join('; ') +
'. Preparação conservada.',
];
else mode = 'annual_non_civil';
} else
pending = [
'o período L6 ' +
(from || 'por confirmar') +
' — ' +
(to || 'por confirmar') +
' não corresponde ao ano civil ' +
year +
'. Reconciliar o enquadramento antes de usar as estimativas ordinárias; a preparação fica conservada.',
];
}
const start = mode === 'civil' ? year + '-01-01' : from;
return {
version: advanceCalendarVersion,
mode,
candidate,
pending,
from: mode === 'civil' ? year + '-01-01' : from,
to: mode === 'civil' ? year + '-12-31' : to,
ppcMonths:
mode === 'unavailable' ? [] : [4, 6, 8].map((i) => month(start, i)),
pecMonths:
mode === 'unavailable' ? [] : [5, 7, 9].map((i) => month(start, i)),
previousFrom:
mode === 'annual_non_civil'
? d!.previousFrom
: String(Number(year) - 1) + '-01-01',
previousTo:
mode === 'annual_non_civil'
? d!.previousTo
: String(Number(year) - 1) + '-12-31',
source:
mode === 'annual_non_civil'
? 'CIRPC7(2),Lei34/2007,BR52,original SHA256cdbef875ad7ccc1517b12faf5de0e893a70ed2ed81e33913cd6dc4a674474e6e; RIRPC27(1)(a)/29(1),BR16/I16-04-2008,original SHA256fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee. Períodos anuais completos documentados;5.º/7.º/9.º mês PPC e6.º/8.º/10.º mês PEC. ' +
d!.currentReference +
'; ' +
d!.previousReference
: '',
};
}