Passed
Pull Request — master (#220)
by
unknown
01:36
created

server/src/Application/HumanResource/PayrollElements/Query/GetPayrollElementsQueryHandler.ts   A

Complexity

Total Complexity 4
Complexity/F 2

Size

Lines of Code 183
Function Count 2

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 88
mnd 2
bc 2
fnc 2
dl 0
loc 183
bpm 1
cpm 2
noi 0
c 0
b 0
f 0
rs 10

2 Functions

Rating   Name   Duplication   Size   Complexity  
A GetPayrollElementsQueryHandler.getUserMealTickets 0 35 3
B GetPayrollElementsQueryHandler.execute 0 118 1
1
import { Inject } from '@nestjs/common';
2
import { IQueryBus, QueryHandler } from '@nestjs/cqrs';
3
import { InjectEntityManager } from '@nestjs/typeorm';
4
import { EntityManager } from 'typeorm';
5
import { IUserRepository } from 'src/Domain/HumanResource/User/Repository/IUserRepository';
6
import { IUserAdministrativeRepository } from 'src/Domain/HumanResource/User/Repository/IUserAdministrativeRepository';
7
import { LeaveView, PayrollElementsView } from '../View/PayrollElementsView';
8
import { GetPayrollElementsQuery } from './GetPayrollElementsQuery';
9
import { MealTicketSummaryView } from '../../MealTicket/Views/MealTicketSummaryView';
10
import { CountMealTicketPerMonthQuery } from '../../MealTicket/Query/CountMealTicketPerMonthQuery';
11
12
@QueryHandler(GetPayrollElementsQuery)
13
export class GetPayrollElementsQueryHandler {
14
  constructor(
15
    @InjectEntityManager()
16
    private readonly manager: EntityManager,
17
    @Inject('IQueryBus')
18
    private readonly queryBus: IQueryBus,
19
    @Inject('IUserRepository')
20
    private readonly userRepository: IUserRepository,
21
    @Inject('IUserAdministrativeRepository')
22
    private readonly userAdministrativeRepository: IUserAdministrativeRepository
23
  ) {}
24
25
  private async getUserMealTickets(): Promise<Map<string, number>> {
26
    const currentDate = new Date();
27
28
    // NOTE: this code suffers from the "N+1 queries" problem, as we fetch the meal tickets count
29
    // for each user. But we wanted to reuse the existing meal ticket calculation logic,
30
    // which exists in JavaScript code.
31
    // Ideally we would move the meal ticket calculation logic to the database.
32
33
    const mealTicketsByUserId = new Map<string, number>();
34
35
    const _users = await this.userRepository.findUsers(false);
36
37
    for (let user of _users) {
38
      const userAdministrative = await this.userAdministrativeRepository.findOneByUserId(
39
        user.getId()
40
      );
41
42
      if (userAdministrative.getLeavingDate() !== null) {
43
        // User is not working here anymore, don't include them in the payroll elements.
44
        continue;
45
      }
46
47
      const monthSummaries: MealTicketSummaryView[] = await this.queryBus.execute(
48
        new CountMealTicketPerMonthQuery(user, currentDate)
49
      );
50
51
      const thisMonthSummary = monthSummaries.find(
52
        view => view.month === currentDate.getMonth()
53
      );
54
55
      mealTicketsByUserId.set(user.getId(), thisMonthSummary.total);
56
    }
57
58
    return mealTicketsByUserId;
59
  }
60
61
  public async execute(
62
    _: GetPayrollElementsQuery
63
  ): Promise<PayrollElementsView[]> {
64
    const sql = `
65
SELECT
66
  "user".id AS "userId",
67
  "firstName",
68
  "lastName",
69
  "executivePosition",
70
  "joiningDate",
71
  "leavingDate",
72
  "annualEarnings",
73
  ("annualEarnings" / 12) AS "monthlyEarnings",
74
  "workingTime",
75
  "transportFee",
76
  "healthInsurance",
77
  (
78
    SELECT
79
      COALESCE(SUM(leave.time / 60.0 / 7.0), 0)
80
    FROM leave_request
81
    INNER JOIN leave ON leave."leaveRequestId" = leave_request.id
82
    INNER JOIN "user" AS u ON u.id = leave_request."userId" AND u.id = "user".id
83
    WHERE leave_request.status = 'accepted'
84
    AND leave_request.type = 'paid'
85
    AND u.id = "user".id
86
  ) AS "totalPaidLeaves",
87
  (
88
    SELECT
89
      COALESCE(SUM(leave.time / 60.0 / 7.0), 0)
90
    FROM leave_request
91
    INNER JOIN leave ON leave."leaveRequestId" = leave_request.id
92
    INNER JOIN "user" AS u ON u.id = leave_request."userId" AND u.id = "user".id
93
    WHERE leave_request.status = 'accepted'
94
    AND leave_request.type = 'unpaid'
95
  ) AS "totalUnpaidLeaves",
96
  (
97
    SELECT
98
      COALESCE(SUM(leave.time / 60.0 / 7.0), 0)
99
    FROM leave_request
100
    INNER JOIN leave ON leave."leaveRequestId" = leave_request.id
101
    INNER JOIN "user" AS u ON u.id = leave_request."userId" AND u.id = "user".id
102
    WHERE leave_request.status = 'accepted'
103
    AND leave_request.type = 'medical'
104
  ) AS "totalMedicalLeaves",
105
  (
106
    SELECT
107
      COALESCE(SUM(leave.time / 60.0 / 7.0), 0)
108
    FROM leave_request
109
    INNER JOIN leave ON leave."leaveRequestId" = leave_request.id
110
    INNER JOIN "user" AS u ON u.id = leave_request."userId" AND u.id = "user".id
111
    WHERE leave_request.status = 'accepted'
112
    AND leave_request.type = 'special'
113
  ) AS "totalSpecialLeaves"
114
FROM "user"
115
INNER JOIN user_administrative AS user_a ON "user"."userAdministrativeId" = user_a.id
116
WHERE user_a."leavingDate" IS NULL;
117
    `;
118
119
    const rows = await this.manager.query(sql);
120
121
    const leaveSql = `
122
SELECT
123
  "user".id AS "userId",
124
  "startDate",
125
  "endDate"
126
FROM leave_request
127
INNER JOIN "user" ON "user".id = leave_request."userId"
128
WHERE leave_request.status = 'accepted'
129
ORDER BY "startDate";
130
    `;
131
132
    const leaveRows = await this.manager.query(leaveSql);
133
134
    const mealTicketsByUserId = await this.getUserMealTickets();
135
136
    return rows.map(
137
      ({
138
        userId,
139
        firstName,
140
        lastName,
141
        executivePosition,
142
        joiningDate,
143
        leavingDate,
144
        annualEarnings,
145
        monthlyEarnings,
146
        workingTime,
147
        transportFee,
148
        healthInsurance,
149
        totalPaidLeaves,
150
        totalUnpaidLeaves,
151
        totalMedicalLeaves,
152
        totalSpecialLeaves
153
      }) => {
154
        const leaves = leaveRows
155
          .filter(({ userId: leaveUserId }) => leaveUserId === userId)
156
          .map(({ startDate, endDate }) => new LeaveView(startDate, endDate));
157
158
        const mealTickets = mealTicketsByUserId.get(userId);
159
160
        return new PayrollElementsView(
161
          userId,
162
          firstName,
163
          lastName,
164
          executivePosition,
165
          joiningDate,
166
          leavingDate,
167
          +annualEarnings * 0.01,
168
          +monthlyEarnings * 0.01,
169
          workingTime,
170
          +transportFee * 0.01,
171
          mealTickets,
172
          healthInsurance,
173
          +totalPaidLeaves,
174
          +totalUnpaidLeaves,
175
          +totalMedicalLeaves,
176
          +totalSpecialLeaves,
177
          leaves
178
        );
179
      }
180
    );
181
  }
182
}
183