112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
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!);
|
|
};
|