feat: add associated entities to anomalies
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Anomaly" ADD COLUMN "associatedEntities" JSONB DEFAULT '{}';
|
||||
@@ -508,7 +508,7 @@ model Employee {
|
||||
}
|
||||
|
||||
model DyflexisEmployee {
|
||||
id Int @id
|
||||
id Int @id
|
||||
firstname String
|
||||
surname String
|
||||
contractStart DateTime
|
||||
@@ -580,18 +580,19 @@ model Trace {
|
||||
}
|
||||
|
||||
model Anomaly {
|
||||
id String @id
|
||||
type String
|
||||
description String
|
||||
groupKey String
|
||||
groupDescription String
|
||||
workedOnBy String
|
||||
sleepTimer Int
|
||||
sleepSince DateTime?
|
||||
solution SolutionTarget[] @relation()
|
||||
resolvedAt DateTime?
|
||||
manuallyResolved Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
id String @id
|
||||
type String
|
||||
description String
|
||||
groupKey String
|
||||
groupDescription String
|
||||
associatedEntities Json? @default("{}")
|
||||
workedOnBy String
|
||||
sleepTimer Int
|
||||
sleepSince DateTime?
|
||||
solution SolutionTarget[] @relation()
|
||||
resolvedAt DateTime?
|
||||
manuallyResolved Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model SolutionTarget {
|
||||
|
||||
@@ -55,6 +55,22 @@ enum ApprovalState {
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
type AssociatedEntities {
|
||||
attendanceRegistration: [AttendanceRegistration!]
|
||||
employee: [Employee!]
|
||||
plannedTime: [PlannedTime!]
|
||||
tour: [Tour!]
|
||||
workTime: [WorkTime!]
|
||||
}
|
||||
|
||||
type AttendanceRegistration {
|
||||
attendanceRegistrationId: Int!
|
||||
dateTime: DateTime!
|
||||
employeeId: Int!
|
||||
event: String!
|
||||
personnelNumber: String!
|
||||
}
|
||||
|
||||
"""
|
||||
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
|
||||
"""
|
||||
@@ -65,6 +81,16 @@ type DocumentInfo {
|
||||
tdLocation: String
|
||||
}
|
||||
|
||||
type Employee {
|
||||
id: String!
|
||||
name: String!
|
||||
occuptationalHealthExamination: DateTime
|
||||
personTransportCertificate: DateTime
|
||||
personnelNumber: String!
|
||||
qualification: String
|
||||
surname: String!
|
||||
}
|
||||
|
||||
type HomeDashboardKPIs {
|
||||
anomalyCount: Int!
|
||||
billableTourCount: Int!
|
||||
@@ -148,12 +174,26 @@ type PaginatedTour {
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
type PlannedTime {
|
||||
department: String!
|
||||
duration: Int!
|
||||
endDate: DateTime!
|
||||
firstname: String!
|
||||
id: String!
|
||||
note: String
|
||||
pause: Int!
|
||||
startDate: DateTime!
|
||||
surname: String!
|
||||
userId: String!
|
||||
}
|
||||
|
||||
type Query {
|
||||
accountingStateMeta(state: AccountingState!): TourStateMeta!
|
||||
accountingTours(cursor: String, filters: AccountingFilterArgs, state: AccountingState!, take: Int! = 10): PaginatedTour!
|
||||
anomalies(cursor: String, take: Int! = 10): PaginatedAnomaly!
|
||||
anomaliesProto: [Anomaly!]!
|
||||
anomaly(id: String!): Anomaly!
|
||||
associatedEntities(id: String!): AssociatedEntities!
|
||||
groupedAnomalies(cursor: String, take: Int! = 10): PaginatedAnomalyGroup!
|
||||
homeDashboardKpis: HomeDashboardKPIs!
|
||||
managerKpi(ordinanceType: String!): KpiInfo!
|
||||
@@ -337,4 +377,16 @@ type TourStateMeta {
|
||||
negativeRevenueDeviation: Float
|
||||
positiveRevenueDeviation: Float
|
||||
revenueSum: Float
|
||||
}
|
||||
|
||||
type WorkTime {
|
||||
department: String!
|
||||
duration: Int!
|
||||
endDate: DateTime!
|
||||
firstname: String!
|
||||
id: String!
|
||||
pause: Int!
|
||||
startDate: DateTime!
|
||||
surname: String!
|
||||
userId: String!
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export type Anomaly = {
|
||||
description: string;
|
||||
groupKey: string;
|
||||
groupDescription: string;
|
||||
associatedEntities: Generated<unknown | null>;
|
||||
workedOnBy: string;
|
||||
sleepTimer: number;
|
||||
sleepSince: Timestamp | null;
|
||||
|
||||
+112
-1
@@ -1,6 +1,115 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
import { Anomaly, SolutionTarget, SolutionTargetStep } from '@prisma/client';
|
||||
import {
|
||||
Anomaly,
|
||||
AttendanceRegistration,
|
||||
Employee,
|
||||
PlannedTime,
|
||||
SolutionTarget,
|
||||
SolutionTargetStep,
|
||||
WorkTime,
|
||||
} from '@prisma/client';
|
||||
import { JsonValue } from '@prisma/client/runtime/library';
|
||||
import { Paginated } from 'src/core/base/pagination.input';
|
||||
import { AnomalyAsssociatedEntities } from '../feat-business-objects/entities/anomaly/anomalies.dto';
|
||||
import { TourObjectType } from '../app-accounting/tour.object-type';
|
||||
|
||||
@ObjectType('PlannedTime')
|
||||
export class PlannedTimeObjectType implements PlannedTime {
|
||||
@Field()
|
||||
id: string;
|
||||
@Field()
|
||||
userId: string;
|
||||
@Field()
|
||||
firstname: string;
|
||||
@Field()
|
||||
surname: string;
|
||||
@Field(() => Date)
|
||||
startDate: Date;
|
||||
@Field(() => Date)
|
||||
endDate: Date;
|
||||
@Field()
|
||||
department: string;
|
||||
@Field(() => Int)
|
||||
pause: number;
|
||||
@Field(() => Int)
|
||||
duration: number;
|
||||
@Field({nullable: true})
|
||||
note: string;
|
||||
}
|
||||
|
||||
@ObjectType('WorkTime')
|
||||
export class WorkTimeObjectType implements WorkTime {
|
||||
@Field()
|
||||
id: string;
|
||||
@Field()
|
||||
userId: string;
|
||||
@Field()
|
||||
firstname: string;
|
||||
@Field()
|
||||
surname: string;
|
||||
@Field(() => Date)
|
||||
startDate: Date;
|
||||
@Field(() => Date)
|
||||
endDate: Date;
|
||||
@Field()
|
||||
department: string;
|
||||
@Field(() => Int)
|
||||
pause: number;
|
||||
@Field(() => Int)
|
||||
duration: number;
|
||||
}
|
||||
|
||||
@ObjectType('Employee')
|
||||
export class EmployeeObjectType implements Employee {
|
||||
@Field()
|
||||
name: string;
|
||||
@Field()
|
||||
id: string;
|
||||
@Field()
|
||||
surname: string;
|
||||
@Field()
|
||||
personnelNumber: string;
|
||||
// @Field()
|
||||
mobile: string;
|
||||
@Field({ nullable: true })
|
||||
qualification: string;
|
||||
@Field(() => Date, { nullable: true })
|
||||
occuptationalHealthExamination: Date;
|
||||
@Field(() => Date, { nullable: true })
|
||||
personTransportCertificate: Date;
|
||||
}
|
||||
|
||||
@ObjectType('AttendanceRegistration')
|
||||
export class AttendanceRegistrationObjectType
|
||||
implements AttendanceRegistration
|
||||
{
|
||||
@Field()
|
||||
personnelNumber: string;
|
||||
@Field(() => Int)
|
||||
attendanceRegistrationId: number;
|
||||
@Field(() => Int)
|
||||
employeeId: number;
|
||||
@Field(() => Date)
|
||||
dateTime: Date;
|
||||
@Field()
|
||||
event: string;
|
||||
}
|
||||
|
||||
@ObjectType('AssociatedEntities')
|
||||
export class AssociatedEntitiesObjectType
|
||||
implements AnomalyAsssociatedEntities
|
||||
{
|
||||
@Field(() => [TourObjectType], { nullable: true })
|
||||
tour?: TourObjectType[];
|
||||
@Field(() => [EmployeeObjectType], { nullable: true })
|
||||
employee?: EmployeeObjectType[];
|
||||
@Field(() => [PlannedTimeObjectType], { nullable: true })
|
||||
plannedTime?: PlannedTimeObjectType[];
|
||||
@Field(() => [WorkTimeObjectType], { nullable: true })
|
||||
workTime?: WorkTimeObjectType[];
|
||||
@Field(() => [AttendanceRegistrationObjectType], { nullable: true })
|
||||
attendanceRegistration?: AttendanceRegistrationObjectType[];
|
||||
}
|
||||
|
||||
@ObjectType('SolutionTarget')
|
||||
export class SolutionTargetObjectType implements SolutionTarget {
|
||||
@@ -32,6 +141,8 @@ export class AnomalyObjectType implements Anomaly {
|
||||
groupKey: string;
|
||||
@Field()
|
||||
groupDescription: string;
|
||||
// @Field()
|
||||
associatedEntities: JsonValue;
|
||||
@Field()
|
||||
workedOnBy: string;
|
||||
@Field(() => Int)
|
||||
|
||||
+6
@@ -18,6 +18,7 @@ import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomal
|
||||
import { KpiService } from '../feat-kpis/kpi.service';
|
||||
import {
|
||||
AnomalyObjectType,
|
||||
AssociatedEntitiesObjectType,
|
||||
PaginatedAnomalyGroupObjectType,
|
||||
PaginatedAnomalyObjectType,
|
||||
SolutionTargetObjectType,
|
||||
@@ -99,6 +100,11 @@ export class ControlCenterResolver {
|
||||
return this.anomalyService.acceptAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
@Query(() => AssociatedEntitiesObjectType)
|
||||
async associatedEntities(@Args({ name: 'id' }) anomalyId: string) {
|
||||
return this.anomalyService.getAssociatedEntities(anomalyId);
|
||||
}
|
||||
|
||||
//! Used when creating anomaly list
|
||||
// @ResolveField(() => TourObjectType, { nullable: true })
|
||||
// tour(@Parent() anomaly: AnomalyObjectType) {
|
||||
|
||||
+27
-1
@@ -5,9 +5,15 @@ import { AnomalyDetectorService } from './anomaly-detector.service';
|
||||
|
||||
export type AnomalySkeleton = Pick<
|
||||
Prisma.AnomalyCreateInput,
|
||||
'createdAt' | 'workedOnBy' | 'sleepTimer' | 'sleepSince'
|
||||
| 'associatedEntities'
|
||||
| 'createdAt'
|
||||
| 'workedOnBy'
|
||||
| 'sleepTimer'
|
||||
| 'sleepSince'
|
||||
>;
|
||||
export abstract class AnomalyDetector {
|
||||
private associatedEntities: Record<string, { id: string }[]> = {};
|
||||
|
||||
constructor(protected readonly dataService: AnomalyDetectorService) {}
|
||||
/**
|
||||
* Unique identifier of the Anomaly Type which is implemented by the class
|
||||
@@ -25,8 +31,28 @@ export abstract class AnomalyDetector {
|
||||
abstract createDescription(data: unknown): string;
|
||||
abstract createSolution(data: unknown): Prisma.SolutionTargetCreateInput[];
|
||||
|
||||
protected addAssociatedEntity(type: string, id: string): void {
|
||||
this.associatedEntities = {
|
||||
...this.associatedEntities,
|
||||
[type]: [
|
||||
...(this.associatedEntities[type]?.length
|
||||
? this.associatedEntities[type]
|
||||
: []),
|
||||
{ id },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
protected flushAssociatedEntities(): void {
|
||||
this.associatedEntities = {};
|
||||
}
|
||||
|
||||
protected getAnomalySkeleton(): AnomalySkeleton {
|
||||
const associatedEntities = this.associatedEntities;
|
||||
this.flushAssociatedEntities();
|
||||
|
||||
return {
|
||||
associatedEntities,
|
||||
createdAt: new Date(),
|
||||
workedOnBy: '',
|
||||
sleepTimer: 60 * 60 * 1000,
|
||||
|
||||
+2
-1
@@ -89,7 +89,8 @@ export class AnomalyDetectorService {
|
||||
],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
let startOfGivenDay: Date;
|
||||
if (day.getHours() >= 0 && day.getHours() < 3) {
|
||||
// TODO: Is there a better solution? This seems a bit flacky
|
||||
if (day.getHours() >= 0 && day.getHours() < 2) {
|
||||
startOfGivenDay = addDays(day, -1);
|
||||
} else {
|
||||
startOfGivenDay = startOfDay(day);
|
||||
|
||||
+11
-1
@@ -17,7 +17,14 @@ export class DriverCertificateDetector extends AnomalyDetector {
|
||||
toursInRange
|
||||
.filter(({ driverId }) => !!driverId)
|
||||
.map(
|
||||
async ({ driverId, startDate, driverName, carName, operationId }) => {
|
||||
async ({
|
||||
id: tourId,
|
||||
driverId,
|
||||
startDate,
|
||||
driverName,
|
||||
carName,
|
||||
operationId,
|
||||
}) => {
|
||||
try {
|
||||
const driverEmployee =
|
||||
await this.dataService.findEmployeeById(driverId);
|
||||
@@ -37,6 +44,9 @@ export class DriverCertificateDetector extends AnomalyDetector {
|
||||
startDate,
|
||||
)
|
||||
) {
|
||||
this.addAssociatedEntity('employee', driverEmployee.id);
|
||||
this.addAssociatedEntity('tour', tourId);
|
||||
|
||||
return this.createAnomaly(
|
||||
`${operationId}-${driverId}-${this.type}`,
|
||||
operationId,
|
||||
|
||||
+12
-2
@@ -17,8 +17,18 @@ export class DriverCodriverSameDetector extends AnomalyDetector {
|
||||
|
||||
const anomalies = await Promise.all(
|
||||
toursInRange.map(
|
||||
({ operationId, carName, startDate, driverId, codriverId }) => {
|
||||
if (driverId === codriverId) {
|
||||
({
|
||||
id: tourId,
|
||||
operationId,
|
||||
carName,
|
||||
startDate,
|
||||
driverId,
|
||||
codriverId,
|
||||
}) => {
|
||||
if (driverId === codriverId && driverId !== null) {
|
||||
this.addAssociatedEntity('tour', tourId);
|
||||
this.addAssociatedEntity('employee', driverId);
|
||||
|
||||
return this.createAnomaly(
|
||||
`${operationId}-${this.type}`,
|
||||
operationId,
|
||||
|
||||
+33
-8
@@ -152,6 +152,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
|
||||
private async detectAnomalyForEmployee(
|
||||
{
|
||||
id: tourId,
|
||||
driverId,
|
||||
codriverId,
|
||||
startDate,
|
||||
@@ -174,15 +175,15 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
|
||||
const startOfTour = this.getStartOfTour(
|
||||
new Date(startDate),
|
||||
[startBegin, startEnd, target, targetBegin, empty].filter(
|
||||
(val) => !!val,
|
||||
)[0],
|
||||
[startBegin, startEnd, target, targetBegin, empty]
|
||||
.filter((val) => !!val)
|
||||
.at(0),
|
||||
);
|
||||
|
||||
if (!startOfTour) return Promise.resolve(undefined);
|
||||
|
||||
if (this.validateEmployee(employee)) {
|
||||
const [isEmployeeLoggedIn, checkedInterval] =
|
||||
const [isEmployeeLoggedIn, checkedInterval, associatedEntities] =
|
||||
await this.isEmployeeLoggedInAtDateTime(
|
||||
employee.personnelNumber,
|
||||
startOfTour,
|
||||
@@ -190,8 +191,14 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
|
||||
if (!isEmployeeLoggedIn) {
|
||||
const employeeName = `${employee.name} ${employee.surname}`;
|
||||
this.addAssociatedEntity('employee', employee.id);
|
||||
this.addAssociatedEntity('tour', tourId);
|
||||
Object.entries(associatedEntities).map(([type, entityIds]) =>
|
||||
entityIds.map((id) => this.addAssociatedEntity(type, id)),
|
||||
);
|
||||
|
||||
return this.createAnomaly(
|
||||
`${operationId}-${driverId}-${this.type}`,
|
||||
`${operationId}-${employeeId}-${this.type}`,
|
||||
operationId,
|
||||
employeeName,
|
||||
employee.mobile,
|
||||
@@ -258,7 +265,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
private async isEmployeeLoggedInAtDateTime(
|
||||
personnelNumber: string,
|
||||
datetimeToCheck: Date,
|
||||
): Promise<[boolean, Interval]> {
|
||||
): Promise<[boolean, Interval, Record<string, string[]>]> {
|
||||
const workTime = await this.dataService.findWorkTimeByPersonnelNumberForDay(
|
||||
personnelNumber,
|
||||
datetimeToCheck,
|
||||
@@ -288,6 +295,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
||||
isWithinInterval(datetimeToCheck, interval),
|
||||
interval,
|
||||
{ workTime: [workTime.id] },
|
||||
];
|
||||
} else if (
|
||||
firstClockedIn &&
|
||||
@@ -300,6 +308,12 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
start: firstClockedIn.dateTime,
|
||||
end: undefined,
|
||||
},
|
||||
{
|
||||
attendanceRegistration: [
|
||||
`${firstClockedIn.attendanceRegistrationId}`,
|
||||
`${lastClockedOut.attendanceRegistrationId}`,
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if (firstClockedIn && lastClockedOut) {
|
||||
const interval = {
|
||||
@@ -310,6 +324,12 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
||||
isWithinInterval(datetimeToCheck, interval),
|
||||
interval,
|
||||
{
|
||||
attendanceRegistration: [
|
||||
`${firstClockedIn.attendanceRegistrationId}`,
|
||||
`${lastClockedOut.attendanceRegistrationId}`,
|
||||
],
|
||||
},
|
||||
];
|
||||
} else if (firstClockedIn && !lastClockedOut) {
|
||||
return [
|
||||
@@ -319,9 +339,14 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
datetimeToCheckWithBuffer,
|
||||
),
|
||||
{ start: firstClockedIn.dateTime, end: undefined },
|
||||
{
|
||||
attendanceRegistration: [
|
||||
`${firstClockedIn.attendanceRegistrationId}`,
|
||||
],
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [false, undefined];
|
||||
return [false, undefined, {}];
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error, {
|
||||
@@ -330,7 +355,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
firstClockedIn,
|
||||
lastClockedOut,
|
||||
});
|
||||
return [false, undefined];
|
||||
return [false, undefined, {}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -57,6 +57,15 @@ export class WorkingEmployeeNotInDistributionSystemDetector extends AnomalyDetec
|
||||
employee.id,
|
||||
);
|
||||
if (toursWithEmployee.length === 0) {
|
||||
this.addAssociatedEntity(
|
||||
'attendanceRegistration',
|
||||
`${attendanceRegistrationId}`,
|
||||
);
|
||||
this.addAssociatedEntity('employee', employee.id);
|
||||
plannedTime.map(({ id }) =>
|
||||
this.addAssociatedEntity('plannedTime', id),
|
||||
);
|
||||
|
||||
return this.createAnomaly(
|
||||
`${attendanceRegistrationId}-${employee.id}`,
|
||||
format(new Date(dateTime), 'yyyy-MM-dd'),
|
||||
|
||||
+16
@@ -1,3 +1,11 @@
|
||||
import {
|
||||
AttendanceRegistration,
|
||||
Employee,
|
||||
PlannedTime,
|
||||
Tour,
|
||||
WorkTime,
|
||||
} from '@prisma/client';
|
||||
|
||||
export interface AnomalyCreateDto {
|
||||
id: string;
|
||||
type: string;
|
||||
@@ -25,3 +33,11 @@ export interface AnomalyUpdateDto {
|
||||
resolvedAt: Date;
|
||||
manuallyResolved: boolean;
|
||||
}
|
||||
|
||||
export interface AnomalyAsssociatedEntities {
|
||||
tour?: Tour[];
|
||||
plannedTime?: PlannedTime[];
|
||||
workTime?: WorkTime[];
|
||||
attendanceRegistration?: AttendanceRegistration[];
|
||||
employee?: Employee[];
|
||||
}
|
||||
|
||||
+65
-3
@@ -1,12 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Anomaly, SolutionTarget, SolutionTargetStep } from '@prisma/client';
|
||||
import { AnomalyCreateDto } from './anomalies.dto';
|
||||
import { AnomalyAsssociatedEntities, AnomalyCreateDto } from './anomalies.dto';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
import { AnomalyRepository } from './anomaly.repository';
|
||||
import { TourService } from '../tour/tour.service';
|
||||
import { EmployeeService } from '../employee/employee.service';
|
||||
import { PlannedTimeService } from '../planned-time/planned-time.service';
|
||||
import { WorkTimeService } from '../work-time/work-time.service';
|
||||
import { AttendanceRegistrationService } from '../attendance-registration/attendance-registration.service';
|
||||
|
||||
@Injectable()
|
||||
export class AnomalyService {
|
||||
constructor(private readonly repository: AnomalyRepository) {}
|
||||
private logger = new Logger(AnomalyService.name);
|
||||
|
||||
constructor(
|
||||
private readonly repository: AnomalyRepository,
|
||||
private readonly attendanceRegistrationService: AttendanceRegistrationService,
|
||||
private readonly employeeService: EmployeeService,
|
||||
private readonly plannedTimeService: PlannedTimeService,
|
||||
private readonly tourService: TourService,
|
||||
private readonly workTimeService: WorkTimeService,
|
||||
) {}
|
||||
|
||||
async createEvenIfExists(anomaly: AnomalyCreateDto): Promise<Anomaly> {
|
||||
return this.repository.createEvenIfExists(anomaly);
|
||||
@@ -44,6 +58,54 @@ export class AnomalyService {
|
||||
return this.repository.countVisibleAnomalies(types);
|
||||
}
|
||||
|
||||
async getAssociatedEntities(
|
||||
anomalyId: string,
|
||||
): Promise<AnomalyAsssociatedEntities> {
|
||||
const { associatedEntities } = await this.findById(anomalyId);
|
||||
|
||||
const entityList = await Promise.all(
|
||||
Object.entries(associatedEntities).map(
|
||||
async ([type, ids]: [string, { id: string }[]]) => {
|
||||
switch (type) {
|
||||
case 'tour':
|
||||
return Promise.all(
|
||||
ids.map(({ id }) => this.tourService.findById(id)),
|
||||
).then((tour) => ({ tour }));
|
||||
case 'plannedTime':
|
||||
return Promise.all(
|
||||
ids.map(({ id }) => this.plannedTimeService.findById(id)),
|
||||
).then((plannedTime) => ({ plannedTime }));
|
||||
case 'workTime':
|
||||
return Promise.all(
|
||||
ids.map(({ id }) => this.workTimeService.findById(id)),
|
||||
).then((workTime) => ({ workTime }));
|
||||
case 'attendanceRegistration':
|
||||
return Promise.all(
|
||||
ids.map(({ id }) =>
|
||||
this.attendanceRegistrationService.findById(+id),
|
||||
),
|
||||
).then((attendanceRegistration) => ({ attendanceRegistration }));
|
||||
case 'employee':
|
||||
return Promise.all(
|
||||
ids.map(({ id }) => this.employeeService.findById(id)),
|
||||
).then((employee) => ({ employee }));
|
||||
default:
|
||||
this.logger.error(
|
||||
`Associated Entity type not implemented: ${type}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
return entityList.reduce(
|
||||
(sum, cur) => ({
|
||||
...sum,
|
||||
...cur,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
async findSolutionTargetsByAnomalyId(
|
||||
anomalyId: string,
|
||||
): Promise<SolutionTarget[]> {
|
||||
|
||||
+6
@@ -7,6 +7,12 @@ import { PrismaService } from 'src/core/database/prisma.service';
|
||||
export class PlannedTimeRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findById(id: string): Promise<PlannedTime> {
|
||||
return await this.prisma.plannedTime.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
|
||||
+4
@@ -7,6 +7,10 @@ import { PlannedTimeRepository } from './planned-time.repository';
|
||||
export class PlannedTimeService {
|
||||
constructor(private readonly repository: PlannedTimeRepository) {}
|
||||
|
||||
async findById(id: string): Promise<PlannedTime> {
|
||||
return this.repository.findById(id);
|
||||
}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
|
||||
+6
@@ -80,6 +80,12 @@ export class TourRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async findById(tourId: string): Promise<Tour> {
|
||||
return await this.prisma.tour.findUnique({
|
||||
where: { id: tourId },
|
||||
});
|
||||
}
|
||||
|
||||
async findInInterval(
|
||||
interval: Interval,
|
||||
ordinanceTypes: string[] = [],
|
||||
|
||||
+4
@@ -28,6 +28,10 @@ export class TourService {
|
||||
);
|
||||
}
|
||||
|
||||
async findById(tourId: string): Promise<Tour> {
|
||||
return this.repository.findById(tourId);
|
||||
}
|
||||
|
||||
async findByOperationId(operationId: string): Promise<Tour> {
|
||||
return this.repository.findByOperationId(operationId);
|
||||
}
|
||||
|
||||
+6
@@ -7,6 +7,12 @@ import { PrismaService } from 'src/core/database/prisma.service';
|
||||
export class WorkTimeRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findById(id: string): Promise<WorkTime> {
|
||||
return await this.prisma.workTime.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
|
||||
+4
@@ -7,6 +7,10 @@ import { WorkTimeRepository } from './work-time.repository';
|
||||
export class WorkTimeService {
|
||||
constructor(private readonly repository: WorkTimeRepository) {}
|
||||
|
||||
async findById(id: string): Promise<WorkTime> {
|
||||
return this.repository.findById(id);
|
||||
}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
|
||||
@@ -316,6 +316,10 @@ export class KpiService {
|
||||
include: {
|
||||
state: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: 'asc'
|
||||
},
|
||||
take: 1000
|
||||
});
|
||||
const timePerTickets = movedTickets
|
||||
.map(({ state }) => {
|
||||
|
||||
Reference in New Issue
Block a user