Compare commits

...

13 Commits

Author SHA1 Message Date
louispaulb
9999dff6ce Fix critical bugs: transaction integrity, PTO calculations, session secret
Some checks failed
Node-CI / test (push) Successful in 1m25s
Node-CI / lint (push) Successful in 1m41s
Node-CI / build (push) Failing after 2m4s
- banking-hours.service: use tx instead of this.prisma inside transaction
- sick-leave.service: use tx inside transaction + increment instead of set
- vacation.service: remove invalid WHERE clause on paidTimeOff update
- main.ts: session secret from env var, dev auth bypass, CORS origin:true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 13:10:31 -04:00
Matthieu Haineault
154c7063d8 feat(employee-list): added the new contract columns to the getEmployeeList route 2026-03-23 15:15:21 -04:00
Matthieu Haineault
3a65030764 Merge branch 'main' of git.targo.ca:Targo/targo_backend 2026-03-23 14:49:46 -04:00
Matthieu Haineault
a6d2a3b3cd feat(contracts): finishing touch for the contracts model 2026-03-23 14:49:42 -04:00
Nic D
d5e101804a fix(pay-periods): fix issue where mileage was not being calculated properly 2026-03-23 14:38:07 -04:00
Matthieu Haineault
a5bd7d54fe fix(contracts): added a contract class to employee and timesheet overview dtos and ajusted queries 2026-03-23 14:21:36 -04:00
Matthieu Haineault
c47dcb1f2f Merge branch 'main' of git.targo.ca:Targo/targo_backend 2026-03-23 09:01:16 -04:00
Matthieu Haineault
10b51f5ae6 feat(contract): added the contact model in the schema.prisma and base setup for the contracts module. moved the columns daily_expected_hours and applicable_overtime of the employees table to the new contract table. 2026-03-23 09:01:09 -04:00
Nic D
8fdc2baf21 fix(PTO): Fix incorrect module access allowed for PTO GET Totals route 2026-03-18 15:43:12 -04:00
Nic D
9f0ce738c2 Merge branch 'main' of https://git.targo.ca/Targo/targo_backend 2026-03-18 09:24:50 -04:00
Matthieu Haineault
2b04c3151d fix(csv): small fix on the shift type checking 2026-03-17 15:17:24 -04:00
Matthieu Haineault
b1069c0add fix(holiday): fixed the valid codes for the holiday calculations 2026-03-17 14:42:17 -04:00
Matthieu Haineault
d004fa9fa2 fix(csv): added evening, emergency, holiday and vacation hours in the overtime calculations 2026-03-17 14:35:55 -04:00
36 changed files with 3627 additions and 400 deletions

4
.gitignore vendored
View File

@ -55,9 +55,5 @@ pids
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Generated prisma folders (from -> npm run prisma:generated)
prisma/mariadb/generated/prisma/client/mariadb/
prisma/postgres/generated/prisma/client/postgres/
prisma/prisma-legacy/generated/prisma/client/legacy/
!swagger-spec.json !swagger-spec.json

View File

@ -37,6 +37,11 @@ export type userModuleAccess = Prisma.userModuleAccessModel
* *
*/ */
export type Employees = Prisma.EmployeesModel export type Employees = Prisma.EmployeesModel
/**
* Model Contracts
*
*/
export type Contracts = Prisma.ContractsModel
/** /**
* Model LeaveRequests * Model LeaveRequests
* *

View File

@ -57,6 +57,11 @@ export type userModuleAccess = Prisma.userModuleAccessModel
* *
*/ */
export type Employees = Prisma.EmployeesModel export type Employees = Prisma.EmployeesModel
/**
* Model Contracts
*
*/
export type Contracts = Prisma.ContractsModel
/** /**
* Model LeaveRequests * Model LeaveRequests
* *

View File

@ -314,6 +314,33 @@ export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedIntNullableFilter<$PrismaModel> _max?: Prisma.NestedIntNullableFilter<$PrismaModel>
} }
export type DecimalFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
}
export type DecimalWithAggregatesFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedDecimalFilter<$PrismaModel>
_sum?: Prisma.NestedDecimalFilter<$PrismaModel>
_min?: Prisma.NestedDecimalFilter<$PrismaModel>
_max?: Prisma.NestedDecimalFilter<$PrismaModel>
}
export type EnumLeaveTypesFilter<$PrismaModel = never> = { export type EnumLeaveTypesFilter<$PrismaModel = never> = {
equals?: $Enums.LeaveTypes | Prisma.EnumLeaveTypesFieldRefInput<$PrismaModel> equals?: $Enums.LeaveTypes | Prisma.EnumLeaveTypesFieldRefInput<$PrismaModel>
in?: $Enums.LeaveTypes[] | Prisma.ListEnumLeaveTypesFieldRefInput<$PrismaModel> in?: $Enums.LeaveTypes[] | Prisma.ListEnumLeaveTypesFieldRefInput<$PrismaModel>
@ -432,33 +459,6 @@ export type BoolNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedBoolNullableFilter<$PrismaModel> _max?: Prisma.NestedBoolNullableFilter<$PrismaModel>
} }
export type DecimalFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
}
export type DecimalWithAggregatesFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedDecimalFilter<$PrismaModel>
_sum?: Prisma.NestedDecimalFilter<$PrismaModel>
_min?: Prisma.NestedDecimalFilter<$PrismaModel>
_max?: Prisma.NestedDecimalFilter<$PrismaModel>
}
export type NestedUuidFilter<$PrismaModel = never> = { export type NestedUuidFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
@ -743,6 +743,33 @@ export type NestedFloatNullableFilter<$PrismaModel = never> = {
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
} }
export type NestedDecimalFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
}
export type NestedDecimalWithAggregatesFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedDecimalFilter<$PrismaModel>
_sum?: Prisma.NestedDecimalFilter<$PrismaModel>
_min?: Prisma.NestedDecimalFilter<$PrismaModel>
_max?: Prisma.NestedDecimalFilter<$PrismaModel>
}
export type NestedEnumLeaveTypesFilter<$PrismaModel = never> = { export type NestedEnumLeaveTypesFilter<$PrismaModel = never> = {
equals?: $Enums.LeaveTypes | Prisma.EnumLeaveTypesFieldRefInput<$PrismaModel> equals?: $Enums.LeaveTypes | Prisma.EnumLeaveTypesFieldRefInput<$PrismaModel>
in?: $Enums.LeaveTypes[] | Prisma.ListEnumLeaveTypesFieldRefInput<$PrismaModel> in?: $Enums.LeaveTypes[] | Prisma.ListEnumLeaveTypesFieldRefInput<$PrismaModel>
@ -850,31 +877,4 @@ export type NestedBoolNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedBoolNullableFilter<$PrismaModel> _max?: Prisma.NestedBoolNullableFilter<$PrismaModel>
} }
export type NestedDecimalFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
}
export type NestedDecimalWithAggregatesFilter<$PrismaModel = never> = {
equals?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
in?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
notIn?: runtime.Decimal[] | runtime.DecimalJsLike[] | number[] | string[] | Prisma.ListDecimalFieldRefInput<$PrismaModel>
lt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
lte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gt?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
gte?: runtime.Decimal | runtime.DecimalJsLike | number | string | Prisma.DecimalFieldRefInput<$PrismaModel>
not?: Prisma.NestedDecimalWithAggregatesFilter<$PrismaModel> | runtime.Decimal | runtime.DecimalJsLike | number | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_avg?: Prisma.NestedDecimalFilter<$PrismaModel>
_sum?: Prisma.NestedDecimalFilter<$PrismaModel>
_min?: Prisma.NestedDecimalFilter<$PrismaModel>
_max?: Prisma.NestedDecimalFilter<$PrismaModel>
}

File diff suppressed because one or more lines are too long

View File

@ -388,6 +388,7 @@ export const ModelName = {
Notifications: 'Notifications', Notifications: 'Notifications',
userModuleAccess: 'userModuleAccess', userModuleAccess: 'userModuleAccess',
Employees: 'Employees', Employees: 'Employees',
Contracts: 'Contracts',
LeaveRequests: 'LeaveRequests', LeaveRequests: 'LeaveRequests',
LeaveRequestsArchive: 'LeaveRequestsArchive', LeaveRequestsArchive: 'LeaveRequestsArchive',
Timesheets: 'Timesheets', Timesheets: 'Timesheets',
@ -419,7 +420,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
omit: GlobalOmitOptions omit: GlobalOmitOptions
} }
meta: { meta: {
modelProps: "users" | "notifications" | "userModuleAccess" | "employees" | "leaveRequests" | "leaveRequestsArchive" | "timesheets" | "timesheetsArchive" | "schedulePresets" | "schedulePresetShifts" | "shifts" | "shiftsArchive" | "bankCodes" | "expenses" | "expensesArchive" | "oAuthSessions" | "sessions" | "preferences" | "paidTimeOff" | "payPeriods" modelProps: "users" | "notifications" | "userModuleAccess" | "employees" | "contracts" | "leaveRequests" | "leaveRequestsArchive" | "timesheets" | "timesheetsArchive" | "schedulePresets" | "schedulePresetShifts" | "shifts" | "shiftsArchive" | "bankCodes" | "expenses" | "expensesArchive" | "oAuthSessions" | "sessions" | "preferences" | "paidTimeOff" | "payPeriods"
txIsolationLevel: TransactionIsolationLevel txIsolationLevel: TransactionIsolationLevel
} }
model: { model: {
@ -719,6 +720,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
} }
} }
} }
Contracts: {
payload: Prisma.$ContractsPayload<ExtArgs>
fields: Prisma.ContractsFieldRefs
operations: {
findUnique: {
args: Prisma.ContractsFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload> | null
}
findUniqueOrThrow: {
args: Prisma.ContractsFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
findFirst: {
args: Prisma.ContractsFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload> | null
}
findFirstOrThrow: {
args: Prisma.ContractsFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
findMany: {
args: Prisma.ContractsFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>[]
}
create: {
args: Prisma.ContractsCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
createMany: {
args: Prisma.ContractsCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.ContractsCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>[]
}
delete: {
args: Prisma.ContractsDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
update: {
args: Prisma.ContractsUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
deleteMany: {
args: Prisma.ContractsDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.ContractsUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.ContractsUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>[]
}
upsert: {
args: Prisma.ContractsUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$ContractsPayload>
}
aggregate: {
args: Prisma.ContractsAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateContracts>
}
groupBy: {
args: Prisma.ContractsGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.ContractsGroupByOutputType>[]
}
count: {
args: Prisma.ContractsCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.ContractsCountAggregateOutputType> | number
}
}
}
LeaveRequests: { LeaveRequests: {
payload: Prisma.$LeaveRequestsPayload<ExtArgs> payload: Prisma.$LeaveRequestsPayload<ExtArgs>
fields: Prisma.LeaveRequestsFieldRefs fields: Prisma.LeaveRequestsFieldRefs
@ -1947,19 +2022,30 @@ export const EmployeesScalarFieldEnum = {
user_id: 'user_id', user_id: 'user_id',
external_payroll_id: 'external_payroll_id', external_payroll_id: 'external_payroll_id',
company_code: 'company_code', company_code: 'company_code',
daily_expected_hours: 'daily_expected_hours',
first_work_day: 'first_work_day', first_work_day: 'first_work_day',
last_work_day: 'last_work_day', last_work_day: 'last_work_day',
supervisor_id: 'supervisor_id', supervisor_id: 'supervisor_id',
job_title: 'job_title', job_title: 'job_title',
is_supervisor: 'is_supervisor', is_supervisor: 'is_supervisor',
applicable_overtime: 'applicable_overtime',
schedule_preset_id: 'schedule_preset_id' schedule_preset_id: 'schedule_preset_id'
} as const } as const
export type EmployeesScalarFieldEnum = (typeof EmployeesScalarFieldEnum)[keyof typeof EmployeesScalarFieldEnum] export type EmployeesScalarFieldEnum = (typeof EmployeesScalarFieldEnum)[keyof typeof EmployeesScalarFieldEnum]
export const ContractsScalarFieldEnum = {
id: 'id',
employee_id: 'employee_id',
daily_expected_hours: 'daily_expected_hours',
applicable_overtime: 'applicable_overtime',
phone_allocation: 'phone_allocation',
on_call_allocation: 'on_call_allocation',
weekend_on_call_allocation: 'weekend_on_call_allocation'
} as const
export type ContractsScalarFieldEnum = (typeof ContractsScalarFieldEnum)[keyof typeof ContractsScalarFieldEnum]
export const LeaveRequestsScalarFieldEnum = { export const LeaveRequestsScalarFieldEnum = {
id: 'id', id: 'id',
employee_id: 'employee_id', employee_id: 'employee_id',
@ -2326,6 +2412,20 @@ export type EnumApplicableOvertimeFieldRefInput<$PrismaModel> = FieldRefInputTyp
/**
* Reference to a field of type 'Decimal'
*/
export type DecimalFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Decimal'>
/**
* Reference to a field of type 'Decimal[]'
*/
export type ListDecimalFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Decimal[]'>
/** /**
* Reference to a field of type 'LeaveTypes' * Reference to a field of type 'LeaveTypes'
*/ */
@ -2354,20 +2454,6 @@ export type ListEnumLeaveApprovalStatusFieldRefInput<$PrismaModel> = FieldRefInp
/**
* Reference to a field of type 'Decimal'
*/
export type DecimalFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Decimal'>
/**
* Reference to a field of type 'Decimal[]'
*/
export type ListDecimalFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Decimal[]'>
/** /**
* Reference to a field of type 'Weekday' * Reference to a field of type 'Weekday'
*/ */
@ -2494,6 +2580,7 @@ export type GlobalOmitConfig = {
notifications?: Prisma.NotificationsOmit notifications?: Prisma.NotificationsOmit
userModuleAccess?: Prisma.userModuleAccessOmit userModuleAccess?: Prisma.userModuleAccessOmit
employees?: Prisma.EmployeesOmit employees?: Prisma.EmployeesOmit
contracts?: Prisma.ContractsOmit
leaveRequests?: Prisma.LeaveRequestsOmit leaveRequests?: Prisma.LeaveRequestsOmit
leaveRequestsArchive?: Prisma.LeaveRequestsArchiveOmit leaveRequestsArchive?: Prisma.LeaveRequestsArchiveOmit
timesheets?: Prisma.TimesheetsOmit timesheets?: Prisma.TimesheetsOmit

View File

@ -55,6 +55,7 @@ export const ModelName = {
Notifications: 'Notifications', Notifications: 'Notifications',
userModuleAccess: 'userModuleAccess', userModuleAccess: 'userModuleAccess',
Employees: 'Employees', Employees: 'Employees',
Contracts: 'Contracts',
LeaveRequests: 'LeaveRequests', LeaveRequests: 'LeaveRequests',
LeaveRequestsArchive: 'LeaveRequestsArchive', LeaveRequestsArchive: 'LeaveRequestsArchive',
Timesheets: 'Timesheets', Timesheets: 'Timesheets',
@ -138,19 +139,30 @@ export const EmployeesScalarFieldEnum = {
user_id: 'user_id', user_id: 'user_id',
external_payroll_id: 'external_payroll_id', external_payroll_id: 'external_payroll_id',
company_code: 'company_code', company_code: 'company_code',
daily_expected_hours: 'daily_expected_hours',
first_work_day: 'first_work_day', first_work_day: 'first_work_day',
last_work_day: 'last_work_day', last_work_day: 'last_work_day',
supervisor_id: 'supervisor_id', supervisor_id: 'supervisor_id',
job_title: 'job_title', job_title: 'job_title',
is_supervisor: 'is_supervisor', is_supervisor: 'is_supervisor',
applicable_overtime: 'applicable_overtime',
schedule_preset_id: 'schedule_preset_id' schedule_preset_id: 'schedule_preset_id'
} as const } as const
export type EmployeesScalarFieldEnum = (typeof EmployeesScalarFieldEnum)[keyof typeof EmployeesScalarFieldEnum] export type EmployeesScalarFieldEnum = (typeof EmployeesScalarFieldEnum)[keyof typeof EmployeesScalarFieldEnum]
export const ContractsScalarFieldEnum = {
id: 'id',
employee_id: 'employee_id',
daily_expected_hours: 'daily_expected_hours',
applicable_overtime: 'applicable_overtime',
phone_allocation: 'phone_allocation',
on_call_allocation: 'on_call_allocation',
weekend_on_call_allocation: 'weekend_on_call_allocation'
} as const
export type ContractsScalarFieldEnum = (typeof ContractsScalarFieldEnum)[keyof typeof ContractsScalarFieldEnum]
export const LeaveRequestsScalarFieldEnum = { export const LeaveRequestsScalarFieldEnum = {
id: 'id', id: 'id',
employee_id: 'employee_id', employee_id: 'employee_id',

View File

@ -12,6 +12,7 @@ export type * from './models/Users'
export type * from './models/Notifications' export type * from './models/Notifications'
export type * from './models/userModuleAccess' export type * from './models/userModuleAccess'
export type * from './models/Employees' export type * from './models/Employees'
export type * from './models/Contracts'
export type * from './models/LeaveRequests' export type * from './models/LeaveRequests'
export type * from './models/LeaveRequestsArchive' export type * from './models/LeaveRequestsArchive'
export type * from './models/Timesheets' export type * from './models/Timesheets'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -431,14 +431,6 @@ export type PaidTimeOffUncheckedUpdateOneWithoutEmployeeNestedInput = {
update?: Prisma.XOR<Prisma.XOR<Prisma.PaidTimeOffUpdateToOneWithWhereWithoutEmployeeInput, Prisma.PaidTimeOffUpdateWithoutEmployeeInput>, Prisma.PaidTimeOffUncheckedUpdateWithoutEmployeeInput> update?: Prisma.XOR<Prisma.XOR<Prisma.PaidTimeOffUpdateToOneWithWhereWithoutEmployeeInput, Prisma.PaidTimeOffUpdateWithoutEmployeeInput>, Prisma.PaidTimeOffUncheckedUpdateWithoutEmployeeInput>
} }
export type DecimalFieldUpdateOperationsInput = {
set?: runtime.Decimal | runtime.DecimalJsLike | number | string
increment?: runtime.Decimal | runtime.DecimalJsLike | number | string
decrement?: runtime.Decimal | runtime.DecimalJsLike | number | string
multiply?: runtime.Decimal | runtime.DecimalJsLike | number | string
divide?: runtime.Decimal | runtime.DecimalJsLike | number | string
}
export type PaidTimeOffCreateWithoutEmployeeInput = { export type PaidTimeOffCreateWithoutEmployeeInput = {
vacation_hours?: runtime.Decimal | runtime.DecimalJsLike | number | string vacation_hours?: runtime.Decimal | runtime.DecimalJsLike | number | string
banked_hours?: runtime.Decimal | runtime.DecimalJsLike | number | string banked_hours?: runtime.Decimal | runtime.DecimalJsLike | number | string

View File

@ -9,13 +9,14 @@ datasource db {
} }
model Users { model Users {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
first_name String first_name String
last_name String last_name String
email String @unique email String @unique
phone_number String phone_number String
residence String? residence String?
role Roles @default(EMPLOYEE) role Roles @default(EMPLOYEE)
notifications Notifications? @relation("UserNotification") notifications Notifications? @relation("UserNotification")
employee Employees? @relation("UserEmployee") employee Employees? @relation("UserEmployee")
oauth_sessions OAuthSessions[] @relation("UserOAuthSessions") oauth_sessions OAuthSessions[] @relation("UserOAuthSessions")
@ -34,7 +35,8 @@ model Notifications {
metadata Json @db.JsonB metadata Json @db.JsonB
created_at DateTime @default(now()) created_at DateTime @default(now())
viewed_at DateTime? viewed_at DateTime?
user Users @relation("UserNotification", fields: [user_id], references: [id])
user Users @relation("UserNotification", fields: [user_id], references: [id])
@@map("notifications") @@map("notifications")
} }
@ -51,48 +53,64 @@ model userModuleAccess {
chatbot Boolean @default(false) chatbot Boolean @default(false)
ticket Boolean @default(false) ticket Boolean @default(false)
ticket_management Boolean @default(false) ticket_management Boolean @default(false)
user Users @relation("UserModuleAccess", fields: [user_id], references: [id])
user Users @relation("UserModuleAccess", fields: [user_id], references: [id])
@@map("user_module_access") @@map("user_module_access")
} }
model Employees { model Employees {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
user_id String @unique @db.Uuid user_id String @unique @db.Uuid
external_payroll_id Int external_payroll_id Int
company_code Int company_code Int
daily_expected_hours Int @default(24) first_work_day DateTime @db.Date
first_work_day DateTime @db.Date last_work_day DateTime? @db.Date
last_work_day DateTime? @db.Date supervisor_id Int?
supervisor_id Int? job_title String?
job_title String? is_supervisor Boolean @default(false)
is_supervisor Boolean @default(false) schedule_preset_id Int?
applicable_overtime ApplicableOvertime[] @default([WEEKLY])
schedule_preset_id Int? schedule_preset SchedulePresets? @relation("EmployeesSchedulePreset", fields: [schedule_preset_id], references: [id])
schedule_preset SchedulePresets? @relation("EmployeesSchedulePreset", fields: [schedule_preset_id], references: [id]) supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) crew Employees[] @relation("EmployeeSupervisor")
crew Employees[] @relation("EmployeeSupervisor") user Users @relation("UserEmployee", fields: [user_id], references: [id])
user Users @relation("UserEmployee", fields: [user_id], references: [id]) leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
leave_request LeaveRequests[] @relation("LeaveRequestEmployee") timesheet Timesheets[] @relation("TimesheetEmployee")
timesheet Timesheets[] @relation("TimesheetEmployee") paid_time_off PaidTimeOff? @relation("EmployeePaidTimeOff")
paid_time_off PaidTimeOff? @relation("EmployeePaidTimeOff") contracts Contracts? @relation("EmployeeContract")
@@map("employees") @@map("employees")
} }
model Contracts {
id Int @id @default(autoincrement())
employee_id Int @unique
daily_expected_hours Int @default(24)
applicable_overtime ApplicableOvertime[] @default([WEEKLY])
phone_allocation Decimal @default(0.00) @db.Decimal(5, 2)
on_call_allocation Decimal @default(0.00) @db.Decimal(5, 2)
weekend_on_call_allocation Decimal @default(0.00) @db.Decimal(5, 2)
employee Employees @relation("EmployeeContract", fields: [employee_id], references: [id])
@@map("contracts")
}
model LeaveRequests { model LeaveRequests {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
employee_id Int employee_id Int
leave_type LeaveTypes leave_type LeaveTypes
comment String comment String
approval_status LeaveApprovalStatus @default(PENDING) approval_status LeaveApprovalStatus @default(PENDING)
bank_code_id Int bank_code_id Int
payable_hours Decimal? @db.Decimal(5, 2) payable_hours Decimal? @db.Decimal(5, 2)
requested_hours Decimal? @db.Decimal(5, 2) requested_hours Decimal? @db.Decimal(5, 2)
dates DateTime[] @db.Date dates DateTime[] @db.Date
bank_code BankCodes @relation("LeaveRequestBankCodes", fields: [bank_code_id], references: [id])
employee Employees @relation("LeaveRequestEmployee", fields: [employee_id], references: [id]) bank_code BankCodes @relation("LeaveRequestBankCodes", fields: [bank_code_id], references: [id])
archive LeaveRequestsArchive? @relation("LeaveRequestToArchive") employee Employees @relation("LeaveRequestEmployee", fields: [employee_id], references: [id])
archive LeaveRequestsArchive? @relation("LeaveRequestToArchive")
@@unique([employee_id, leave_type, dates], name: "leave_per_employee_date") @@unique([employee_id, leave_type, dates], name: "leave_per_employee_date")
@@index([employee_id, dates]) @@index([employee_id, dates])
@ -110,22 +128,24 @@ model LeaveRequestsArchive {
date DateTime @db.Date date DateTime @db.Date
payable_hours Decimal? @db.Decimal(5, 2) payable_hours Decimal? @db.Decimal(5, 2)
requested_hours Decimal? @db.Decimal(5, 2) requested_hours Decimal? @db.Decimal(5, 2)
leave_request LeaveRequests @relation("LeaveRequestToArchive", fields: [leave_request_id], references: [id])
leave_request LeaveRequests @relation("LeaveRequestToArchive", fields: [leave_request_id], references: [id])
@@index([employee_id, date]) @@index([employee_id, date])
@@map("leave_requests_archive") @@map("leave_requests_archive")
} }
model Timesheets { model Timesheets {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
employee_id Int employee_id Int
is_approved Boolean @default(false) is_approved Boolean @default(false)
start_date DateTime @db.Date start_date DateTime @db.Date
// hours_to_bank Decimal? @db.Decimal(5, 2) //will need to be implemented in the future // hours_to_bank Decimal? @db.Decimal(5, 2) //will need to be implemented in the future
expense Expenses[] @relation("ExpensesTimesheet")
shift Shifts[] @relation("ShiftTimesheet") expense Expenses[] @relation("ExpensesTimesheet")
employee Employees @relation("TimesheetEmployee", fields: [employee_id], references: [id]) shift Shifts[] @relation("ShiftTimesheet")
archive TimesheetsArchive[] @relation("TimesheetsToArchive") employee Employees @relation("TimesheetEmployee", fields: [employee_id], references: [id])
archive TimesheetsArchive[] @relation("TimesheetsToArchive")
// @@unique([employee_id, start_date], name: "employee_id_start_date") // @@unique([employee_id, start_date], name: "employee_id_start_date")
@@map("timesheets") @@map("timesheets")

View File

@ -0,0 +1,16 @@
import { Controller, Get, Param } from "@nestjs/common";
// import { Access } from "src/common/decorators/module-access.decorators";
import { ContractService } from "src/identity-and-account/contract/services/contract.service";
@Controller('contracts')
export class ContractController {
constructor(private readonly getService: ContractService) { }
@Get('details/:email')
async getContractDetailsByEmail(
@Param('email') email: string
) {
return await this.getService.getContractDetailsByEmail(email);
}
}

View File

@ -0,0 +1,12 @@
import { Type } from "class-transformer";
import { IsArray, IsInt, IsString } from "class-validator";
import { ApplicableOvertime } from "prisma/postgres/generated/prisma/client/postgres/enums";
export class Contract {
@IsInt() id: number;
@IsInt() daily_expected_hours: number;
@IsString() @IsArray() applicable_overtime: ApplicableOvertime[];
@Type(() => Number) phone_allocation: number;
@Type(() => Number) on_call_allocation: number;
@Type(() => Number) weekend_on_call_allocation: number;
}

View File

@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { ContractController } from "src/identity-and-account/contract/contract.controller";
import { ContractService } from "src/identity-and-account/contract/services/contract.service";
@Module({
imports: [
ContractService,
],
providers: [
ContractController,
]
}) export class ContractModule { }

View File

@ -0,0 +1,43 @@
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Contract } from "src/identity-and-account/contract/contract.dto";
@Injectable()
export class ContractService {
constructor(
private readonly prisma: PrismaPostgresService,
private readonly emailresolver: EmailToIdResolver,
) { }
getContractDetailsByEmail = async (email: string): Promise<Result<Contract, string>> => {
try {
const employee = await this.emailresolver.findIdByEmail(email);
if (!employee.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
const contract = await this.prisma.contracts.findUnique({
where: { employee_id: employee.data },
omit: { employee_id: true },
});
if (!contract) return { success: false, error: 'CONTRACT_NOT_FOUND' };
const contractDetails: Contract = {
id: contract.id,
daily_expected_hours: contract.daily_expected_hours,
applicable_overtime: contract.applicable_overtime,
on_call_allocation: Number(contract.on_call_allocation),
weekend_on_call_allocation: Number(contract.weekend_on_call_allocation),
phone_allocation: Number(contract.phone_allocation),
};
return { success: true, data: contractDetails }
} catch (error) {
console.error(error);
return { success: false, error: 'CONTRACT_NOT_FOUND' };
}
}
}

View File

@ -2,6 +2,7 @@ import { IsArray, IsBoolean, IsDateString, IsEmail, IsInt, IsNotEmpty, IsOptiona
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { PaidTimeOffDto } from 'src/time-and-attendance/paid-time-off/paid-time-off.dto'; import { PaidTimeOffDto } from 'src/time-and-attendance/paid-time-off/paid-time-off.dto';
import { Prisma } from 'prisma/postgres/generated/prisma/client/postgres/client'; import { Prisma } from 'prisma/postgres/generated/prisma/client/postgres/client';
import { Contract } from 'src/identity-and-account/contract/contract.dto';
export class EmployeeDetailedDto { export class EmployeeDetailedDto {
@IsString() @IsNotEmpty() first_name: string; @IsString() @IsNotEmpty() first_name: string;
@ -14,9 +15,9 @@ export class EmployeeDetailedDto {
@IsEmail() @IsOptional() email: string; @IsEmail() @IsOptional() email: string;
@IsString() phone_number: string; @IsString() phone_number: string;
@IsDateString() first_work_day: string; @IsDateString() first_work_day: string;
@IsInt() daily_expected_hours: number;
@IsDateString() @IsOptional() last_work_day?: string | null; @IsDateString() @IsOptional() last_work_day?: string | null;
@IsString() @IsOptional() residence?: string; @IsString() @IsOptional() residence?: string;
@IsInt() @Type(() => Contract) contract: Partial<Contract>;
@IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: Partial<PaidTimeOffDto>; @IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: Partial<PaidTimeOffDto>;
@IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number;
@IsArray() @IsString({ each: true }) user_module_access: string[]; @IsArray() @IsString({ each: true }) user_module_access: string[];
@ -34,9 +35,9 @@ export class EmployeeDetailedUpsertDto {
@IsEmail() @IsOptional() email: string; @IsEmail() @IsOptional() email: string;
@IsString() phone_number: string; @IsString() phone_number: string;
@IsDateString() first_work_day: string; @IsDateString() first_work_day: string;
@IsInt() daily_expected_hours: number;
@IsDateString() @IsOptional() last_work_day?: string | null; @IsDateString() @IsOptional() last_work_day?: string | null;
@IsString() @IsOptional() residence?: string; @IsString() @IsOptional() residence?: string;
@IsInt() @Type(() => Contract) contract: Partial<Contract>;
@IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: PaidTimeOffDto; @IsOptional() @Type(() => PaidTimeOffDto) paid_time_off?: PaidTimeOffDto;
@IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number;
@IsArray() @IsString({ each: true }) user_module_access: string[]; @IsArray() @IsString({ each: true }) user_module_access: string[];
@ -74,9 +75,26 @@ export type EmployeeWithDetails = Prisma.EmployeesGetPayload<{
first_work_day: true, first_work_day: true,
last_work_day: true, last_work_day: true,
external_payroll_id: true, external_payroll_id: true,
paid_time_off: true, paid_time_off: {
select: {
id: true,
employee_id: true,
sick_hours: true,
vacation_hours: true,
banked_hours: true,
last_updated: true,
},
},
is_supervisor: true, is_supervisor: true,
daily_expected_hours: true, contracts: {
select: {
daily_expected_hours: true,
applicable_overtime: true,
weekend_on_call_allocation: true,
on_call_allocation: true,
phone_allocation: true,
},
},
schedule_preset_id: true, schedule_preset_id: true,
schedule_preset: { select: { id: true } } schedule_preset: { select: { id: true } }
} }

View File

@ -44,13 +44,20 @@ export class EmployeesCreateService {
data: { data: {
user_id: user.id, user_id: user.id,
external_payroll_id: dto.external_payroll_id, external_payroll_id: dto.external_payroll_id,
daily_expected_hours: dto.daily_expected_hours,
company_code: company_code, company_code: company_code,
job_title: dto.job_title, job_title: dto.job_title,
first_work_day: first_work_day, first_work_day: first_work_day,
is_supervisor: dto.is_supervisor, is_supervisor: dto.is_supervisor,
supervisor_id: supervisor_id, supervisor_id: supervisor_id,
schedule_preset_id: dto.preset_id, schedule_preset_id: dto.preset_id,
contracts: {
create: {
daily_expected_hours: dto.contract.daily_expected_hours,
phone_allocation: dto.contract.phone_allocation,
on_call_allocation: dto.contract.on_call_allocation,
weekend_on_call_allocation: dto.contract.weekend_on_call_allocation,
},
},
}, },
}); });
}); });

View File

@ -37,8 +37,16 @@ export class EmployeesGetService {
}, },
}, },
}, },
contracts: {
select: {
daily_expected_hours: true,
applicable_overtime: true,
on_call_allocation: true,
weekend_on_call_allocation: true,
phone_allocation: true,
},
},
is_supervisor: true, is_supervisor: true,
daily_expected_hours: true,
job_title: true, job_title: true,
company_code: true, company_code: true,
external_payroll_id: true, external_payroll_id: true,
@ -47,23 +55,33 @@ export class EmployeesGetService {
schedule_preset_id: true, schedule_preset_id: true,
} }
}).then(rows => rows.map(r => ({ });
first_name: r.user.first_name,
last_name: r.user.last_name, const employeeDetailedList = employee_list.map(r => {
email: r.user.email, return {
phone_number: r.user.phone_number, first_name: r.user.first_name,
company_name: toStringFromCompanyCode(r.company_code), last_name: r.user.last_name,
job_title: r.job_title ?? '', email: r.user.email,
daily_expected_hours: r.daily_expected_hours, phone_number: r.user.phone_number,
external_payroll_id: r.external_payroll_id, company_name: toStringFromCompanyCode(r.company_code),
employee_full_name: `${r.user.first_name} ${r.user.last_name}`, job_title: r.job_title ?? '',
is_supervisor: r.is_supervisor, contract: {
supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`, daily_expected_hours: r.contracts?.daily_expected_hours ?? 24,
first_work_day: toStringFromDate(r.first_work_day), applicable_overtime: r.contracts?.applicable_overtime ?? [],
last_work_day: r.last_work_day ? toStringFromDate(r.last_work_day) : null, phone_allocation: Number(r.contracts?.phone_allocation),
preset_id: r.schedule_preset_id ?? undefined, on_call_allocation: Number(r.contracts?.on_call_allocation),
}))); weekend_on_call_allocation: Number(r.contracts?.weekend_on_call_allocation),
return { success: true, data: employee_list }; },
external_payroll_id: r.external_payroll_id,
employee_full_name: `${r.user.first_name} ${r.user.last_name}`,
is_supervisor: r.is_supervisor,
supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`,
first_work_day: toStringFromDate(r.first_work_day),
last_work_day: r.last_work_day ? toStringFromDate(r.last_work_day) : null,
preset_id: r.schedule_preset_id ?? undefined,
}
});
return { success: true, data: employeeDetailedList };
}; };
async findOwnProfile(email: string): Promise<Result<Partial<EmployeeDetailedDto>, string>> { async findOwnProfile(email: string): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
@ -89,7 +107,11 @@ export class EmployeesGetService {
paid_time_off: true, paid_time_off: true,
is_supervisor: true, is_supervisor: true,
schedule_preset_id: true, schedule_preset_id: true,
daily_expected_hours: true, contracts: {
select: {
daily_expected_hours: true,
},
},
supervisor: { supervisor: {
select: { select: {
id: true, user: { id: true, user: {
@ -103,6 +125,7 @@ export class EmployeesGetService {
}, },
}); });
if (!existing_profile) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; if (!existing_profile) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
if (!existing_profile.contracts) return { success: false, error: 'CONTRACT_NOT_FOUND' };
const company_name = toStringFromCompanyCode(existing_profile.company_code); const company_name = toStringFromCompanyCode(existing_profile.company_code);
@ -113,7 +136,9 @@ export class EmployeesGetService {
email: existing_profile.user.email, email: existing_profile.user.email,
supervisor_full_name: `${existing_profile.supervisor?.user.first_name} ${existing_profile.supervisor?.user.last_name}`, supervisor_full_name: `${existing_profile.supervisor?.user.first_name} ${existing_profile.supervisor?.user.last_name}`,
company_name: company_name, company_name: company_name,
daily_expected_hours: existing_profile.daily_expected_hours, contract: {
daily_expected_hours: existing_profile.contracts?.daily_expected_hours ?? 24,
},
job_title: existing_profile.job_title ?? '', job_title: existing_profile.job_title ?? '',
external_payroll_id: existing_profile.external_payroll_id, external_payroll_id: existing_profile.external_payroll_id,
paid_time_off: { paid_time_off: {
@ -170,6 +195,11 @@ export class EmployeesGetService {
}, },
}, },
}, },
contracts: {
select: {
daily_expected_hours: true,
}
},
job_title: true, job_title: true,
company_code: true, company_code: true,
first_work_day: true, first_work_day: true,
@ -177,7 +207,6 @@ export class EmployeesGetService {
external_payroll_id: true, external_payroll_id: true,
paid_time_off: true, paid_time_off: true,
is_supervisor: true, is_supervisor: true,
daily_expected_hours: true,
schedule_preset_id: true, schedule_preset_id: true,
schedule_preset: { schedule_preset: {
select: { select: {
@ -196,35 +225,36 @@ export class EmployeesGetService {
const company_name = toStringFromCompanyCode(employee.company_code); const company_name = toStringFromCompanyCode(employee.company_code);
return { const detailed_employee: EmployeeDetailedDto = {
success: true, first_name: employee.user.first_name,
data: { last_name: employee.user.last_name,
first_name: employee.user.first_name, email: employee.user.email,
last_name: employee.user.last_name, residence: employee.user.residence ?? '',
email: employee.user.email, phone_number: employee.user.phone_number,
residence: employee.user.residence ?? '', company_name: company_name,
phone_number: employee.user.phone_number, is_supervisor: employee.is_supervisor ?? false,
company_name: company_name, job_title: employee.job_title ?? '',
is_supervisor: employee.is_supervisor ?? false, external_payroll_id: employee.external_payroll_id,
job_title: employee.job_title ?? '', paid_time_off: {
external_payroll_id: employee.external_payroll_id, id: employee.paid_time_off?.id ?? -1,
paid_time_off: { employee_id: employee.paid_time_off?.employee_id ?? -1,
id: employee.paid_time_off?.id ?? -1, sick_hours: employee.paid_time_off?.sick_hours.toNumber() ?? 0,
employee_id: employee.paid_time_off?.employee_id ?? -1, vacation_hours: employee.paid_time_off?.vacation_hours.toNumber() ?? 0,
sick_hours: employee.paid_time_off?.sick_hours.toNumber() ?? 0, banked_hours: employee.paid_time_off?.banked_hours.toNumber() ?? 0,
vacation_hours: employee.paid_time_off?.vacation_hours.toNumber() ?? 0, last_updated: employee.paid_time_off?.last_updated?.toISOString() ?? null,
banked_hours: employee.paid_time_off?.banked_hours.toNumber() ?? 0,
last_updated: employee.paid_time_off?.last_updated?.toISOString() ?? null,
},
employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`,
first_work_day: toStringFromDate(employee.first_work_day),
last_work_day: employee.last_work_day ? toStringFromDate(employee.last_work_day) : undefined,
supervisor_full_name: employee.supervisor ? `${employee.supervisor?.user.first_name} ${employee.supervisor?.user.last_name}` : '',
user_module_access: module_access_array,
daily_expected_hours: employee.daily_expected_hours,
preset_id: employee.schedule_preset_id ? employee.schedule_preset_id : undefined,
}, },
}; employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`,
first_work_day: toStringFromDate(employee.first_work_day),
last_work_day: employee.last_work_day ? toStringFromDate(employee.last_work_day) : undefined,
supervisor_full_name: employee.supervisor ? `${employee.supervisor?.user.first_name} ${employee.supervisor?.user.last_name}` : '',
user_module_access: module_access_array,
contract: {
daily_expected_hours: employee.contracts?.daily_expected_hours ?? 24,
},
preset_id: employee.schedule_preset_id ? employee.schedule_preset_id : undefined,
}
return { success: true, data: detailed_employee };
}; };
} }

View File

@ -98,13 +98,21 @@ export class EmployeesUpdateService {
data: { data: {
company_code: company_code, company_code: company_code,
job_title: dto.job_title, job_title: dto.job_title,
daily_expected_hours: dto.daily_expected_hours,
first_work_day: toDateFromString(dto.first_work_day), first_work_day: toDateFromString(dto.first_work_day),
last_work_day: last_work_day, last_work_day: last_work_day,
is_supervisor: dto.is_supervisor, is_supervisor: dto.is_supervisor,
supervisor_id: supervisor_id, supervisor_id: supervisor_id,
schedule_preset_id: dto.preset_id, schedule_preset_id: dto.preset_id,
external_payroll_id: dto.external_payroll_id, external_payroll_id: dto.external_payroll_id,
contracts: {
update: {
applicable_overtime: dto.contract.applicable_overtime,
daily_expected_hours: dto.contract.daily_expected_hours,
on_call_allocation: dto.contract.on_call_allocation,
weekend_on_call_allocation: dto.contract.weekend_on_call_allocation,
phone_allocation: dto.contract.phone_allocation,
},
},
}, },
}); });

View File

@ -0,0 +1 @@
GET http://localhost:3000/contracts/details/matthieuh@targointernet.com

View File

@ -0,0 +1 @@
GET http://localhost:3000/employees/employee-list

View File

@ -17,6 +17,8 @@ import { EmployeesCreateService } from "src/identity-and-account/employees/servi
import { EmployeesUpdateService } from "src/identity-and-account/employees/services/employees-update.service"; import { EmployeesUpdateService } from "src/identity-and-account/employees/services/employees-update.service";
import { HomePageController } from "src/identity-and-account/help/help-page.controller"; import { HomePageController } from "src/identity-and-account/help/help-page.controller";
import { HomePageService } from "src/identity-and-account/help/help-page.service"; import { HomePageService } from "src/identity-and-account/help/help-page.service";
import { ContractController } from "src/identity-and-account/contract/contract.controller";
import { ContractService } from "src/identity-and-account/contract/services/contract.service";
@Module({ @Module({
imports: [ imports: [
@ -28,6 +30,7 @@ import { HomePageService } from "src/identity-and-account/help/help-page.service
], ],
controllers: [ controllers: [
EmployeesController, EmployeesController,
ContractController,
PreferencesController, PreferencesController,
ModuleAccessController, ModuleAccessController,
HomePageController, HomePageController,
@ -42,6 +45,7 @@ import { HomePageService } from "src/identity-and-account/help/help-page.service
AccessUpdateService, AccessUpdateService,
AccessGetService, AccessGetService,
HomePageService, HomePageService,
ContractService,
], ],
}) })
export class IdentityAndAccountModule { }; export class IdentityAndAccountModule { };

View File

@ -21,7 +21,7 @@ async function bootstrap() {
// Authentication and session // Authentication and session
app.use(session({ app.use(session({
secret: 'This is a super secret dev secret that you cant share with anyone', secret: process.env.SESSION_SECRET || 'dev-only-secret-change-in-production',
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
rolling: true, rolling: true,
@ -39,9 +39,27 @@ async function bootstrap() {
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
// LOCAL DEV: bypass Authentik by injecting a fake authenticated user
if (process.env.DEV_BYPASS_AUTH === 'true') {
console.log('⚠ DEV_BYPASS_AUTH enabled — all requests authenticated as louis@targo.ca');
app.use((req, _res, next) => {
if (!req.user) {
req.user = {
first_name: 'Louis',
last_name: 'Paul',
email: 'louis@targo.ca',
role: 'ADMIN',
user_module_access: ['timesheets', 'timesheets_approval', 'employee_list', 'employee_management', 'personal_profile', 'dashboard'],
};
req.isAuthenticated = () => true;
}
next();
});
}
// Enable CORS // Enable CORS
app.enableCors({ app.enableCors({
origin: ['http://10.100.251.2:9011', 'http://10.5.14.111:9012', 'http://10.100.251.2:9013', 'http://localhost:9000', 'https://app.targo.ca', 'https://portail.targo.ca', 'https://staging.app.targo.ca'], origin: true, // allow all origins in dev
credentials: true, credentials: true,
}); });

View File

@ -17,7 +17,7 @@ export class BankedHoursService {
try { try {
const result = await this.prisma.$transaction(async (tx) => { const result = await this.prisma.$transaction(async (tx) => {
const employee = await this.prisma.employees.findUnique({ const employee = await tx.employees.findUnique({
where: { id: employee_id }, where: { id: employee_id },
select: { select: {
id: true, id: true,

View File

@ -30,23 +30,16 @@ export class HolidayService {
holiday_date: Date holiday_date: Date
): Promise<Result<number, string>> { ): Promise<Result<number, string>> {
try { try {
const valid_codes = ['G1', 'G43', 'G140', 'G104', 'G105', 'G305', 'G700', 'G720']; const valid_codes = ['G1', 'G43', 'G48', 'G104', 'G105', 'G109', 'G140', 'G720'];
const holiday_week_start = getWeekStart(holiday_date); const holiday_week_start = getWeekStart(holiday_date);
const window_start = new Date(holiday_week_start.getTime() - 4 * MS_PER_WEEK); const window_start = new Date(holiday_week_start.getTime() - 4 * MS_PER_WEEK);
const window_end = new Date(holiday_week_start.getTime() - 1); const window_end = new Date(holiday_week_start.getTime() - 1);
const employee = await this.prisma.employees.findFirst({ const employee = await this.prisma.employees.findFirst({
where: { where: { external_payroll_id, company_code }
external_payroll_id,
company_code,
},
select: {
id: true,
}
}); });
if (!employee) if (!employee) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
const shifts = await this.prisma.shifts.findMany({ const shifts = await this.prisma.shifts.findMany({
where: { where: {
@ -61,8 +54,7 @@ export class HolidayService {
for (const shift of shifts) { for (const shift of shifts) {
const hours = computeHours(shift.start_time, shift.end_time); const hours = computeHours(shift.start_time, shift.end_time);
if (hours <= 0) if (hours <= 0) continue;
continue;
const shift_week_start = getWeekStart(shift.date); const shift_week_start = getWeekStart(shift.date);
const key = shift_week_start.getTime(); const key = shift_week_start.getTime();

View File

@ -109,7 +109,7 @@ export class SickLeaveService {
employee_id, employee_id,
}, },
data: { data: {
sick_hours, sick_hours: { increment: sick_hours },
last_updated, last_updated,
} }
}) })
@ -129,7 +129,7 @@ export class SickLeaveService {
try { try {
const result = await this.prisma.$transaction(async (tx) => { const result = await this.prisma.$transaction(async (tx) => {
const employee = await this.prisma.employees.findUnique({ const employee = await tx.employees.findUnique({
where: { id: employee_id }, where: { id: employee_id },
select: { select: {
id: true, id: true,

View File

@ -97,7 +97,7 @@ export class VacationService {
} else { } else {
//update vacation_bank //update vacation_bank
await tx.paidTimeOff.update({ await tx.paidTimeOff.update({
where: { employee_id: employee_id, vacation_hours: { gte: asked_hours } }, where: { employee_id: employee_id },
data: { data: {
vacation_hours: { decrement: asked_hours }, vacation_hours: { decrement: asked_hours },
last_updated: new Date(), last_updated: new Date(),

View File

@ -1,8 +1,7 @@
import { Body, Controller, Param, Post, Res, StreamableFile } from "@nestjs/common"; import { Body, Controller, Param, Post, StreamableFile } from "@nestjs/common";
import { CsvExportService } from "./services/csv-exports.service"; import { CsvExportService } from "./services/csv-exports.service";
import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators"; import { ModuleAccessAllowed } from "src/common/decorators/modules-guard.decorators";
import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client"; import { Modules as ModulesEnum } from "prisma/postgres/generated/prisma/client/postgres/client";
// import { Response } from "express";
import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service"; import { CsvGeneratorService } from "src/time-and-attendance/exports/services/csv-builder.service";
import type { CsvFilters } from "src/time-and-attendance/exports/export-csv-options.dto"; import type { CsvFilters } from "src/time-and-attendance/exports/export-csv-options.dto";
@ -20,7 +19,6 @@ export class CsvExportController {
@Param('year') year: number, @Param('year') year: number,
@Param('period_no') period_no: number, @Param('period_no') period_no: number,
@Body() filters: CsvFilters, @Body() filters: CsvFilters,
// @Res() response: Response,
) { ) {
const rows = await this.csvService.collectTransaction(year, period_no, filters); const rows = await this.csvService.collectTransaction(year, period_no, filters);
const buffer = this.generator.generateCsv(rows); const buffer = this.generator.generateCsv(rows);
@ -28,13 +26,6 @@ export class CsvExportController {
return new StreamableFile(buffer, { return new StreamableFile(buffer, {
type: 'text/csv', type: 'text/csv',
disposition: 'attachment; filename=export.csv' disposition: 'attachment; filename=export.csv'
}) });
// response.set({
// 'Content-Type': 'text/csv',
// 'Content-Disposition': 'attachment; filename="export.csv"',
// });
// response.send(blob);
} }
} }

View File

@ -3,6 +3,9 @@ import { HolidayService } from "src/time-and-attendance/domains/services/holiday
import { CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto"; import { CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
const REGULAR = 1; const REGULAR = 1;
const EVENING = 140;
const EMERGENCY = 48;
const HOLIDAY = 104;
const OVERTIME = 43; const OVERTIME = 43;
const VACATION = 109; const VACATION = 109;
const SICK = 105; const SICK = 105;
@ -73,26 +76,46 @@ export const applyOvertimeRequalifications = (
} }
for (const [, rows] of grouped_rows) { for (const [, rows] of grouped_rows) {
const evening_hours = rows.find(r => r.code === EVENING);
const emergency_hours = rows.find(r => r.code === EMERGENCY);
const holiday_hours = rows.find(r => r.code === HOLIDAY);
const regular_hours = rows.find(r => r.code === REGULAR); const regular_hours = rows.find(r => r.code === REGULAR);
const vacation_hours = rows.find(r => r.code === VACATION);
// if no regular hours row, push as is // if no regular hours row, push as is
if (!regular_hours?.quantite_hre) { result.push(...rows); continue; } if (!regular_hours?.quantite_hre) { result.push(...rows); continue; }
// calculate overtime directly from consolidated regular hours const total_hours = (
const overtime_hours = Math.max(0, regular_hours.quantite_hre - WEEKLY_LIMIT_HOURS); regular_hours.quantite_hre
+ (evening_hours?.quantite_hre ?? 0)
+ (emergency_hours?.quantite_hre ?? 0)
+ (holiday_hours?.quantite_hre ?? 0)
+ (vacation_hours?.quantite_hre ?? 0)
)
// calculate overtime directly from consolidated hours
const overtime_hours = Math.max(0, total_hours - WEEKLY_LIMIT_HOURS);
// if no overtime, push as is // if no overtime, push as is
if (overtime_hours <= 0) { result.push(...rows); continue; } if (overtime_hours <= 0) { result.push(...rows); continue; }
// ensures that its not possible to deduct more hours than the amount of regular hours // ensures that its not possible to deduct more hours than the amount of regular or evening hours
const deducted = Math.min(overtime_hours, regular_hours.quantite_hre); const deducted_regular = Math.min(overtime_hours, regular_hours.quantite_hre);
const remaining = regular_hours.quantite_hre - deducted; const remaining_overtime = overtime_hours - deducted_regular;
const deducted_evening = Math.min(remaining_overtime, evening_hours?.quantite_hre ?? 0);
const remaining_regular = (regular_hours.quantite_hre ?? 0) - deducted_regular;
const remaining_evening = (evening_hours?.quantite_hre ?? 0) - deducted_evening;
for (const row of rows) { for (const row of rows) {
if (row === regular_hours) { if (row === regular_hours) {
// pushes the regular row with subtracted overtime hours, if enough hours remaining // pushes the regular row with subtracted overtime hours, if enough hours remaining
if (remaining > 0) { if (remaining_regular > 0) {
result.push({ ...regular_hours, quantite_hre: remaining }); result.push({ ...regular_hours, quantite_hre: remaining_regular });
}
} else if (row === evening_hours) {
// pushes the evening row with subtracted overtime hours, if enough not enough regular hours remaining
if (remaining_evening > 0) {
result.push({ ...evening_hours, quantite_hre: remaining_evening });
} }
} else { } else {
// other rows are left unchanged // other rows are left unchanged
@ -100,11 +123,13 @@ export const applyOvertimeRequalifications = (
} }
} }
//adds a new row with overtime hours deducted from the regular hours //adds a new row with overtime hours deducted from the regular hours
result.push({ if (deducted_regular + deducted_evening > 0) {
...regular_hours, result.push({
code: OVERTIME, ...regular_hours,
quantite_hre: deducted, code: OVERTIME,
}); quantite_hre: deducted_regular + deducted_evening,
});
}
} }
return result; return result;
} }

View File

@ -10,7 +10,7 @@ export class PaidTimeOffController {
) { } ) { }
@Get('totals') @Get('totals')
@ModuleAccessAllowed('timesheets', 'timesheets_approval', 'employee_management') @ModuleAccessAllowed('timesheets')
async getPaidTimeOffTotalsForOneEmployee( async getPaidTimeOffTotalsForOneEmployee(
@Access('email') email: string, @Access('email') email: string,
@Query('email') employee_email?: string, @Query('email') employee_email?: string,

View File

@ -146,13 +146,7 @@ export class GetOverviewService {
switch (type) { switch (type) {
case "EVENING": case "EVENING":
if (total_weekly_hours + hours <= 40) { record.other_hours.evening_hours += Math.min(hours, 8 - daily_hours);
record.other_hours.evening_hours += Math.min(hours, 8 - daily_hours);
record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
} else {
record.other_hours.evening_hours += Math.max(40 - total_weekly_hours, 0);
record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
}
total_weekly_hours += hours; total_weekly_hours += hours;
record.total_hours += hours; record.total_hours += hours;
break; break;
@ -169,12 +163,16 @@ export class GetOverviewService {
record.total_hours += hours; record.total_hours += hours;
total_weekly_hours += hours; total_weekly_hours += hours;
break; break;
case "VACATION": record.other_hours.vacation_hours += hours; case "VACATION":
record.other_hours.vacation_hours += hours;
total_weekly_hours += hours;
break; break;
case "REGULAR": case "REGULAR":
if (total_weekly_hours + hours <= 40) { if (total_weekly_hours + hours <= 40) {
record.regular_hours += Math.min(hours, 8 - daily_hours); record.regular_hours += hours;
record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0); // TODO: ADD DAILY OVERTIME CHECK HERE
// record.regular_hours += Math.min(hours, 8 - daily_hours);
// record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
} else { } else {
record.regular_hours += Math.max(40 - total_weekly_hours, 0); record.regular_hours += Math.max(40 - total_weekly_hours, 0);
record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours); record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
@ -192,9 +190,9 @@ export class GetOverviewService {
record.expenses = Number((record.expenses + amount).toFixed(2)); record.expenses = Number((record.expenses + amount).toFixed(2));
const type = (expense.bank_code?.type || "").toUpperCase(); const type = (expense.bank_code?.type || "").toUpperCase();
const rate = expense.bank_code?.modifier ?? 1; const rate = expense.bank_code?.modifier ?? 1;
const mileage = Number(expense.mileage) / rate; const mileage = Number(expense.mileage);
if (type === "MILEAGE" && rate > 0) { if (type === "MILEAGE" && rate > 0) {
record.mileage = Number((record.mileage += Math.round(mileage)).toFixed(2)); record.mileage = Number((record.mileage += mileage).toFixed(2));
} }
} }

View File

@ -1,4 +1,3 @@
import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/common/utils/constants.utils"; import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/common/utils/constants.utils";
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service"; import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
@ -56,16 +55,28 @@ export class GetTimesheetsOverviewService {
//find user infos using the employee_id //find user infos using the employee_id
const employee = await this.prisma.employees.findUnique({ const employee = await this.prisma.employees.findUnique({
where: { id: employee_id.data }, where: { id: employee_id.data },
select: { daily_expected_hours: true, schedule_preset: true, user: true }, select: { schedule_preset: true, user: true, id: true }
}); });
if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` } if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
if (!employee.user) return { success: false, error: 'USER_NOT_FOUND' };
const contractDetails = await this.prisma.contracts.findUnique({
where: { employee_id: employee.id },
select: {
daily_expected_hours: true,
applicable_overtime: true,
phone_allocation: true,
on_call_allocation: true,
weekend_on_call_allocation: true,
}
});
if (!contractDetails) return { success: false, error: 'CONTRACT_NOT_FOUND' };
//builds employee details //builds employee details
const has_preset_schedule = employee.schedule_preset !== null; const has_preset_schedule = employee.schedule_preset !== null;
const user = employee.user; const user = employee.user;
const employee_fullname = `${user.first_name} ${user.last_name}`.trim(); const employee_fullname = `${user.first_name} ${user.last_name}`.trim();
//maps all timesheet's infos //maps all timesheet's infos
const timesheets = await Promise.all(rows.map((timesheet) => mapOneTimesheet(timesheet))); const timesheets = await Promise.all(rows.map((timesheet) => mapOneTimesheet(timesheet)));
if (!timesheets) return { success: false, error: 'INVALID_TIMESHEET' } if (!timesheets) return { success: false, error: 'INVALID_TIMESHEET' }
@ -73,7 +84,13 @@ export class GetTimesheetsOverviewService {
const data: Timesheets = { const data: Timesheets = {
has_preset_schedule, has_preset_schedule,
employee_fullname, employee_fullname,
daily_expected_hours: employee.daily_expected_hours, contract: {
daily_expected_hours: contractDetails.daily_expected_hours,
applicable_overtime: contractDetails.applicable_overtime,
phone_allocation: Number(contractDetails.phone_allocation),
on_call_allocation: Number(contractDetails.on_call_allocation),
weekend_on_call_allocation: Number(contractDetails.weekend_on_call_allocation),
},
timesheets, timesheets,
} }

View File

@ -1,5 +1,5 @@
import { Type } from "class-transformer"; import { Type } from "class-transformer";
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator"; import { IsArray, IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator";
export class TimesheetEntity { export class TimesheetEntity {
@IsInt() id: number; @IsInt() id: number;
@ -11,10 +11,19 @@ export class TimesheetEntity {
export class Timesheets { export class Timesheets {
@IsBoolean() has_preset_schedule: boolean; @IsBoolean() has_preset_schedule: boolean;
@IsString() employee_fullname: string; @IsString() employee_fullname: string;
@IsInt() daily_expected_hours: number; @Type(() => Contract) contract: Contract;
@Type(() => Number)
@Type(() => Timesheet) timesheets: Timesheet[]; @Type(() => Timesheet) timesheets: Timesheet[];
} }
export class Contract {
@Type(() => Number) daily_expected_hours: number;
@Type(() => Number) phone_allocation: number;
@Type(() => Number) on_call_allocation: number;
@Type(() => Number) weekend_on_call_allocation: number;
@IsArray() @IsString() applicable_overtime: string[];
}
export class Timesheet { export class Timesheet {
@IsInt() timesheet_id: number; @IsInt() timesheet_id: number;
@IsBoolean() is_approved: boolean; @IsBoolean() is_approved: boolean;

View File

@ -82,6 +82,7 @@ export const mapOneTimesheet = (
weekly_hours[subgroup] += hours; weekly_hours[subgroup] += hours;
} }
// TODO: ADD DAILY OVERTIME CHECK HERE
// const dailyOvertimeOwed = Math.max(daily_hours.regular - timesheet.employee.daily_expected_hours, 0) // const dailyOvertimeOwed = Math.max(daily_hours.regular - timesheet.employee.daily_expected_hours, 0)
// daily_hours.overtime = dailyOvertimeOwed; // daily_hours.overtime = dailyOvertimeOwed;
// daily_hours.regular -= dailyOvertimeOwed; // daily_hours.regular -= dailyOvertimeOwed;