refactor(expenses): commented old files, set up create update and delete expenses. set up findAll method and ajust query params of the timesheet controller
This commit is contained in:
parent
9270033f24
commit
af9d89da01
|
|
@ -57,6 +57,20 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/auth/me": {
|
||||
"get": {
|
||||
"operationId": "AuthController_getProfile",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Auth"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/employees/employee-list": {
|
||||
"get": {
|
||||
"operationId": "EmployeesController_findListEmployees",
|
||||
|
|
@ -153,8 +167,6 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"/employees/profile/{email}": {
|
||||
"get": {
|
||||
"operationId": "EmployeesController_findOneProfile",
|
||||
|
|
@ -195,381 +207,6 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/timesheets": {
|
||||
"get": {
|
||||
"operationId": "TimesheetsController_getPeriodByQuery",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "year",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "period_no",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Timesheets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/timesheets/{email}": {
|
||||
"get": {
|
||||
"operationId": "TimesheetsController_getByEmail",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Timesheets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/timesheets/shifts/{email}": {
|
||||
"post": {
|
||||
"operationId": "TimesheetsController_createTimesheetShifts",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateWeekShiftsDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Timesheets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/Expenses/upsert/{email}/{date}": {
|
||||
"put": {
|
||||
"operationId": "ExpensesController_upsert_by_date",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "date",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpsertExpenseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Expenses"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/Expenses/list/{email}/{year}/{period_no}": {
|
||||
"get": {
|
||||
"operationId": "ExpensesController_findExpenseListByPayPeriodAndEmail",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "year",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "period_no",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Expenses"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/upsert/{email}": {
|
||||
"put": {
|
||||
"operationId": "ShiftsController_upsert_by_date",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"required": true,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpsertShiftDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/delete/{email}/{date}": {
|
||||
"delete": {
|
||||
"operationId": "ShiftsController_remove",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "email",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "date",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpsertShiftDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/approval/{id}": {
|
||||
"patch": {
|
||||
"operationId": "ShiftsController_approve",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/summary": {
|
||||
"get": {
|
||||
"operationId": "ShiftsController_getSummary",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/export.csv": {
|
||||
"get": {
|
||||
"operationId": "ShiftsController_exportCsv",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
||||
"/notifications/summary": {
|
||||
"get": {
|
||||
"operationId": "NotificationsController_summary",
|
||||
|
|
@ -598,80 +235,6 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"/leave-requests/upsert": {
|
||||
"post": {
|
||||
"operationId": "LeaveRequestController_upsertLeaveRequest",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpsertLeaveRequestDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Leave Requests"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/v1/login": {
|
||||
"get": {
|
||||
"operationId": "AuthController_login",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Auth"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/callback": {
|
||||
"get": {
|
||||
"operationId": "AuthController_loginCallback",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Auth"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/me": {
|
||||
"get": {
|
||||
"operationId": "AuthController_getProfile",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Auth"
|
||||
]
|
||||
}
|
||||
},
|
||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
||||
"/oauth-sessions": {
|
||||
"post": {
|
||||
"operationId": "OauthSessionsController_create",
|
||||
|
|
@ -1327,29 +890,10 @@
|
|||
"first_work_day"
|
||||
]
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
"EmployeeProfileItemDto": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"CreateWeekShiftsDto": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"UpsertExpenseDto": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"UpsertShiftDto": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
"UpsertLeaveRequestDto": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
},
|
||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
||||
"CreateOauthSessionDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
10
src/modules/expenses/controllers/expense.controller.ts
Normal file
10
src/modules/expenses/controllers/expense.controller.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Controller } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
|
||||
@Controller('expense')
|
||||
export class ExpenseController {
|
||||
constructor(private readonly prisma: PrismaService){}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class CreateExpenseDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Unique ID of the expense (auto-generated)',
|
||||
})
|
||||
@Allow()
|
||||
id?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 101,
|
||||
description: 'ID number for a set timesheet',
|
||||
})
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
timesheet_id: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 7,
|
||||
description: 'ID number of an bank code (link with bank-codes)',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
bank_code_id: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '3018-10-20T00:00:00.000Z',
|
||||
description: 'Date where the expense was made',
|
||||
})
|
||||
@IsDateString()
|
||||
date: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 17.82,
|
||||
description: 'amount in $ for a refund',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
|
||||
@ApiProperty({
|
||||
example:'Spent for mileage between A and B',
|
||||
description:'explain`s why the expense was made'
|
||||
})
|
||||
@IsString()
|
||||
comment: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'DENIED, APPROUVED, PENDING, etc...',
|
||||
description: 'validation status',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
is_approved?: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
example:'Asked X to go there as an emergency response',
|
||||
description:'Supervisro`s justification for the spending of an employee'
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
supervisor_comment?: string;
|
||||
}
|
||||
0
src/modules/expenses/dtos/expense.dto.ts
Normal file
0
src/modules/expenses/dtos/expense.dto.ts
Normal file
0
src/modules/expenses/dtos/get-expense.dto.ts
Normal file
0
src/modules/expenses/dtos/get-expense.dto.ts
Normal file
|
|
@ -1,26 +0,0 @@
|
|||
import { Type } from "class-transformer";
|
||||
import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class SearchExpensesDto {
|
||||
@IsOptional()
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
timesheet_id?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
bank_code_id?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
comment_contains?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
start_date: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
end_date: string;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateExpenseDto } from "./create-expense.dto";
|
||||
|
||||
export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
MaxLength,
|
||||
Min,
|
||||
ValidateIf,
|
||||
ValidateNested
|
||||
} from "class-validator";
|
||||
|
||||
export class ExpensePayloadDto {
|
||||
@IsString()
|
||||
type!: string;
|
||||
|
||||
@ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
amount?: number;
|
||||
|
||||
@ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
mileage?: number;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(280)
|
||||
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||
comment!: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value === null || value === undefined || value === '') return undefined;
|
||||
if (typeof value === 'number') return value.toString();
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length ? trimmed : undefined;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
@IsString()
|
||||
@Matches(/^\d+$/)
|
||||
@MaxLength(255)
|
||||
attachment?: string;
|
||||
}
|
||||
|
||||
|
||||
export class UpsertExpenseDto {
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(()=> ExpensePayloadDto)
|
||||
old_expense?: ExpensePayloadDto;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(()=> ExpensePayloadDto)
|
||||
new_expense?: ExpensePayloadDto;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
export type UpsertAction = 'create' | 'update' | 'delete';
|
||||
|
||||
export interface ExpenseResponse {
|
||||
date: string;
|
||||
type: string;
|
||||
amount: number;
|
||||
comment: string;
|
||||
is_approved: boolean;
|
||||
};
|
||||
|
||||
export type UpsertExpenseResult = {
|
||||
action: UpsertAction;
|
||||
day: ExpenseResponse[]
|
||||
};
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
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!);
|
||||
};
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// import { ApiProperty } from "@nestjs/swagger";
|
||||
// import { Type } from "class-transformer";
|
||||
// import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
// export class CreateExpenseDto {
|
||||
// @ApiProperty({
|
||||
// example: 1,
|
||||
// description: 'Unique ID of the expense (auto-generated)',
|
||||
// })
|
||||
// @Allow()
|
||||
// id?: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 101,
|
||||
// description: 'ID number for a set timesheet',
|
||||
// })
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// timesheet_id: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 7,
|
||||
// description: 'ID number of an bank code (link with bank-codes)',
|
||||
// })
|
||||
// @Type(() => Number)
|
||||
// @IsInt()
|
||||
// bank_code_id: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: '3018-10-20T00:00:00.000Z',
|
||||
// description: 'Date where the expense was made',
|
||||
// })
|
||||
// @IsDateString()
|
||||
// date: string;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 17.82,
|
||||
// description: 'amount in $ for a refund',
|
||||
// })
|
||||
// @Type(() => Number)
|
||||
// @IsNumber()
|
||||
// amount: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example:'Spent for mileage between A and B',
|
||||
// description:'explain`s why the expense was made'
|
||||
// })
|
||||
// @IsString()
|
||||
// comment: string;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 'DENIED, APPROUVED, PENDING, etc...',
|
||||
// description: 'validation status',
|
||||
// })
|
||||
// @IsOptional()
|
||||
// @IsBoolean()
|
||||
// is_approved?: boolean;
|
||||
|
||||
// @ApiProperty({
|
||||
// example:'Asked X to go there as an emergency response',
|
||||
// description:'Supervisro`s justification for the spending of an employee'
|
||||
// })
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// supervisor_comment?: string;
|
||||
// }
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// export type UpsertAction = 'create' | 'update' | 'delete';
|
||||
|
||||
// export interface ExpenseResponse {
|
||||
// date: string;
|
||||
// type: string;
|
||||
// amount: number;
|
||||
// comment: string;
|
||||
// is_approved: boolean;
|
||||
// };
|
||||
|
||||
// export type UpsertExpenseResult = {
|
||||
// action: UpsertAction;
|
||||
// day: ExpenseResponse[]
|
||||
// };
|
||||
111
src/modules/expenses/~misc_deprecated-files/expenses.utils.ts
Normal file
111
src/modules/expenses/~misc_deprecated-files/expenses.utils.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// 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!);
|
||||
// };
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
||||
|
||||
// export class SearchExpensesDto {
|
||||
// @IsOptional()
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// timesheet_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// bank_code_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// comment_contains?: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsDateString()
|
||||
// start_date: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsDateString()
|
||||
// end_date: string;
|
||||
// }
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// import { PartialType } from "@nestjs/swagger";
|
||||
// import { CreateExpenseDto } from "./create-expense.dto";
|
||||
|
||||
// export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// import { Transform, Type } from "class-transformer";
|
||||
// import {
|
||||
// IsNumber,
|
||||
// IsOptional,
|
||||
// IsString,
|
||||
// Matches,
|
||||
// MaxLength,
|
||||
// Min,
|
||||
// ValidateIf,
|
||||
// ValidateNested
|
||||
// } from "class-validator";
|
||||
|
||||
// export class ExpensePayloadDto {
|
||||
// @IsString()
|
||||
// type!: string;
|
||||
|
||||
// @ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
||||
// @IsNumber()
|
||||
// @Min(0)
|
||||
// amount?: number;
|
||||
|
||||
// @ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
||||
// @IsNumber()
|
||||
// @Min(0)
|
||||
// mileage?: number;
|
||||
|
||||
// @IsString()
|
||||
// @MaxLength(280)
|
||||
// @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||
// comment!: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @Transform(({ value }) => {
|
||||
// if (value === null || value === undefined || value === '') return undefined;
|
||||
// if (typeof value === 'number') return value.toString();
|
||||
// if (typeof value === 'string') {
|
||||
// const trimmed = value.trim();
|
||||
// return trimmed.length ? trimmed : undefined;
|
||||
// }
|
||||
// return undefined;
|
||||
// })
|
||||
// @IsString()
|
||||
// @Matches(/^\d+$/)
|
||||
// @MaxLength(255)
|
||||
// attachment?: string;
|
||||
// }
|
||||
|
||||
|
||||
// export class UpsertExpenseDto {
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ExpensePayloadDto)
|
||||
// old_expense?: ExpensePayloadDto;
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ExpensePayloadDto)
|
||||
// new_expense?: ExpensePayloadDto;
|
||||
// }
|
||||
|
|
@ -1,17 +1,26 @@
|
|||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { GetTimesheetsOverviewService } from "../services/timesheet-get-overview.service";
|
||||
import { Controller, Get, Query} from "@nestjs/common";
|
||||
import { BadRequestException, Controller, Get, Query} from "@nestjs/common";
|
||||
|
||||
@Controller('timesheets')
|
||||
export class TimesheetController {
|
||||
constructor(private readonly timesheetOverview: GetTimesheetsOverviewService){}
|
||||
constructor(
|
||||
private readonly timesheetOverview: GetTimesheetsOverviewService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
){}
|
||||
|
||||
@Get()
|
||||
async getTimesheetByIds(
|
||||
@Query('timesheet_ids') timesheet_ids: string ) {
|
||||
const parsed = timesheet_ids.split(/,\s*/).map(value => Number(value)).filter(Number.isFinite);
|
||||
return this.timesheetOverview.getTimesheetsByIds(parsed);
|
||||
@Query('employee_email') employee_email: string,
|
||||
@Query('year') year: string,
|
||||
@Query('period_number') period_number: string,
|
||||
) {
|
||||
if (!employee_email || !year || !period_number) {
|
||||
throw new BadRequestException('Query params "employee_email", "year" and eriod_number" are required.');
|
||||
}
|
||||
const employee_id = await this.emailResolver.findIdByEmail(employee_email);
|
||||
const pay_year = Number(year);
|
||||
const period_num = Number(period_number);
|
||||
return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(employee_id, pay_year, period_num);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers";
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers";
|
||||
|
||||
type TotalHours = {
|
||||
regular: number;
|
||||
|
|
@ -23,26 +23,31 @@ type TotalExpenses = {
|
|||
export class GetTimesheetsOverviewService {
|
||||
constructor(private readonly prisma: PrismaService) { }
|
||||
|
||||
async getTimesheetsByIds(timesheet_ids: number[]) {
|
||||
if (!Array.isArray(timesheet_ids) || timesheet_ids.length === 0) throw new NotFoundException(`Timesheet_ids are missing`);
|
||||
async getTimesheetsForEmployeeByPeriod(employee_id: number, pay_year: number, pay_period_no: number) {
|
||||
//find period using year and period_no
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { pay_year, pay_period_no },
|
||||
});
|
||||
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${pay_period_no} not found`);
|
||||
|
||||
//fetch all needed data using timesheet ids
|
||||
const rows = await this.prisma.timesheets.findMany({
|
||||
where: { id: { in: timesheet_ids } },
|
||||
include: {
|
||||
employee: { include: { user: true } },
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true, attachment_record: true } },
|
||||
},
|
||||
orderBy: { start_date: 'asc' },
|
||||
//loads the timesheets related to the fetched pay-period
|
||||
const rows = await this.loadTimesheets({
|
||||
employee_id,
|
||||
start_date: { gte: period.period_start, lte: period.period_end },
|
||||
});
|
||||
|
||||
if (rows.length === 0) throw new NotFoundException('Timesheet(s) not found');
|
||||
|
||||
//build full name
|
||||
const user = rows[0].employee.user;
|
||||
//find user infos using the employee_id
|
||||
const employee = await this.prisma.employees.findUnique({
|
||||
where: { id: employee_id },
|
||||
include: { user: true },
|
||||
});
|
||||
if (!employee) throw new NotFoundException(`Employee #${employee_id} not found`);
|
||||
|
||||
//builds employee full name
|
||||
const user = employee.user;
|
||||
const employee_fullname = `${user.first_name} ${user.last_name}`.trim();
|
||||
|
||||
//maps all timesheet's infos
|
||||
const timesheets = rows.map((timesheet) => this.mapOneTimesheet(timesheet));
|
||||
return { employee_fullname, timesheets };
|
||||
}
|
||||
|
|
@ -51,16 +56,29 @@ export class GetTimesheetsOverviewService {
|
|||
//-----------------------------------------------------------------------------------
|
||||
// MAPPERS & HELPERS
|
||||
//-----------------------------------------------------------------------------------
|
||||
//fetch timesheet's infos
|
||||
private async loadTimesheets(where: any) {
|
||||
return this.prisma.timesheets.findMany({
|
||||
where,
|
||||
include: {
|
||||
employee: { include: { user: true } },
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true, attachment_record: true } },
|
||||
},
|
||||
orderBy: { start_date: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
private mapOneTimesheet(timesheet: any) {
|
||||
//converts string to UTC date format
|
||||
const start = toDateFromString(timesheet.start_date);
|
||||
const start = toDateFromString(timesheet.start_date);
|
||||
const day_dates = sevenDaysFrom(start);
|
||||
|
||||
//map of shifts by days
|
||||
const shifts_by_date = new Map<string, any[]>();
|
||||
for (const shift of timesheet.shift) {
|
||||
const date = toStringFromDate(shift.date);
|
||||
const arr = shifts_by_date.get(date) ?? [];
|
||||
const arr = shifts_by_date.get(date) ?? [];
|
||||
arr.push(shift);
|
||||
shifts_by_date.set(date, arr);
|
||||
}
|
||||
|
|
@ -68,40 +86,40 @@ export class GetTimesheetsOverviewService {
|
|||
const expenses_by_date = new Map<string, any[]>();
|
||||
for (const expense of timesheet.expense) {
|
||||
const date = toStringFromDate(expense.date);
|
||||
const arr = expenses_by_date.get(date) ?? [];
|
||||
const arr = expenses_by_date.get(date) ?? [];
|
||||
arr.push(expense);
|
||||
expenses_by_date.set(date, arr);
|
||||
}
|
||||
//weekly totals
|
||||
const weekly_hours: TotalHours[] = [emptyHours()];
|
||||
const weekly_hours: TotalHours[] = [emptyHours()];
|
||||
const weekly_expenses: TotalExpenses[] = [emptyExpenses()];
|
||||
|
||||
//map of days
|
||||
const days = day_dates.map((date) => {
|
||||
const date_iso = toStringFromDate(date);
|
||||
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
||||
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
||||
const expenses_source = expenses_by_date.get(date_iso) ?? [];
|
||||
//inner map of shifts
|
||||
const shifts = shifts_source.map((shift) => ({
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toHHmmFromDate(shift.start_time),
|
||||
end_time: toHHmmFromDate(shift.end_time),
|
||||
type: shift.bank_code?.type ?? '',
|
||||
is_remote: shift.is_remote ?? false,
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toHHmmFromDate(shift.start_time),
|
||||
end_time: toHHmmFromDate(shift.end_time),
|
||||
type: shift.bank_code?.type ?? '',
|
||||
is_remote: shift.is_remote ?? false,
|
||||
is_approved: shift.is_approved ?? false,
|
||||
shift_id: shift.id ?? null,
|
||||
comment: shift.comment ?? null,
|
||||
shift_id: shift.id ?? null,
|
||||
comment: shift.comment ?? null,
|
||||
}));
|
||||
|
||||
//inner map of expenses
|
||||
const expenses = expenses_source.map((expense) => ({
|
||||
date: toStringFromDate(expense.date),
|
||||
amount: expense.amount ? Number(expense.amount) : undefined,
|
||||
mileage: expense.mileage ? Number(expense.mileage) : undefined,
|
||||
expense_id: expense.id ?? null,
|
||||
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
||||
date: toStringFromDate(expense.date),
|
||||
amount: expense.amount ? Number(expense.amount) : undefined,
|
||||
mileage: expense.mileage ? Number(expense.mileage) : undefined,
|
||||
expense_id: expense.id ?? null,
|
||||
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
||||
is_approved: expense.is_approved ?? false,
|
||||
comment: expense.comment ?? '',
|
||||
comment: expense.comment ?? '',
|
||||
supervisor_comment: expense.supervisor_comment,
|
||||
}));
|
||||
|
||||
|
|
@ -113,7 +131,7 @@ export class GetTimesheetsOverviewService {
|
|||
for (const shift of shifts_source) {
|
||||
const hours = diffOfHours(shift.start_time, shift.end_time);
|
||||
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
|
||||
daily_hours[0][subgroup] += hours;
|
||||
daily_hours[0][subgroup] += hours;
|
||||
weekly_hours[0][subgroup] += hours;
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +166,7 @@ export class GetTimesheetsOverviewService {
|
|||
});
|
||||
return {
|
||||
timesheet_id: timesheet.id,
|
||||
is_approved: timesheet.is_approved ?? false,
|
||||
is_approved: timesheet.is_approved ?? false,
|
||||
days,
|
||||
weekly_hours,
|
||||
weekly_expenses,
|
||||
|
|
@ -156,37 +174,36 @@ export class GetTimesheetsOverviewService {
|
|||
}
|
||||
}
|
||||
|
||||
const emptyHours = (): TotalHours => {
|
||||
return { regular: 0, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0 };
|
||||
}
|
||||
const emptyExpenses = (): TotalExpenses => {
|
||||
return { expenses: 0, per_diem: 0, on_call: 0, mileage: 0 };
|
||||
}
|
||||
//filled array with default values
|
||||
const emptyHours = (): TotalHours => { return { regular: 0, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0 } };
|
||||
const emptyExpenses = (): TotalExpenses => { return { expenses: 0, per_diem: 0, on_call: 0, mileage: 0 } };
|
||||
|
||||
//calculate the differences of hours
|
||||
const diffOfHours = (a: Date, b: Date): number => {
|
||||
const ms = new Date(b).getTime() - new Date(a).getTime();
|
||||
return Math.max(0, Math.round((ms / 36e5) * 1000) / 1000);
|
||||
}
|
||||
|
||||
const num = (value: any): number => {
|
||||
return value ? Number(value) : 0;
|
||||
}
|
||||
//validate numeric values
|
||||
const num = (value: any): number => { return value ? Number(value) : 0 };
|
||||
|
||||
// shift's subgroup types
|
||||
const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
|
||||
const type = bank_code.type;
|
||||
if (type.includes('EVENING')) return 'evening';
|
||||
if (type.includes('EVENING')) return 'evening';
|
||||
if (type.includes('EMERGENCY')) return 'emergency';
|
||||
if (type.includes('OVERTIME')) return 'overtime';
|
||||
if (type.includes('VACATION')) return 'vacation';
|
||||
if (type.includes('HOLIDAY')) return 'holiday';
|
||||
if (type.includes('SICK')) return 'sick';
|
||||
if (type.includes('OVERTIME')) return 'overtime';
|
||||
if (type.includes('VACATION')) return 'vacation';
|
||||
if (type.includes('HOLIDAY')) return 'holiday';
|
||||
if (type.includes('SICK')) return 'sick';
|
||||
return 'regular'
|
||||
}
|
||||
|
||||
// expense's subgroup types
|
||||
const expenseSubgroupFromBankCode = (bank_code: any): keyof TotalExpenses => {
|
||||
const type = bank_code.type;
|
||||
if (type.includes('MILEAGE')) return 'mileage';
|
||||
if (type.includes('MILEAGE')) return 'mileage';
|
||||
if (type.includes('PER_DIEM')) return 'per_diem';
|
||||
if (type.includes('ON_CALL')) return 'on_call';
|
||||
if (type.includes('ON_CALL')) return 'on_call';
|
||||
return 'expenses';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ import { Module } from '@nestjs/common';
|
|||
providers: [
|
||||
TimesheetArchiveService,
|
||||
GetTimesheetsOverviewService,
|
||||
SharedModule,
|
||||
],
|
||||
exports: [
|
||||
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class TimesheetsModule {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user