123 lines
5.8 KiB
TypeScript
123 lines
5.8 KiB
TypeScript
export type ProductionTaxOption = {
|
|
mode: '' | 'annual' | 'startup';
|
|
declarationDate: string;
|
|
startupDate: string;
|
|
returnStatus: '' | 'none' | 'declared';
|
|
returnDeclarationDate: string;
|
|
operationDate: string;
|
|
origin?: { rowId: string; description: string; establishment: string };
|
|
};
|
|
export const productionOptionVersion = 'production-option/2';
|
|
// Civil Code 279(c), read with 296: the term ends at 24:00 on this date.
|
|
// A missing anniversary day is clamped within the same month, never rolled to March.
|
|
export function productionMinimumTermEnd(effective: string): string {
|
|
const year = Number(effective.slice(0, 4)) + 5,
|
|
month = Number(effective.slice(5, 7)),
|
|
day = Number(effective.slice(8, 10));
|
|
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
const lastDay = [
|
|
31,
|
|
leap ? 29 : 28,
|
|
31,
|
|
30,
|
|
31,
|
|
30,
|
|
31,
|
|
31,
|
|
30,
|
|
31,
|
|
30,
|
|
31,
|
|
][month - 1];
|
|
return `${String(year).padStart(4, '0')}-${String(month).padStart(2, '0')}-${String(Math.min(day, lastDay)).padStart(2, '0')}`;
|
|
}
|
|
export const productionReturnEffective = (declarationDate: string): string =>
|
|
String(Number(declarationDate.slice(0, 4)) + 1).padStart(4, '0') + '-01-01';
|
|
export const emptyProductionTaxOption = (): ProductionTaxOption => ({
|
|
mode: '',
|
|
declarationDate: '',
|
|
startupDate: '',
|
|
returnStatus: '',
|
|
returnDeclarationDate: '',
|
|
operationDate: '',
|
|
});
|
|
export function productionOptionCheck(
|
|
option: ProductionTaxOption | undefined,
|
|
period: string,
|
|
): { error?: string; summary?: string } {
|
|
const valid = (value: unknown): value is string =>
|
|
typeof value === 'string' &&
|
|
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
|
|
Number(value.slice(0, 4)) >= 1 &&
|
|
Number.isFinite(new Date(value + 'T00:00:00Z').valueOf()) &&
|
|
new Date(value + 'T00:00:00Z').toISOString().slice(0, 10) === value;
|
|
if (
|
|
!option ||
|
|
!['annual', 'startup'].includes(option.mode) ||
|
|
!valid(option.declarationDate) ||
|
|
!valid(option.operationDate)
|
|
)
|
|
return {
|
|
error: 'Indique o tipo/data da declaração de opção e a data da operação; não se presume renúncia eficaz.',
|
|
};
|
|
if (option.operationDate.slice(0, 7) !== period)
|
|
return {
|
|
error: 'A data da operação deve pertencer ao período desta linha; não repartir montantes automaticamente.',
|
|
};
|
|
let effective =
|
|
String(Number(option.declarationDate.slice(0, 4)) + 1).padStart(
|
|
4,
|
|
'0',
|
|
) + '-01-01';
|
|
if (option.mode === 'startup') {
|
|
if (
|
|
!valid(option.startupDate) ||
|
|
option.declarationDate > option.startupDate
|
|
)
|
|
return {
|
|
error: 'Na opção constante da declaração de início, indique a data de início e uma declaração que não lhe seja posterior.',
|
|
};
|
|
effective = option.startupDate;
|
|
}
|
|
if (option.operationDate < effective)
|
|
return {
|
|
error: `Opção ainda sem efeitos nesta operação; início de efeitos ${effective}.`,
|
|
};
|
|
if (!['none', 'declared'].includes(option.returnStatus))
|
|
return {
|
|
error: 'Indique expressamente se existe declaração de regresso à isenção; informação desconhecida não significa ausência de declaração.',
|
|
};
|
|
if (option.returnStatus === 'none' && option.returnDeclarationDate)
|
|
return {
|
|
error: 'Foi indicada ausência de declaração de regresso, mas existe uma data guardada; resolva a divergência explicitamente.',
|
|
};
|
|
let end = '';
|
|
if (option.returnStatus === 'declared') {
|
|
if (!valid(option.returnDeclarationDate))
|
|
return {
|
|
error: 'Data da declaração de regresso à isenção em falta ou inválida; não presumir uma data.',
|
|
};
|
|
const anniversary = productionMinimumTermEnd(effective);
|
|
if (option.returnDeclarationDate < option.declarationDate)
|
|
return {
|
|
error: 'A declaração de regresso antecede a declaração de opção; resolva a divergência do histórico.',
|
|
};
|
|
if (option.returnDeclarationDate > anniversary)
|
|
return {
|
|
error: `A declaração de regresso indicada é posterior ao termo às 24 horas de ${anniversary}; o CIVA, art.11(3), exige entrega antes de expirar o prazo. Não presumir efeitos para esta declaração tardia.`,
|
|
};
|
|
end = productionReturnEffective(option.returnDeclarationDate);
|
|
if (end <= anniversary)
|
|
return {
|
|
error: `O regresso não pode produzir efeitos antes de terminado o prazo mínimo, às 24 horas de ${anniversary}.`,
|
|
};
|
|
if (option.operationDate >= end)
|
|
return {
|
|
error: `Opção já cessou na data desta operação; regresso à isenção com efeitos ${end}. Reveja o tratamento.`,
|
|
};
|
|
}
|
|
return {
|
|
summary: `Art.11 (${productionOptionVersion}): declaração ${option.declarationDate} (${option.mode === 'startup' ? 'opção no início da actividade' : 'opção anual'}); efeitos ${effective}; termo mínimo às 24 horas de ${productionMinimumTermEnd(effective)} (Código Civil, arts.279.º,c), e296.º); operação ${option.operationDate}; ${end ? `declaração de regresso ${option.returnDeclarationDate}, efeitos ${end} (CIVA, art.11(3)); o termo do prazo não antecipa estes efeitos` : 'ausência de declaração de regresso expressamente indicada; tributação continua, sem caducidade automática ao fim de cinco anos'}.${option.origin ? ` Opção reutilizada de ${option.origin.description} (${option.origin.rowId}), estabelecimento ${option.origin.establishment}; escolha confirmada nesta linha.` : ''}`,
|
|
};
|
|
}
|