import { BadRequestException } from "@nestjs/common"; import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces"; import { Prisma } from "@prisma/client"; //uppercase and trim for validation export function normalizeType(type: string): string { return (type ?? '').trim().toUpperCase(); }; //required comment after trim export function assertAndTrimComment(comment: string): string { const cmt = (comment ?? '').trim(); if(cmt.length === 0) { throw new BadRequestException('A comment is required'); } return cmt; }; //rounding $ to 2 decimals export function roundMoney2(num: number): number { return Math.round((num + Number.EPSILON) * 100)/ 100; }; export function computeMileageAmount(km: number, modifier: number): number { if(km < 0) throw new BadRequestException('mileage must be positive'); if(modifier < 0) throw new BadRequestException('modifier must be positive'); return roundMoney2(km * modifier); }; //compat. types with Prisma.Decimal. work around Prisma import in utils. export type DecimalLike = | number | string | { toNumber?: () => number } | { toString?: () => string }; //safe conversion to number export function toNumberSafe(value: DecimalLike): number { if(typeof value === 'number') return value; if(value && typeof (value as any).toNumber === 'function') return (value as any).toNumber(); return Number( typeof (value as any)?.toString === 'function' ? (value as any).toString() : value, ); } export const parseAttachmentId = (value: unknown): number | null => { if (value == null) { return null; } if (typeof value === 'number') { if (!Number.isInteger(value) || value <= 0) { throw new BadRequestException('Invalid attachment id'); } return value; } if (typeof value === 'string') { const trimmed = value.trim(); if (!trimmed.length) return null; if (!/^\d+$/.test(trimmed)) throw new BadRequestException('Invalid attachment id'); const parsed = Number(trimmed); if (parsed <= 0) throw new BadRequestException('Invalid attachment id'); return parsed; } throw new BadRequestException('Invalid attachment id'); }; //map of a row for DayExpenseResponse export function mapDbExpenseToDayResponse(row: { date: Date; amount: Prisma.Decimal | number | string | null; mileage?: Prisma.Decimal | number | string | null; comment: string; is_approved: boolean; bank_code?: { type?: string | null } | null; }): ExpenseResponse { const yyyyMmDd = row.date.toISOString().slice(0,10); const toNum = (value: any)=> (value == null ? 0 : Number(value)); return { date: yyyyMmDd, type: normalizeType(row.bank_code?.type ?? 'UNKNOWN'), amount: toNum(row.amount), comment: row.comment, is_approved: row.is_approved, ...(row.mileage !== null ? { mileage: toNum(row.mileage) }: {}), }; } export const computeAmountDecimal = ( type: string, payload: { amount?: number; mileage?: number; }, modifier: number, ): Prisma.Decimal => { if(type === 'MILEAGE') { const km = payload.mileage ?? 0; const amountNumber = computeMileageAmount(km, modifier); return new Prisma.Decimal(amountNumber); } return new Prisma.Decimal(payload.amount!); };