implement latest changes to employee payroll process

- Add ContractDaysWeek
- Add Sickness Days to employee export sheet
- Add Gesundheitsbonus value per employee
- Additional fixes
This commit is contained in:
Marcel Arndt
2025-12-12 11:52:03 +01:00
parent 156733f65a
commit 0951282f11
17 changed files with 408 additions and 205 deletions
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "DyflexisEmployee" ADD COLUMN "contractDaysWeek" INTEGER NOT NULL DEFAULT 0;
@@ -553,6 +553,7 @@ model DyflexisEmployee {
surname String
contractStart DateTime
contractEnd DateTime?
contractDaysWeek Int @default(0)
contractHoursWeek Float
contractSalaryHour Float
contractTypeName String?
@@ -109,6 +109,7 @@ export type DyflexisEmployee = {
surname: string;
contractStart: Timestamp;
contractEnd: Timestamp | null;
contractDaysWeek: Generated<number>;
contractHoursWeek: number;
contractSalaryHour: number;
contractTypeName: string | null;
@@ -1,9 +1,9 @@
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { SourceRepository } from './source.repository';
import { ConfigService } from 'src/core/config/config.service';
import { format, Interval } from 'date-fns';
import {
catchError,
combineLatest,
EMPTY,
expand,
forkJoin,
@@ -12,11 +12,15 @@ import {
Observable,
of,
reduce,
switchMap,
tap,
switchMap
} from 'rxjs';
import { format, Interval } from 'date-fns';
import { HolidayRaw, SickDaysRaw } from './source.interface';
import { ConfigService } from 'src/core/config/config.service';
import {
EmployeeContractRaw,
HolidayRaw,
SickDaysRaw,
} from './source.interface';
import { SourceRepository } from './source.repository';
interface DyflexisAuthHeader {
Cookie: string;
@@ -40,11 +44,72 @@ export class DyflexisUserAccessExtractorsService {
) {}
loadData(): Observable<any> {
// return this.extractSicknessDays({
// start: new Date('2025-01-01T00:00:00.000Z'),
// end: new Date('2025-12-31T23:59:59.999Z'),
// });
return this.extractHolidays(new Date('2025-01-01T00:00:00.000Z'));
return combineLatest([
// this.extractWorkdays(),
// this.extractSicknessDays({
// start: new Date('2025-01-01T00:00:00.000Z'),
// end: new Date('2025-12-31T23:59:59.999Z'),
// }),
this.extractHolidays(new Date('2025-01-01T00:00:00.000Z')),
]);
}
extractWorkdays(): Observable<any> {
return this.dyflexisLogin(
this.config.dyflexisAppUsername(),
this.config.dyflexisAppPassword(),
).pipe(
switchMap((authHeader) =>
forkJoin({
authHeader: of(authHeader),
employees: this.fetchEmployees(authHeader),
}),
),
switchMap(({ authHeader, employees }) =>
forkJoin(
employees.map((employee) =>
this.fetchWorkdaysForEmployee(authHeader, employee.id),
),
),
),
map((workdaysPerEmployee) => workdaysPerEmployee.flat(1)),
switchMap((workdays) => from(this.repository.createWorkdays(workdays))),
map((res) => {
return { count: Number(res.at(0).numUpdatedRows) };
}),
);
}
private fetchWorkdaysForEmployee(
authHeader: DyflexisAuthHeader,
employeeId: number,
): Observable<{ employeeId: number; daysPerWeek: number }> {
const endpoint = (employeeId: number) =>
`hamburg/employees/contract/data/${employeeId}`;
return this.http
.get<{
contracts: EmployeeContractRaw[];
}>(`${this.config.dyflexisAppBaseUrl()}${endpoint(employeeId)}`, {
headers: {
...authHeader,
},
})
.pipe(
map((response) =>
response.data.contracts
.sort(
({ startDate: dateA }, { startDate: dateB }) =>
new Date(dateB).getTime() - new Date(dateA).getTime(),
)
.map(({ daysPerWeek }) => daysPerWeek)
.at(0),
),
map((daysPerWeek) => ({
employeeId,
daysPerWeek,
})),
);
}
extractHolidays(year: Date): Observable<any> {
@@ -101,6 +166,9 @@ export class DyflexisUserAccessExtractorsService {
return { bookDate, isLocked, isExcludedFromPayroll, employeeId };
}),
),
catchError(() => {
return [[]];
}),
);
}
@@ -115,7 +183,6 @@ export class DyflexisUserAccessExtractorsService {
employees: this.fetchEmployees(authHeader),
}),
),
tap(({ employees }) => console.log(employees.length)),
switchMap(({ authHeader, employees }) =>
forkJoin(
employees.map((employee) =>
@@ -67,7 +67,7 @@ export interface TourRaw {
beifahrer?: string; // ID
fahrerName?: string;
fahrer?: string; // ID
overRTW?: string;
firmName?: string;
schwerlast?: string;
@@ -184,7 +184,6 @@ export interface FahrzeugItemDto {
verordnungsart: string;
}
export interface SickDaysRaw {
startDate: string;
endDate: string;
@@ -198,4 +197,25 @@ export interface HolidayRaw {
bookDate: string;
isLocked: boolean;
isExcludedFromPayroll: boolean;
}
}
export interface EmployeeContractRaw {
id: number;
officeId: number;
employeeId: number;
typeId: number;
serial: string;
daysPerWeek: number;
hoursPerWeek: number;
upperLimit: any;
startDate: string;
endDate: any;
salaryAmount: number;
salaryPeriod: number;
salaryType: number;
hourlySalary: number;
active: boolean;
workingPattern: any;
isSyncedWithPayroll: boolean;
lastLockDate: string;
}
@@ -22,7 +22,7 @@ import {
TourRaw,
WorkTimeRaw,
} from './source.interface';
import { Kysely } from 'kysely';
import { Kysely, sql } from 'kysely';
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
import { DB } from 'src/core/database/types';
@@ -112,6 +112,23 @@ export class SourceRepository {
});
}
createWorkdays(workdays: { employeeId: number; daysPerWeek: number }[]) {
const values = workdays.map(
({ employeeId, daysPerWeek }) =>
sql`(${employeeId}::int4, ${daysPerWeek}::int4)`,
);
return this.db
.with('v(id, days)', () => sql`(VALUES ${sql.join(values)})`)
.updateTable('DyflexisEmployee as e')
.set({
contractDaysWeek: sql.ref('v.days'),
})
.from('v')
.whereRef('e.id', '=', 'v.id')
.execute();
}
createPatients(
patients: PatientRaw[],
jobId: string,