feat(timesheet): added Post function to create a new shifts inside a timesheet
This commit is contained in:
parent
5063c1dfec
commit
4f7563ce9b
|
|
@ -542,53 +542,6 @@
|
|||
"Timesheets"
|
||||
]
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "TimesheetsController_update",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateTimesheetDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Timesheet updated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateTimesheetDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Timesheet not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"summary": "Update timesheet",
|
||||
"tags": [
|
||||
"Timesheets"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "TimesheetsController_remove",
|
||||
"parameters": [
|
||||
|
|
@ -2408,48 +2361,7 @@
|
|||
},
|
||||
"CreateTimesheetDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"example": 1,
|
||||
"description": "timesheet`s unique ID (auto-generated)"
|
||||
},
|
||||
"employee_id": {
|
||||
"type": "number",
|
||||
"example": 426433,
|
||||
"description": "employee`s ID number of linked timsheet"
|
||||
},
|
||||
"is_approved": {
|
||||
"type": "boolean",
|
||||
"example": true,
|
||||
"description": "Timesheet`s status approval"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"employee_id",
|
||||
"is_approved"
|
||||
]
|
||||
},
|
||||
"UpdateTimesheetDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"example": 1,
|
||||
"description": "timesheet`s unique ID (auto-generated)"
|
||||
},
|
||||
"employee_id": {
|
||||
"type": "number",
|
||||
"example": 426433,
|
||||
"description": "employee`s ID number of linked timsheet"
|
||||
},
|
||||
"is_approved": {
|
||||
"type": "boolean",
|
||||
"example": true,
|
||||
"description": "Timesheet`s status approval"
|
||||
}
|
||||
}
|
||||
"properties": {}
|
||||
},
|
||||
"CreateExpenseDto": {
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -250,10 +250,10 @@ export class PayPeriodsQueryService {
|
|||
const amount = toMoney(expense.amount);
|
||||
record.expenses += amount;
|
||||
|
||||
const categorie = (expense.bank_code?.categorie || "").toUpperCase();
|
||||
const type = (expense.bank_code?.type || "").toUpperCase();
|
||||
const rate = expense.bank_code?.modifier ?? 0;
|
||||
if (categorie === "MILEAGE" && rate > 0) {
|
||||
record.mileage += amount / rate;
|
||||
if (type === "MILEAGE" && rate > 0) {
|
||||
record.mileage += Math.round((amount / rate)/100)*100;
|
||||
}
|
||||
record.is_approved = record.is_approved && expense.timesheet.is_approved;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { BadRequestException, Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from '@nestjs/common';
|
||||
import { BadRequestException, Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
|
||||
import { TimesheetsQueryService } from '../services/timesheets-query.service';
|
||||
import { CreateTimesheetDto } from '../dtos/create-timesheet.dto';
|
||||
import { CreateTimesheetDto, CreateWeekShiftsDto } from '../dtos/create-timesheet.dto';
|
||||
import { Timesheets } from '@prisma/client';
|
||||
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
|
@ -20,15 +19,6 @@ export class TimesheetsController {
|
|||
private readonly timesheetsCommand: TimesheetsCommandService,
|
||||
) {}
|
||||
|
||||
// @Post()
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Create timesheet' })
|
||||
// @ApiResponse({ status: 201, description: 'Timesheet created', type: CreateTimesheetDto })
|
||||
// @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
||||
// create(@Body() dto: CreateTimesheetDto): Promise<Timesheets> {
|
||||
// return this.timesheetsQuery.create(dto);
|
||||
// }
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
async getPeriodByQuery(
|
||||
|
|
@ -49,6 +39,17 @@ export class TimesheetsController {
|
|||
return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
|
||||
}
|
||||
|
||||
@Post('shifts/:email')
|
||||
async createTimesheetShifts(
|
||||
@Param('email') email: string,
|
||||
@Body() dto: CreateWeekShiftsDto,
|
||||
@Query('offset') offset?: string,
|
||||
): Promise<TimesheetDto> {
|
||||
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
||||
return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
|
||||
}
|
||||
|
||||
|
||||
@Get(':id')
|
||||
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Find timesheet' })
|
||||
|
|
@ -58,18 +59,6 @@ export class TimesheetsController {
|
|||
return this.timesheetsQuery.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Update timesheet' })
|
||||
@ApiResponse({ status: 201, description: 'Timesheet updated', type: CreateTimesheetDto })
|
||||
@ApiResponse({ status: 400, description: 'Timesheet not found' })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id:number,
|
||||
@Body() dto: UpdateTimesheetDto,
|
||||
): Promise<Timesheets> {
|
||||
return this.timesheetsQuery.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
// @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Delete timesheet' })
|
||||
|
|
|
|||
|
|
@ -1,28 +1,33 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { Allow, IsBoolean, IsInt, IsOptional } from "class-validator";
|
||||
import { IsArray, IsOptional, IsString, Length, Matches, ValidateNested } from "class-validator";
|
||||
|
||||
export class CreateTimesheetDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'timesheet`s unique ID (auto-generated)',
|
||||
})
|
||||
@Allow()
|
||||
id?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 426433,
|
||||
description: 'employee`s ID number of linked timsheet',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
employee_id: number;
|
||||
@IsString()
|
||||
@Matches(/^\d{4}-\d{2}-\d{2}$/)
|
||||
date!: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Timesheet`s status approval',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
is_approved?: boolean;
|
||||
@IsString()
|
||||
@Length(1,64)
|
||||
type!: string;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^\d{2}:\d{2}$/)
|
||||
start_time!: string;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^\d{2}:\d{2}$/)
|
||||
end_time!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Length(0,512)
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class CreateWeekShiftsDto {
|
||||
@IsArray()
|
||||
@ValidateNested({each:true})
|
||||
@Type(()=> CreateTimesheetDto)
|
||||
shifts!: CreateTimesheetDto[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Prisma, Timesheets } from "@prisma/client";
|
||||
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { TimesheetsQueryService } from "./timesheets-query.service";
|
||||
import { CreateTimesheetDto } from "../dtos/create-timesheet.dto";
|
||||
import { TimesheetDto } from "../dtos/overview-timesheet.dto";
|
||||
import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
|
||||
|
||||
@Injectable()
|
||||
export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
||||
constructor(prisma: PrismaService) {super(prisma);}
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
private readonly query: TimesheetsQueryService,
|
||||
) {super(prisma);}
|
||||
|
||||
protected get delegate() {
|
||||
return this.prisma.timesheets;
|
||||
}
|
||||
|
||||
protected delegateFor(transaction: Prisma.TransactionClient) {
|
||||
return transaction.timesheets;
|
||||
}
|
||||
|
|
@ -37,4 +45,83 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
|||
return timesheet;
|
||||
}
|
||||
|
||||
|
||||
//create shifts within timesheet's week - employee overview functions
|
||||
private parseISODate(iso: string): Date {
|
||||
const [ y, m, d ] = iso.split('-').map(Number);
|
||||
return new Date(y, (m ?? 1) - 1, d ?? 1);
|
||||
}
|
||||
|
||||
private parseHHmm(t: string): Date {
|
||||
const [ hh, mm ] = t.split(':').map(Number);
|
||||
return new Date(1970, 0, 1, hh || 0, mm || 0, 0, 0);
|
||||
}
|
||||
|
||||
async createWeekShiftsAndReturnOverview(
|
||||
email:string,
|
||||
shifts: CreateTimesheetDto[],
|
||||
week_offset = 0,
|
||||
): Promise<TimesheetDto> {
|
||||
|
||||
//match user's email with email
|
||||
const user = await this.prisma.users.findUnique({
|
||||
where: { email },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!user) throw new NotFoundException(`user with email ${email} not found`);
|
||||
|
||||
//fetchs employee matchint user's email
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: { user_id: user?.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`employee for ${ email } not found`);
|
||||
|
||||
//insure that the week starts on sunday and finishes on saturday
|
||||
const base = new Date();
|
||||
base.setDate(base.getDate() + week_offset * 7);
|
||||
const start_week = getWeekStart(base, 0);
|
||||
const end_week = getWeekEnd(start_week);
|
||||
|
||||
const timesheet = await this.prisma.timesheets.upsert({
|
||||
where: {
|
||||
employee_id_start_date: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_week,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_week,
|
||||
is_approved: false,
|
||||
},
|
||||
update: {},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
//validations and insertions
|
||||
for(const shift of shifts) {
|
||||
const date = this.parseISODate(shift.date);
|
||||
if (date < start_week || date > end_week) throw new BadRequestException(`date ${shift.date} not in current week`);
|
||||
|
||||
const bank_code = await this.prisma.bankCodes.findFirst({
|
||||
where: { type: shift.type },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!bank_code) throw new BadRequestException(`Invalid bank_code type: ${shift.type}`);
|
||||
|
||||
await this.prisma.shifts.create({
|
||||
data: {
|
||||
timesheet_id: timesheet.id,
|
||||
bank_code_id: bank_code.id,
|
||||
date: date,
|
||||
start_time: this.parseHHmm(shift.start_time),
|
||||
end_time: this.parseHHmm(shift.end_time),
|
||||
description: shift.description ?? null,
|
||||
is_approved: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
return this.query.getTimesheetByEmail(email, week_offset);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
|||
import { buildPeriod, endOfDayUTC, toUTCDateOnly } from '../utils/timesheet.helpers';
|
||||
import type { ShiftRow, ExpenseRow } from '../utils/timesheet.helpers';
|
||||
import { TimesheetDto } from '../dtos/overview-timesheet.dto';
|
||||
import { CreateWeekShiftsDto } from '../dtos/create-timesheet.dto';
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -219,19 +220,20 @@ export class TimesheetsQueryService {
|
|||
return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours };
|
||||
}
|
||||
|
||||
async update(id: number, dto:UpdateTimesheetDto): Promise<Timesheets> {
|
||||
await this.findOne(id);
|
||||
const { employee_id, is_approved } = dto;
|
||||
return this.prisma.timesheets.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(employee_id !== undefined && { employee_id }),
|
||||
...(is_approved !== undefined && { is_approved }),
|
||||
},
|
||||
include: { employee: { include: { user: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
//deprecated
|
||||
// async update(id: number, dto:UpdateTimesheetDto): Promise<Timesheets> {
|
||||
// await this.findOne(id);
|
||||
// const { employee_id, is_approved } = dto;
|
||||
// return this.prisma.timesheets.update({
|
||||
// where: { id },
|
||||
// data: {
|
||||
// ...(employee_id !== undefined && { employee_id }),
|
||||
// ...(is_approved !== undefined && { is_approved }),
|
||||
// },
|
||||
// include: { employee: { include: { user: true } },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
async remove(id: number): Promise<Timesheets> {
|
||||
await this.findOne(id);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user