fix(expenses): ajusted mileage logic
This commit is contained in:
parent
3b4dd9ddb5
commit
f8f4ad5a83
|
|
@ -1,21 +1,19 @@
|
|||
import { Transform, Type } from "class-transformer";
|
||||
import { IsDefined, IsNumber, IsOptional, IsString, maxLength, MaxLength, Min, ValidateIf, ValidateNested } from "class-validator";
|
||||
import { IsNumber, IsOptional, IsString, MaxLength, Min, ValidateIf, ValidateNested } from "class-validator";
|
||||
|
||||
export class ExpensePayloadDto {
|
||||
@IsString()
|
||||
type!: string;
|
||||
|
||||
@ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
||||
@IsDefined()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
amount!: number;
|
||||
amount?: number;
|
||||
|
||||
@ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
||||
@IsDefined()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
mileage!: number;
|
||||
mileage?: number;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(280)
|
||||
|
|
|
|||
|
|
@ -72,32 +72,60 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
orderBy: [{ date: 'asc' }, { id: 'asc' }],
|
||||
});
|
||||
|
||||
return rows.map(this.mapDbToDayResponse);
|
||||
return rows.map((r) =>
|
||||
this.mapDbToDayResponse({
|
||||
date: r.date,
|
||||
amount: r.amount ?? 0,
|
||||
mileage: r.mileage ?? 0,
|
||||
comment: r.comment,
|
||||
is_approved: r.is_approved,
|
||||
bank_code: r.bank_code,
|
||||
}));
|
||||
};
|
||||
|
||||
const normalizePayload = async (payload: {
|
||||
type: string;
|
||||
amount?: number;
|
||||
mileage?: number;
|
||||
comment: string;
|
||||
type: string;
|
||||
amount?: number;
|
||||
mileage?: number;
|
||||
comment: string;
|
||||
attachment?: string;
|
||||
}): Promise<{
|
||||
type: string;
|
||||
type: string;
|
||||
bank_code_id: number;
|
||||
amount: Prisma.Decimal;
|
||||
comment: string;
|
||||
attachment: string | null;
|
||||
amount: Prisma.Decimal;
|
||||
mileage: number | null;
|
||||
comment: string;
|
||||
attachment: string | null;
|
||||
}> => {
|
||||
const type = this.normalizeType(payload.type);
|
||||
const comment = this.assertAndTrimComment(payload.comment);
|
||||
const type = this.normalizeType(payload.type);
|
||||
const comment = this.assertAndTrimComment(payload.comment);
|
||||
const attachment = payload.attachment?.trim()?.length ? payload.attachment.trim() : null;
|
||||
|
||||
const { id: bank_code_id, modifier } = await this.lookupBankCodeOrThrow(tx, type);
|
||||
const amount = this.computeAmountDecimal(type, payload, modifier);
|
||||
let amount = this.computeAmountDecimal(type, payload, modifier);
|
||||
let mileage: number | null = null;
|
||||
|
||||
if (type === 'MILEAGE') {
|
||||
mileage = Number(payload.mileage ?? 0);
|
||||
if (!(mileage > 0)) {
|
||||
throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE');
|
||||
}
|
||||
|
||||
const amountNumber = computeMileageAmount(mileage, modifier);
|
||||
amount = new Prisma.Decimal(amountNumber);
|
||||
|
||||
} else {
|
||||
if (!(typeof payload.amount === 'number' && payload.amount >= 0)) {
|
||||
throw new BadRequestException('Amount required for non-MILEAGE expense');
|
||||
}
|
||||
amount = new Prisma.Decimal(payload.amount);
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
bank_code_id,
|
||||
amount,
|
||||
amount,
|
||||
mileage,
|
||||
comment,
|
||||
attachment
|
||||
};
|
||||
|
|
@ -105,18 +133,20 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
|
||||
const findExactOld = async (norm: {
|
||||
bank_code_id: number;
|
||||
amount: Prisma.Decimal;
|
||||
comment: string;
|
||||
attachment: string | null;
|
||||
amount: Prisma.Decimal;
|
||||
mileage: number | null;
|
||||
comment: string;
|
||||
attachment: string | null;
|
||||
}) => {
|
||||
return tx.expenses.findFirst({
|
||||
where: {
|
||||
timesheet_id: timesheet_id,
|
||||
date: dateOnly,
|
||||
date: dateOnly,
|
||||
bank_code_id: norm.bank_code_id,
|
||||
amount: norm.amount,
|
||||
comment: norm.comment,
|
||||
attachment: norm.attachment,
|
||||
amount: norm.amount,
|
||||
comment: norm.comment,
|
||||
attachment: norm.attachment,
|
||||
...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }),
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
|
@ -136,24 +166,24 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
await tx.expenses.delete({where: { id: existing.id } });
|
||||
action = 'deleted';
|
||||
}
|
||||
//-------------------- CREATE --------------------
|
||||
//-------------------- CREATE --------------------
|
||||
else if (!old_expense && new_expense) {
|
||||
const new_exp = await normalizePayload(new_expense);
|
||||
await tx.expenses.create({
|
||||
data: {
|
||||
timesheet_id: timesheet_id,
|
||||
date: dateOnly,
|
||||
date: dateOnly,
|
||||
bank_code_id: new_exp.bank_code_id,
|
||||
amount: new_exp.amount,
|
||||
mileage: null,
|
||||
comment: new_exp.comment,
|
||||
attachment: new_exp.attachment,
|
||||
is_approved: false,
|
||||
amount: new_exp.amount,
|
||||
mileage: new_exp.mileage,
|
||||
comment: new_exp.comment,
|
||||
attachment: new_exp.attachment,
|
||||
is_approved: false,
|
||||
},
|
||||
});
|
||||
action = 'created';
|
||||
}
|
||||
|
||||
//-------------------- UPDATE --------------------
|
||||
else if(old_expense && new_expense) {
|
||||
const oldNorm = await normalizePayload(old_expense);
|
||||
const existing = await findExactOld(oldNorm);
|
||||
|
|
@ -169,10 +199,10 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
where: { id: existing.id },
|
||||
data: {
|
||||
bank_code_id: new_exp.bank_code_id,
|
||||
amount: new_exp.amount,
|
||||
mileage: null,
|
||||
comment: new_exp.comment,
|
||||
attachment: new_exp.attachment,
|
||||
amount: new_exp.amount,
|
||||
mileage: new_exp.mileage,
|
||||
comment: new_exp.comment,
|
||||
attachment: new_exp.attachment,
|
||||
},
|
||||
});
|
||||
action = 'updated';
|
||||
|
|
@ -188,7 +218,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
}
|
||||
|
||||
|
||||
//helpers imported from utils and repos.
|
||||
//-------------------- helpers --------------------
|
||||
private readonly normalizeType = (type: string): string =>
|
||||
normalizeTypeUtil(type);
|
||||
|
||||
|
|
@ -210,7 +240,10 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
|
||||
private readonly computeAmountDecimal = (
|
||||
type: string,
|
||||
payload: { amount?: number; mileage?: number;},
|
||||
payload: {
|
||||
amount?: number;
|
||||
mileage?: number;
|
||||
},
|
||||
modifier: number,
|
||||
): Prisma.Decimal => {
|
||||
if(type === 'MILEAGE') {
|
||||
|
|
@ -222,11 +255,12 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
};
|
||||
|
||||
private readonly mapDbToDayResponse = (row: {
|
||||
date: Date;
|
||||
amount: Prisma.Decimal | number | string;
|
||||
comment: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal | number | string;
|
||||
mileage: Prisma.Decimal | number | string;
|
||||
comment: string;
|
||||
is_approved: boolean;
|
||||
bank_code: { type: string } | null;
|
||||
bank_code: { type: string } | null;
|
||||
}): DayExpenseResponse => mapDbExpenseToDayResponse(row);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { BadRequestException } from "@nestjs/common";
|
||||
import { DayExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
//uppercase and trim for validation
|
||||
export function normalizeType(type: string): string {
|
||||
|
|
@ -48,18 +49,21 @@ export function toNumberSafe(value: DecimalLike): number {
|
|||
|
||||
//map of a row for DayExpenseResponse
|
||||
export function mapDbExpenseToDayResponse(row: {
|
||||
date: Date;
|
||||
amount: DecimalLike;
|
||||
comment: string;
|
||||
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;
|
||||
bank_code?: { type?: string | null } | null;
|
||||
}): DayExpenseResponse {
|
||||
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: toNumberSafe(row.amount),
|
||||
comment: row.comment,
|
||||
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) }: {}),
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user