103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { TipoDocumentoFiscal } from '../value-objects/FiscalEnums.js';
|
|
import { ResolucionAgotadaException } from '../exceptions/ValidacionFiscalException.js';
|
|
|
|
export interface ResolucionDianProps {
|
|
id?: string;
|
|
tenantId?: string;
|
|
resolutionNumber: string;
|
|
prefix: string;
|
|
fromNumber: bigint;
|
|
toNumber: bigint;
|
|
currentConsecutive: bigint;
|
|
technicalKey: string;
|
|
startDate: Date;
|
|
endDate: Date;
|
|
isActive?: boolean;
|
|
documentType?: TipoDocumentoFiscal;
|
|
}
|
|
|
|
export class ResolucionDian {
|
|
public readonly id?: string;
|
|
public readonly tenantId?: string;
|
|
public readonly resolutionNumber: string;
|
|
public readonly prefix: string;
|
|
public readonly fromNumber: bigint;
|
|
public readonly toNumber: bigint;
|
|
private _currentConsecutive: bigint;
|
|
public readonly technicalKey: string;
|
|
public readonly startDate: Date;
|
|
public readonly endDate: Date;
|
|
public readonly isActive: boolean;
|
|
public readonly documentType: TipoDocumentoFiscal;
|
|
|
|
constructor(props: ResolucionDianProps) {
|
|
this.id = props.id;
|
|
this.tenantId = props.tenantId;
|
|
this.resolutionNumber = props.resolutionNumber.trim();
|
|
this.prefix = props.prefix.trim().toUpperCase();
|
|
this.fromNumber = BigInt(props.fromNumber);
|
|
this.toNumber = BigInt(props.toNumber);
|
|
this._currentConsecutive = BigInt(props.currentConsecutive);
|
|
this.technicalKey = props.technicalKey.trim();
|
|
this.startDate = props.startDate;
|
|
this.endDate = props.endDate;
|
|
this.isActive = props.isActive ?? true;
|
|
this.documentType = props.documentType || TipoDocumentoFiscal.FACTURA_VENTA;
|
|
|
|
this.validarInvariantes();
|
|
}
|
|
|
|
private validarInvariantes(): void {
|
|
if (this.fromNumber > this.toNumber) {
|
|
throw new Error('El rango inicial no puede ser mayor que el rango final de la resolución.');
|
|
}
|
|
}
|
|
|
|
public get currentConsecutive(): bigint {
|
|
return this._currentConsecutive;
|
|
}
|
|
|
|
public estaVigente(fechaReferencia: Date = new Date()): boolean {
|
|
if (!this.isActive) return false;
|
|
return fechaReferencia >= this.startDate && fechaReferencia <= this.endDate;
|
|
}
|
|
|
|
public tieneNumerosDisponibles(): boolean {
|
|
return this._currentConsecutive < this.toNumber;
|
|
}
|
|
|
|
public obtenerSiguienteConsecutivo(): bigint {
|
|
if (!this.estaVigente()) {
|
|
throw new ResolucionAgotadaException(
|
|
`La resolución ${this.resolutionNumber} (Prefijo: ${this.prefix}) ha expirado o se encuentra inactiva.`
|
|
);
|
|
}
|
|
|
|
if (!this.tieneNumerosDisponibles()) {
|
|
throw new ResolucionAgotadaException(
|
|
`Rango de numeración agotado para la resolución ${this.resolutionNumber} (Prefijo: ${this.prefix}). Rango límite: ${this.toNumber}`
|
|
);
|
|
}
|
|
|
|
this._currentConsecutive += 1n;
|
|
return this._currentConsecutive;
|
|
}
|
|
|
|
public formatearNumeroCompleto(consecutivo: bigint): string {
|
|
return this.prefix ? `${this.prefix}-${consecutivo}` : `${consecutivo}`;
|
|
}
|
|
|
|
public porcentajeConsumido(): number {
|
|
const total = Number(this.toNumber - this.fromNumber + 1n);
|
|
if (total <= 0) return 100;
|
|
const usados = Number(this._currentConsecutive - this.fromNumber + 1n);
|
|
const porcentaje = Math.min(100, Math.max(0, (usados / total) * 100));
|
|
return Math.round(porcentaje * 10) / 10;
|
|
}
|
|
|
|
public diasRestantesVigencia(ahora: Date = new Date()): number {
|
|
const diffMs = this.endDate.getTime() - ahora.getTime();
|
|
return Math.max(0, Math.ceil(diffMs / (1000 * 60 * 60 * 24)));
|
|
}
|
|
}
|