Helpers comunes
This commit is contained in:
parent
5f08cfaa15
commit
60dacc4c32
58
modules/core/src/common/helpers/date-helper.ts
Normal file
58
modules/core/src/common/helpers/date-helper.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
type DateInput = string | number | Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatea una fecha según `locale` usando Intl.DateTimeFormat.
|
||||||
|
*
|
||||||
|
* - Acepta Date, timestamp o string.
|
||||||
|
* - Strings "date-only" (YYYY-MM-DD) se crean en hora local para evitar
|
||||||
|
* desfases por zona horaria (off-by-one).
|
||||||
|
* - Devuelve "" si la fecha es inválida.
|
||||||
|
*/
|
||||||
|
function formatDate(
|
||||||
|
input: DateInput,
|
||||||
|
locale?: string,
|
||||||
|
options?: Intl.DateTimeFormatOptions
|
||||||
|
): string {
|
||||||
|
const date = normalizeToDate(input);
|
||||||
|
if (!date || isNaN(date.getTime())) return "";
|
||||||
|
|
||||||
|
// Por defecto, formato corto y consistente.
|
||||||
|
const fmt = new Intl.DateTimeFormat(locale, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fmt.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normaliza distintos inputs a Date. Maneja "YYYY-MM-DD" de forma segura. */
|
||||||
|
function normalizeToDate(input: DateInput): Date | null {
|
||||||
|
if (input instanceof Date) return input;
|
||||||
|
if (typeof input === "number") return new Date(input);
|
||||||
|
|
||||||
|
if (typeof input === "string") {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
|
||||||
|
// Patrón "date-only" ISO (sin hora)
|
||||||
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
||||||
|
if (m) {
|
||||||
|
const [, y, mo, d] = m;
|
||||||
|
// new Date(y, m-1, d) crea la fecha en hora local -> evitamos shift
|
||||||
|
return new Date(Number(y), Number(mo) - 1, Number(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cualquier otra cadena se delega al parser nativo
|
||||||
|
const t = Date.parse(trimmed);
|
||||||
|
if (Number.isNaN(t)) return null;
|
||||||
|
return new Date(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DateHelper = {
|
||||||
|
format: formatDate,
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from "./date-helper";
|
||||||
export * from "./dto-compare-helper";
|
export * from "./dto-compare-helper";
|
||||||
export * from "./money-dto-helper";
|
export * from "./money-dto-helper";
|
||||||
export * from "./money-helper";
|
export * from "./money-helper";
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import Dinero, { Currency } from "dinero.js";
|
import Dinero, { type Currency } from "dinero.js";
|
||||||
import { MoneyDTO } from "../dto";
|
|
||||||
|
import type { MoneyDTO } from "../dto";
|
||||||
|
|
||||||
type DineroPlain = { amount: number; precision: number; currency: string };
|
type DineroPlain = { amount: number; precision: number; currency: string };
|
||||||
|
|
||||||
@ -28,10 +29,54 @@ const toNumericString = (dto?: MoneyDTO | null, fallbackScale = 2): string => {
|
|||||||
return toNumber(dto, fallbackScale).toString();
|
return toNumber(dto, fallbackScale).toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatea un MoneyDTO según un locale.
|
||||||
|
*
|
||||||
|
* @param dto - DTO de porcentaje (p.ej. value="1250", scale="2" representa 12.50).
|
||||||
|
* @param locale - Locale BCP-47 (p.ej. "es-ES", "en-US"). Si no se pasa, usa el del runtime.
|
||||||
|
* @param options - Opciones de `Intl.NumberFormat`. Si no se indican `minimumFractionDigits`/`maximumFractionDigits`,
|
||||||
|
* se infieren del `scale` del DTO. Puedes sobreespecificarlas aquí.
|
||||||
|
* @param fallbackScale - Escala a usar si el DTO no la trae (por defecto 2).
|
||||||
|
* @returns Cadena formateada (p.ej. "12,50 €") o `""` si el DTO está vacío.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const format = (
|
||||||
|
dto: MoneyDTO,
|
||||||
|
locale?: string,
|
||||||
|
options: { hideZeros?: boolean } & Intl.NumberFormatOptions = { hideZeros: false },
|
||||||
|
fallbackScale = 2
|
||||||
|
): string => {
|
||||||
|
const normalizedDTO = normalizeDTO(dto);
|
||||||
|
|
||||||
|
if (isEmptyMoneyDTO(normalizedDTO)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = Number(normalizedDTO?.scale ?? fallbackScale);
|
||||||
|
|
||||||
|
// Respetar fracciones si no vienen dadas en options.
|
||||||
|
const nfOptions: Intl.NumberFormatOptions = {
|
||||||
|
style: "currency",
|
||||||
|
currency: normalizedDTO.currency_code,
|
||||||
|
minimumFractionDigits: options?.minimumFractionDigits ?? scale,
|
||||||
|
maximumFractionDigits: options?.maximumFractionDigits ?? scale,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const absolute = toNumber(normalizedDTO, fallbackScale); // ej. 12.5
|
||||||
|
|
||||||
|
if (options.hideZeros && absolute === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Intl.NumberFormat(locale, nfOptions).format(absolute);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte número a MoneyDTO.
|
* Convierte número a MoneyDTO.
|
||||||
*/
|
*/
|
||||||
const fromNumber = (amount: number, currency: string = "EUR", scale = 2): MoneyDTO => {
|
const fromNumber = (amount: number, currency = "EUR", scale = 2): MoneyDTO => {
|
||||||
return {
|
return {
|
||||||
value: String(Math.round(amount * 10 ** scale)),
|
value: String(Math.round(amount * 10 ** scale)),
|
||||||
scale: String(scale),
|
scale: String(scale),
|
||||||
@ -42,7 +87,7 @@ const fromNumber = (amount: number, currency: string = "EUR", scale = 2): MoneyD
|
|||||||
/**
|
/**
|
||||||
* Convierte cadena numérica a MoneyDTO.
|
* Convierte cadena numérica a MoneyDTO.
|
||||||
*/
|
*/
|
||||||
const fromNumericString = (amount?: string, currency: string = "EUR", scale = 2): MoneyDTO => {
|
const fromNumericString = (amount?: string, currency = "EUR", scale = 2): MoneyDTO => {
|
||||||
if (!amount || amount?.trim?.() === "") {
|
if (!amount || amount?.trim?.() === "") {
|
||||||
return {
|
return {
|
||||||
value: "",
|
value: "",
|
||||||
@ -67,22 +112,13 @@ const normalizeDTO = (dto: MoneyDTO, fallbackCurrency = "EUR"): Required<MoneyDT
|
|||||||
return { value: v, scale: s, currency_code: c };
|
return { value: v, scale: s, currency_code: c };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatea un MoneyDTO según locale.
|
|
||||||
*/
|
|
||||||
const formatDTO = (dto: MoneyDTO, locale: string = "es-ES"): string => {
|
|
||||||
const { value, scale, currency_code } = normalizeDTO(dto);
|
|
||||||
const num = Number(value) / 10 ** Number(scale);
|
|
||||||
return new Intl.NumberFormat(locale, { style: "currency", currency: currency_code }).format(num);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MoneyDTOHelper = {
|
export const MoneyDTOHelper = {
|
||||||
isEmpty: isEmptyMoneyDTO,
|
isEmpty: isEmptyMoneyDTO,
|
||||||
toNumber,
|
toNumber,
|
||||||
toNumericString,
|
toNumericString,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
formatDTO,
|
format,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,12 +2,7 @@
|
|||||||
* Funciones para manipular valores monetarios numéricos.
|
* Funciones para manipular valores monetarios numéricos.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const formatCurrency = (
|
export const formatCurrency = (amount: number, scale = 2, currency = "EUR", locale = "es-ES") => {
|
||||||
amount: number,
|
|
||||||
scale: number = 2,
|
|
||||||
currency = "EUR",
|
|
||||||
locale = "es-ES"
|
|
||||||
) => {
|
|
||||||
return new Intl.NumberFormat(locale, {
|
return new Intl.NumberFormat(locale, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency,
|
currency,
|
||||||
@ -25,7 +20,7 @@ export const formatCurrency = (
|
|||||||
*/
|
*/
|
||||||
export const stripCurrencySymbols = (s: string): string =>
|
export const stripCurrencySymbols = (s: string): string =>
|
||||||
s
|
s
|
||||||
.replace(/[^\d.,\-]/g, "")
|
.replace(/[^\d.,-]/g, "")
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { PercentageDTO } from "../dto";
|
import type { PercentageDTO } from "../dto";
|
||||||
|
|
||||||
const isEmptyPercentageDTO = (dto?: PercentageDTO | null) =>
|
const isEmptyPercentageDTO = (dto?: PercentageDTO | null) =>
|
||||||
!dto || dto.value?.trim?.() === "" || dto.scale?.trim?.() === "";
|
!dto || dto.value?.trim?.() === "" || dto.scale?.trim?.() === "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte un QuantityDTO a número con precisión.
|
* Convierte un PercentageDTO a número con precisión.
|
||||||
*/
|
*/
|
||||||
const toNumber = (dto?: PercentageDTO | null, fallbackScale = 2): number => {
|
const toNumber = (dto?: PercentageDTO | null, fallbackScale = 2): number => {
|
||||||
if (isEmptyPercentageDTO(dto)) {
|
if (isEmptyPercentageDTO(dto)) {
|
||||||
@ -15,7 +15,7 @@ const toNumber = (dto?: PercentageDTO | null, fallbackScale = 2): number => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte un QuantityDTO a cadena numérica con precisión.
|
* Convierte un PercentageDTO a cadena numérica con precisión.
|
||||||
* Puede devolver cadena vacía
|
* Puede devolver cadena vacía
|
||||||
*/
|
*/
|
||||||
const toNumericString = (dto?: PercentageDTO | null, fallbackScale = 2): string => {
|
const toNumericString = (dto?: PercentageDTO | null, fallbackScale = 2): string => {
|
||||||
@ -26,7 +26,46 @@ const toNumericString = (dto?: PercentageDTO | null, fallbackScale = 2): string
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte número a QuantityDTO.
|
* Formatea un PercentageDTO según un locale.
|
||||||
|
*
|
||||||
|
* @param dto - DTO de porcentaje (p.ej. value="1250", scale="2" representa 12.50).
|
||||||
|
* @param locale - Locale BCP-47 (p.ej. "es-ES", "en-US"). Si no se pasa, usa el del runtime.
|
||||||
|
* @param options - Opciones de `Intl.NumberFormat`. Si no se indican `minimumFractionDigits`/`maximumFractionDigits`,
|
||||||
|
* se infieren del `scale` del DTO. Puedes sobreespecificarlas aquí.
|
||||||
|
* @param fallbackScale - Escala a usar si el DTO no la trae (por defecto 2).
|
||||||
|
* @returns Cadena formateada (p.ej. "12,50 %") o `""` si el DTO está vacío.
|
||||||
|
*
|
||||||
|
* Nota: `Intl.NumberFormat` con `style:"percent"` espera una fracción (0.125 → "12,5 %").
|
||||||
|
* Este helper convierte el valor absoluto del DTO (12.5) a fracción dividiendo entre 100.
|
||||||
|
*/
|
||||||
|
const format = (
|
||||||
|
dto: PercentageDTO,
|
||||||
|
locale?: string,
|
||||||
|
options?: Intl.NumberFormatOptions,
|
||||||
|
fallbackScale = 2
|
||||||
|
): string => {
|
||||||
|
if (isEmptyPercentageDTO(dto)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = Number(dto?.scale ?? fallbackScale);
|
||||||
|
|
||||||
|
// Respetar fracciones si no vienen dadas en options.
|
||||||
|
const nfOptions: Intl.NumberFormatOptions = {
|
||||||
|
style: "percent",
|
||||||
|
minimumFractionDigits: options?.minimumFractionDigits ?? scale,
|
||||||
|
maximumFractionDigits: options?.maximumFractionDigits ?? scale,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const absolute = toNumber(dto, fallbackScale); // ej. 12.5
|
||||||
|
const fraction = absolute / 100; // ej. 0.125 para Intl percent
|
||||||
|
|
||||||
|
return new Intl.NumberFormat(locale, nfOptions).format(fraction);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte número a PercentageDTO.
|
||||||
*/
|
*/
|
||||||
const fromNumber = (amount: number, scale = 2): PercentageDTO => {
|
const fromNumber = (amount: number, scale = 2): PercentageDTO => {
|
||||||
return {
|
return {
|
||||||
@ -36,7 +75,7 @@ const fromNumber = (amount: number, scale = 2): PercentageDTO => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte cadena numérica a QuantityDTO.
|
* Convierte cadena numérica a PercentageDTO.
|
||||||
*/
|
*/
|
||||||
const fromNumericString = (amount?: string, scale = 2): PercentageDTO => {
|
const fromNumericString = (amount?: string, scale = 2): PercentageDTO => {
|
||||||
if (!amount || amount?.trim?.() === "") {
|
if (!amount || amount?.trim?.() === "") {
|
||||||
@ -57,4 +96,5 @@ export const PercentageDTOHelper = {
|
|||||||
toNumericString,
|
toNumericString,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
|
format,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { QuantityDTO } from "../dto";
|
import type { QuantityDTO } from "../dto";
|
||||||
|
|
||||||
const isEmptyQuantityDTO = (dto?: QuantityDTO | null) =>
|
const isEmptyQuantityDTO = (dto?: QuantityDTO | null) =>
|
||||||
!dto || dto.value?.trim?.() === "" || dto.scale?.trim?.() === "";
|
!dto || dto.value?.trim?.() === "" || dto.scale?.trim?.() === "";
|
||||||
@ -25,6 +25,42 @@ const toNumericString = (dto?: QuantityDTO | null, fallbackScale = 2): string =>
|
|||||||
return toNumber(dto, fallbackScale).toString();
|
return toNumber(dto, fallbackScale).toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatea un QuantityDTO según un locale.
|
||||||
|
*
|
||||||
|
* @param dto - DTO de porcentaje (p.ej. value="1250", scale="2" representa 12.50).
|
||||||
|
* @param locale - Locale BCP-47 (p.ej. "es-ES", "en-US"). Si no se pasa, usa el del runtime.
|
||||||
|
* @param options - Opciones de `Intl.NumberFormat`. Si no se indican `minimumFractionDigits`/`maximumFractionDigits`,
|
||||||
|
* se infieren del `scale` del DTO. Puedes sobreespecificarlas aquí.
|
||||||
|
* @param fallbackScale - Escala a usar si el DTO no la trae (por defecto 2).
|
||||||
|
* @returns Cadena formateada (p.ej. "12,50") o `""` si el DTO está vacío.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const format = (
|
||||||
|
dto: QuantityDTO,
|
||||||
|
locale?: string,
|
||||||
|
options?: Intl.NumberFormatOptions,
|
||||||
|
fallbackScale = 0
|
||||||
|
): string => {
|
||||||
|
if (isEmptyQuantityDTO(dto)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = Number(dto?.scale ?? fallbackScale);
|
||||||
|
|
||||||
|
// Respetar fracciones si no vienen dadas en options.
|
||||||
|
const nfOptions: Intl.NumberFormatOptions = {
|
||||||
|
style: "decimal",
|
||||||
|
minimumFractionDigits: options?.minimumFractionDigits ?? scale,
|
||||||
|
maximumFractionDigits: options?.maximumFractionDigits ?? scale,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const absolute = toNumber(dto, fallbackScale); // ej. 12.5
|
||||||
|
|
||||||
|
return new Intl.NumberFormat(locale, nfOptions).format(absolute);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte número a QuantityDTO.
|
* Convierte número a QuantityDTO.
|
||||||
*/
|
*/
|
||||||
@ -57,4 +93,5 @@ export const QuantityDTOHelper = {
|
|||||||
toNumericString,
|
toNumericString,
|
||||||
fromNumber,
|
fromNumber,
|
||||||
fromNumericString,
|
fromNumericString,
|
||||||
|
format,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user