feat: add associated entities to anomalies
This commit is contained in:
parent
b31df96a44
commit
d9b615fd47
|
|
@ -3,19 +3,17 @@ import {
|
|||
ErrorHandler,
|
||||
importProvidersFrom,
|
||||
provideZoneChangeDetection,
|
||||
provideAppInitializer,
|
||||
} from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideRouter, withComponentInputBinding } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { MsAuthenticationModule } from './core/components/ms-authentication/ms-authentication.module';
|
||||
import { environment } from '../environments/environment';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { graphqlProvider } from './graphql.provider';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
||||
import { faroInitializer } from './core/telemetry/faro-initializer';
|
||||
import { environment } from '../environments/environment';
|
||||
import { routes } from './app.routes';
|
||||
import { MsAuthenticationModule } from './core/components/ms-authentication/ms-authentication.module';
|
||||
import { GlobalErrorHandler } from './core/telemetry/global-error-handler';
|
||||
import { graphqlProvider } from './graphql.provider';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
|
|
@ -28,7 +26,7 @@ export const appConfig: ApplicationConfig = {
|
|||
useClass: GlobalErrorHandler,
|
||||
},
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideRouter(routes, withComponentInputBinding()),
|
||||
provideAnimationsAsync(),
|
||||
importProvidersFrom(
|
||||
MsAuthenticationModule.forRoot({
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ export type Anomaly = {
|
|||
sleepSince?: Maybe<Scalars['DateTime']['output']>;
|
||||
sleepTimer: Scalars['Int']['output'];
|
||||
solution: Array<SolutionTarget>;
|
||||
tour?: Maybe<Tour>;
|
||||
type: Scalars['String']['output'];
|
||||
workedOnBy: Scalars['String']['output'];
|
||||
};
|
||||
|
|
@ -76,12 +75,41 @@ export enum ApprovalState {
|
|||
Unknown = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export type AssociatedEntities = {
|
||||
__typename?: 'AssociatedEntities';
|
||||
attendanceRegistration?: Maybe<Array<AttendanceRegistration>>;
|
||||
employee?: Maybe<Array<Employee>>;
|
||||
plannedTime?: Maybe<Array<PlannedTime>>;
|
||||
tour?: Maybe<Array<Tour>>;
|
||||
workTime?: Maybe<Array<WorkTime>>;
|
||||
};
|
||||
|
||||
export type AttendanceRegistration = {
|
||||
__typename?: 'AttendanceRegistration';
|
||||
attendanceRegistrationId: Scalars['Int']['output'];
|
||||
dateTime: Scalars['DateTime']['output'];
|
||||
employeeId: Scalars['Int']['output'];
|
||||
event: Scalars['String']['output'];
|
||||
personnelNumber: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type DocumentInfo = {
|
||||
__typename?: 'DocumentInfo';
|
||||
approval: ApprovalInfo;
|
||||
tdLocation?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Employee = {
|
||||
__typename?: 'Employee';
|
||||
id: Scalars['String']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
occuptationalHealthExamination?: Maybe<Scalars['DateTime']['output']>;
|
||||
personTransportCertificate?: Maybe<Scalars['DateTime']['output']>;
|
||||
personnelNumber: Scalars['String']['output'];
|
||||
qualification?: Maybe<Scalars['String']['output']>;
|
||||
surname: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type HomeDashboardKpIs = {
|
||||
__typename?: 'HomeDashboardKPIs';
|
||||
anomalyCount: Scalars['Int']['output'];
|
||||
|
|
@ -194,6 +222,20 @@ export type PaginatedTour = {
|
|||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type PlannedTime = {
|
||||
__typename?: 'PlannedTime';
|
||||
department: Scalars['String']['output'];
|
||||
duration: Scalars['Int']['output'];
|
||||
endDate: Scalars['DateTime']['output'];
|
||||
firstname: Scalars['String']['output'];
|
||||
id: Scalars['String']['output'];
|
||||
note?: Maybe<Scalars['String']['output']>;
|
||||
pause: Scalars['Int']['output'];
|
||||
startDate: Scalars['DateTime']['output'];
|
||||
surname: Scalars['String']['output'];
|
||||
userId: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
accountingStateMeta: TourStateMeta;
|
||||
|
|
@ -201,6 +243,7 @@ export type Query = {
|
|||
anomalies: PaginatedAnomaly;
|
||||
anomaliesProto: Array<Anomaly>;
|
||||
anomaly: Anomaly;
|
||||
associatedEntities: AssociatedEntities;
|
||||
groupedAnomalies: PaginatedAnomalyGroup;
|
||||
homeDashboardKpis: HomeDashboardKpIs;
|
||||
managerKpi: KpiInfo;
|
||||
|
|
@ -242,6 +285,11 @@ export type QueryAnomalyArgs = {
|
|||
};
|
||||
|
||||
|
||||
export type QueryAssociatedEntitiesArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGroupedAnomaliesArgs = {
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
take?: Scalars['Int']['input'];
|
||||
|
|
@ -492,8 +540,28 @@ export type TourStateMeta = {
|
|||
revenueSum?: Maybe<Scalars['Float']['output']>;
|
||||
};
|
||||
|
||||
export type WorkTime = {
|
||||
__typename?: 'WorkTime';
|
||||
department: Scalars['String']['output'];
|
||||
duration: Scalars['Int']['output'];
|
||||
endDate: Scalars['DateTime']['output'];
|
||||
firstname: Scalars['String']['output'];
|
||||
id: Scalars['String']['output'];
|
||||
pause: Scalars['Int']['output'];
|
||||
startDate: Scalars['DateTime']['output'];
|
||||
surname: Scalars['String']['output'];
|
||||
userId: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type AnomalyAllFragment = { __typename?: 'Anomaly', id: string, type: string, description: string, groupKey: string, groupDescription: string, sleepTimer: number, sleepSince?: any | null, workedOnBy: string, resolvedAt?: any | null, createdAt: any, solution: Array<{ __typename?: 'SolutionTarget', anomalyId: string, id: string, name: string, steps: Array<{ __typename?: 'SolutionTargetStep', description: string, id: string, solutionTargetId: string }> }> };
|
||||
|
||||
export type GetAnomalyQueryVariables = Exact<{
|
||||
id: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAnomalyQuery = { __typename?: 'Query', anomaly: { __typename?: 'Anomaly', id: string, type: string, description: string, groupKey: string, groupDescription: string, sleepTimer: number, sleepSince?: any | null, workedOnBy: string, resolvedAt?: any | null, createdAt: any, solution: Array<{ __typename?: 'SolutionTarget', anomalyId: string, id: string, name: string, steps: Array<{ __typename?: 'SolutionTargetStep', description: string, id: string, solutionTargetId: string }> }> } };
|
||||
|
||||
export type GetAnomaliesQueryVariables = Exact<{
|
||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||
take: Scalars['Int']['input'];
|
||||
|
|
@ -524,6 +592,23 @@ export type AcceptAnomalyMutationVariables = Exact<{
|
|||
|
||||
export type AcceptAnomalyMutation = { __typename?: 'Mutation', acceptAnomaly: { __typename?: 'Anomaly', id: string, type: string, description: string, groupKey: string, groupDescription: string, sleepTimer: number, sleepSince?: any | null, workedOnBy: string, resolvedAt?: any | null, createdAt: any, solution: Array<{ __typename?: 'SolutionTarget', anomalyId: string, id: string, name: string, steps: Array<{ __typename?: 'SolutionTargetStep', description: string, id: string, solutionTargetId: string }> }> } };
|
||||
|
||||
export type EmployeeAllFragment = { __typename?: 'Employee', name: string, id: string, surname: string, personnelNumber: string, qualification?: string | null, occuptationalHealthExamination?: any | null, personTransportCertificate?: any | null };
|
||||
|
||||
export type PlannedTimeAllFragment = { __typename?: 'PlannedTime', id: string, userId: string, firstname: string, surname: string, startDate: any, endDate: any, department: string, pause: number, duration: number, note?: string | null };
|
||||
|
||||
export type WorkTimeAllFragment = { __typename?: 'WorkTime', id: string, userId: string, firstname: string, surname: string, startDate: any, endDate: any, department: string, pause: number, duration: number };
|
||||
|
||||
export type AttendanceRegistrationAllFragment = { __typename?: 'AttendanceRegistration', personnelNumber: string, attendanceRegistrationId: number, employeeId: number, dateTime: any, event: string };
|
||||
|
||||
export type TourAllFragment = { __typename?: 'Tour', billDate?: any | null, billNumber?: string | null, carName?: string | null, category?: string | null, check: number, codriverId?: string | null, codriverName?: string | null, consumptionCosts: number, createdAt?: any | null, deletedAt?: any | null, direction: string, done: boolean, driverId?: string | null, driverName?: string | null, empty?: string | null, hasInfection?: boolean | null, healthInsurance?: string | null, healthInsuranceNumber?: string | null, id: string, infectionName?: string | null, occupiedKm: number, operationId: string, ordinanceType?: string | null, patientCity?: string | null, patientId?: string | null, patientName?: string | null, patientStreet?: string | null, patientSurname?: string | null, patientZip?: string | null, rangeEndDate?: any | null, revenue: number, revenueDeviation: number, revenueDispoLive: number, startBegin?: string | null, startCity?: string | null, startDate?: any | null, startEnd?: string | null, startInstitution?: string | null, startStreet?: string | null, startZip?: string | null, target?: string | null, targetBegin?: string | null, targetCity?: string | null, targetInstitution?: string | null, targetStreet?: string | null, targetZip?: string | null, ticketId?: string | null, totalKm: number, transportType?: string | null, type: string, ticket?: { __typename?: 'Ticket', currentState: TicketValidationState, approvalState?: ApprovalState | null, createdAt: any } | null };
|
||||
|
||||
export type GetAnomaliesAssociatedEntitiesQueryVariables = Exact<{
|
||||
id: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAnomaliesAssociatedEntitiesQuery = { __typename?: 'Query', associatedEntities: { __typename?: 'AssociatedEntities', employee?: Array<{ __typename?: 'Employee', name: string, id: string, surname: string, personnelNumber: string, qualification?: string | null, occuptationalHealthExamination?: any | null, personTransportCertificate?: any | null }> | null, plannedTime?: Array<{ __typename?: 'PlannedTime', id: string, userId: string, firstname: string, surname: string, startDate: any, endDate: any, department: string, pause: number, duration: number, note?: string | null }> | null, workTime?: Array<{ __typename?: 'WorkTime', id: string, userId: string, firstname: string, surname: string, startDate: any, endDate: any, department: string, pause: number, duration: number }> | null, attendanceRegistration?: Array<{ __typename?: 'AttendanceRegistration', personnelNumber: string, attendanceRegistrationId: number, employeeId: number, dateTime: any, event: string }> | null, tour?: Array<{ __typename?: 'Tour', billDate?: any | null, billNumber?: string | null, carName?: string | null, category?: string | null, check: number, codriverId?: string | null, codriverName?: string | null, consumptionCosts: number, createdAt?: any | null, deletedAt?: any | null, direction: string, done: boolean, driverId?: string | null, driverName?: string | null, empty?: string | null, hasInfection?: boolean | null, healthInsurance?: string | null, healthInsuranceNumber?: string | null, id: string, infectionName?: string | null, occupiedKm: number, operationId: string, ordinanceType?: string | null, patientCity?: string | null, patientId?: string | null, patientName?: string | null, patientStreet?: string | null, patientSurname?: string | null, patientZip?: string | null, rangeEndDate?: any | null, revenue: number, revenueDeviation: number, revenueDispoLive: number, startBegin?: string | null, startCity?: string | null, startDate?: any | null, startEnd?: string | null, startInstitution?: string | null, startStreet?: string | null, startZip?: string | null, target?: string | null, targetBegin?: string | null, targetCity?: string | null, targetInstitution?: string | null, targetStreet?: string | null, targetZip?: string | null, ticketId?: string | null, totalKm: number, transportType?: string | null, type: string, ticket?: { __typename?: 'Ticket', currentState: TicketValidationState, approvalState?: ApprovalState | null, createdAt: any } | null }> | null } };
|
||||
|
||||
export type HomeDashboardQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
|
|
@ -669,6 +754,130 @@ export const AnomalyAllFragmentDoc = gql`
|
|||
createdAt
|
||||
}
|
||||
`;
|
||||
export const EmployeeAllFragmentDoc = gql`
|
||||
fragment EmployeeAll on Employee {
|
||||
name
|
||||
id
|
||||
surname
|
||||
personnelNumber
|
||||
qualification
|
||||
occuptationalHealthExamination
|
||||
personTransportCertificate
|
||||
}
|
||||
`;
|
||||
export const PlannedTimeAllFragmentDoc = gql`
|
||||
fragment PlannedTimeAll on PlannedTime {
|
||||
id
|
||||
userId
|
||||
firstname
|
||||
surname
|
||||
startDate
|
||||
endDate
|
||||
department
|
||||
pause
|
||||
duration
|
||||
note
|
||||
}
|
||||
`;
|
||||
export const WorkTimeAllFragmentDoc = gql`
|
||||
fragment WorkTimeAll on WorkTime {
|
||||
id
|
||||
userId
|
||||
firstname
|
||||
surname
|
||||
startDate
|
||||
endDate
|
||||
department
|
||||
pause
|
||||
duration
|
||||
}
|
||||
`;
|
||||
export const AttendanceRegistrationAllFragmentDoc = gql`
|
||||
fragment AttendanceRegistrationAll on AttendanceRegistration {
|
||||
personnelNumber
|
||||
attendanceRegistrationId
|
||||
employeeId
|
||||
dateTime
|
||||
event
|
||||
}
|
||||
`;
|
||||
export const TourAllFragmentDoc = gql`
|
||||
fragment TourAll on Tour {
|
||||
billDate
|
||||
billNumber
|
||||
carName
|
||||
category
|
||||
check
|
||||
codriverId
|
||||
codriverName
|
||||
consumptionCosts
|
||||
createdAt
|
||||
deletedAt
|
||||
direction
|
||||
done
|
||||
driverId
|
||||
driverName
|
||||
empty
|
||||
hasInfection
|
||||
healthInsurance
|
||||
healthInsuranceNumber
|
||||
id
|
||||
infectionName
|
||||
occupiedKm
|
||||
operationId
|
||||
ordinanceType
|
||||
patientCity
|
||||
patientId
|
||||
patientName
|
||||
patientStreet
|
||||
patientSurname
|
||||
patientZip
|
||||
rangeEndDate
|
||||
revenue
|
||||
revenueDeviation
|
||||
revenueDispoLive
|
||||
startBegin
|
||||
startCity
|
||||
startDate
|
||||
startEnd
|
||||
startInstitution
|
||||
startStreet
|
||||
startZip
|
||||
target
|
||||
targetBegin
|
||||
targetCity
|
||||
targetInstitution
|
||||
targetStreet
|
||||
targetZip
|
||||
ticketId
|
||||
ticket {
|
||||
currentState
|
||||
approvalState
|
||||
createdAt
|
||||
}
|
||||
totalKm
|
||||
transportType
|
||||
type
|
||||
}
|
||||
`;
|
||||
export const GetAnomalyDocument = gql`
|
||||
query GetAnomaly($id: String!) {
|
||||
anomaly(id: $id) {
|
||||
...AnomalyAll
|
||||
}
|
||||
}
|
||||
${AnomalyAllFragmentDoc}`;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GetAnomalyGQL extends Apollo.Query<GetAnomalyQuery, GetAnomalyQueryVariables> {
|
||||
override document = GetAnomalyDocument;
|
||||
|
||||
constructor(apollo: Apollo.Apollo) {
|
||||
super(apollo);
|
||||
}
|
||||
}
|
||||
export const GetAnomaliesDocument = gql`
|
||||
query GetAnomalies($cursor: String, $take: Int!) {
|
||||
anomalies(cursor: $cursor, take: $take) {
|
||||
|
|
@ -752,6 +961,42 @@ export const AcceptAnomalyDocument = gql`
|
|||
export class AcceptAnomalyGQL extends Apollo.Mutation<AcceptAnomalyMutation, AcceptAnomalyMutationVariables> {
|
||||
override document = AcceptAnomalyDocument;
|
||||
|
||||
constructor(apollo: Apollo.Apollo) {
|
||||
super(apollo);
|
||||
}
|
||||
}
|
||||
export const GetAnomaliesAssociatedEntitiesDocument = gql`
|
||||
query GetAnomaliesAssociatedEntities($id: String!) {
|
||||
associatedEntities(id: $id) {
|
||||
employee {
|
||||
...EmployeeAll
|
||||
}
|
||||
plannedTime {
|
||||
...PlannedTimeAll
|
||||
}
|
||||
workTime {
|
||||
...WorkTimeAll
|
||||
}
|
||||
attendanceRegistration {
|
||||
...AttendanceRegistrationAll
|
||||
}
|
||||
tour {
|
||||
...TourAll
|
||||
}
|
||||
}
|
||||
}
|
||||
${EmployeeAllFragmentDoc}
|
||||
${PlannedTimeAllFragmentDoc}
|
||||
${WorkTimeAllFragmentDoc}
|
||||
${AttendanceRegistrationAllFragmentDoc}
|
||||
${TourAllFragmentDoc}`;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GetAnomaliesAssociatedEntitiesGQL extends Apollo.Query<GetAnomaliesAssociatedEntitiesQuery, GetAnomaliesAssociatedEntitiesQueryVariables> {
|
||||
override document = GetAnomaliesAssociatedEntitiesDocument;
|
||||
|
||||
constructor(apollo: Apollo.Apollo) {
|
||||
super(apollo);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ fragment AnomalyAll on Anomaly {
|
|||
createdAt
|
||||
}
|
||||
|
||||
query GetAnomaly($id: String!) {
|
||||
anomaly(id: $id) {
|
||||
...AnomalyAll
|
||||
}
|
||||
}
|
||||
|
||||
query GetAnomalies($cursor: String, $take: Int!) {
|
||||
anomalies(cursor: $cursor, take: $take) {
|
||||
prevCursor
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
fragment EmployeeAll on Employee {
|
||||
name
|
||||
id
|
||||
surname
|
||||
personnelNumber
|
||||
qualification
|
||||
occuptationalHealthExamination
|
||||
personTransportCertificate
|
||||
}
|
||||
|
||||
fragment PlannedTimeAll on PlannedTime {
|
||||
id
|
||||
userId
|
||||
firstname
|
||||
surname
|
||||
startDate
|
||||
endDate
|
||||
department
|
||||
pause
|
||||
duration
|
||||
note
|
||||
}
|
||||
|
||||
fragment WorkTimeAll on WorkTime {
|
||||
id
|
||||
userId
|
||||
firstname
|
||||
surname
|
||||
startDate
|
||||
endDate
|
||||
department
|
||||
pause
|
||||
duration
|
||||
}
|
||||
|
||||
fragment AttendanceRegistrationAll on AttendanceRegistration {
|
||||
personnelNumber
|
||||
attendanceRegistrationId
|
||||
employeeId
|
||||
dateTime
|
||||
event
|
||||
}
|
||||
|
||||
fragment TourAll on Tour {
|
||||
billDate
|
||||
billNumber
|
||||
carName
|
||||
category
|
||||
check
|
||||
codriverId
|
||||
codriverName
|
||||
consumptionCosts
|
||||
createdAt
|
||||
deletedAt
|
||||
direction
|
||||
done
|
||||
driverId
|
||||
driverName
|
||||
empty
|
||||
hasInfection
|
||||
healthInsurance
|
||||
healthInsuranceNumber
|
||||
id
|
||||
infectionName
|
||||
occupiedKm
|
||||
operationId
|
||||
ordinanceType
|
||||
patientCity
|
||||
patientId
|
||||
patientName
|
||||
patientStreet
|
||||
patientSurname
|
||||
patientZip
|
||||
rangeEndDate
|
||||
revenue
|
||||
revenueDeviation
|
||||
revenueDispoLive
|
||||
startBegin
|
||||
startCity
|
||||
startDate
|
||||
startEnd
|
||||
startInstitution
|
||||
startStreet
|
||||
startZip
|
||||
target
|
||||
targetBegin
|
||||
targetCity
|
||||
targetInstitution
|
||||
targetStreet
|
||||
targetZip
|
||||
ticketId
|
||||
ticket {
|
||||
currentState
|
||||
approvalState
|
||||
createdAt
|
||||
}
|
||||
totalKm
|
||||
transportType
|
||||
type
|
||||
}
|
||||
|
||||
query GetAnomaliesAssociatedEntities($id: String!) {
|
||||
associatedEntities(id: $id) {
|
||||
employee {
|
||||
...EmployeeAll
|
||||
}
|
||||
plannedTime {
|
||||
...PlannedTimeAll
|
||||
}
|
||||
workTime {
|
||||
...WorkTimeAll
|
||||
}
|
||||
attendanceRegistration {
|
||||
...AttendanceRegistrationAll
|
||||
}
|
||||
tour {
|
||||
...TourAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,14 +32,14 @@ interface Anomaly {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dks-anomaly-list-item',
|
||||
templateUrl: './anomaly-list-item.component.html',
|
||||
styleUrls: ['./anomaly-list-item.component.css'],
|
||||
host: {
|
||||
class: 'w-full rounded flex items-center',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
selector: 'dks-anomaly-list-item',
|
||||
templateUrl: './anomaly-list-item.component.html',
|
||||
styleUrls: ['./anomaly-list-item.component.css'],
|
||||
host: {
|
||||
class: 'w-full rounded flex items-center',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class AnomalyListItemComponent {
|
||||
isHovering = false;
|
||||
|
|
@ -76,7 +76,7 @@ export class AnomalyListItemComponent {
|
|||
title: 'Ist dies kein Fehler?',
|
||||
body: 'Sind sie sicher, dass diese Anomalie kein Fehler ist? Dann bestätigen Sie diesen Dialog',
|
||||
},
|
||||
width: 500
|
||||
width: 500,
|
||||
})
|
||||
.afterClosed$.subscribe(
|
||||
(confirmed) => confirmed && this.anomalyAccepted.emit(id)
|
||||
|
|
@ -88,6 +88,7 @@ export class AnomalyListItemComponent {
|
|||
data: {
|
||||
title: this.anomaly.description,
|
||||
solution: this.anomaly.solution,
|
||||
anomalyId: this.anomaly.id,
|
||||
},
|
||||
});
|
||||
dialogRef.afterClosed$.subscribe((result) => {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
<h2>{{ ref.data.title }}</h2>
|
||||
<mat-divider></mat-divider>
|
||||
<article class="my-4 mx-2">
|
||||
<div class="pb-4" *dksRole="['admin']">
|
||||
<a [routerLink]="['/control-center', ref.data.anomalyId]">View Anomaly</a>
|
||||
</div>
|
||||
@for (target of ref.data.solution; track target) {
|
||||
<div class="pb-4">
|
||||
<span class="font-bold text-base">{{ target.name }}</span>
|
||||
<ul class="ml-4">
|
||||
@for (step of target.steps; track step) {
|
||||
<li class="list-disc">
|
||||
{{ step.description }}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pb-4">
|
||||
<span class="font-bold text-base">{{ target.name }}</span>
|
||||
<ul class="ml-4">
|
||||
@for (step of target.steps; track step) {
|
||||
<li class="list-disc">
|
||||
{{ step.description }}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
<mat-divider></mat-divider>
|
||||
|
|
|
|||
|
|
@ -9,16 +9,19 @@ interface SolutionTarget {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dks-anomaly-solution-dialog',
|
||||
templateUrl: './anomaly-solution-dialog.component.html',
|
||||
styleUrls: ['./anomaly-solution-dialog.component.scss'],
|
||||
host: {
|
||||
class: 'm-4',
|
||||
},
|
||||
standalone: false
|
||||
selector: 'dks-anomaly-solution-dialog',
|
||||
templateUrl: './anomaly-solution-dialog.component.html',
|
||||
styleUrls: ['./anomaly-solution-dialog.component.scss'],
|
||||
host: {
|
||||
class: 'm-4',
|
||||
},
|
||||
standalone: false,
|
||||
})
|
||||
export class AnomalySolutionDialogComponent {
|
||||
constructor(
|
||||
public ref: DialogRef<{ title: string; solution: SolutionTarget[] }, 'sleep' | 'accept'>
|
||||
public ref: DialogRef<
|
||||
{ title: string; solution: SolutionTarget[]; anomalyId: string },
|
||||
'sleep' | 'accept'
|
||||
>
|
||||
) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<div class="w-full">
|
||||
@if(anomaly()) {
|
||||
<div>{{anomaly()?.description}}</div>
|
||||
@for (target of anomaly()?.solution; track $index) {
|
||||
<div class="pb-4">
|
||||
<span class="font-bold text-base">{{ target.name }}</span>
|
||||
<ul class="ml-4">
|
||||
@for (step of target.steps; track step) {
|
||||
<li class="list-disc">
|
||||
{{ step.description }}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="flex m-4 justify-between overflow-y-hidden overflow-scroll [&>div]:flex-shrink-0">
|
||||
@if(tours().length > 0) {
|
||||
@for (tour of tours(); track $index) {
|
||||
<div class="flex flex-col mx-2 my-4 p-4 rounded-2xl" style="box-shadow: var(--mat-sys-level2);">
|
||||
@for (item of tour | keyvalue; track $index) {
|
||||
<div>
|
||||
<span>{{item.key}}</span>: <span>{{item.value}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if(plannedTimes().length > 0) {
|
||||
@for (plannedTime of plannedTimes(); track $index) {
|
||||
<div class="flex flex-col mx-2 my-4 p-4 rounded-2xl" style="box-shadow: var(--mat-sys-level2);">
|
||||
@for (item of plannedTime | keyvalue; track $index) {
|
||||
<div>
|
||||
<span>{{item.key}}</span>: <span>{{item.value}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if(workTimes().length > 0) {
|
||||
@for (workTime of workTimes(); track $index) {
|
||||
<div class="flex flex-col mx-2 my-4 p-4 rounded-2xl" style="box-shadow: var(--mat-sys-level2);">
|
||||
@for (item of workTime | keyvalue; track $index) {
|
||||
<div>
|
||||
<span>{{item.key}}</span>: <span>{{item.value}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if(attendanceRegistrations().length > 0) {
|
||||
<div class="flex flex-col">
|
||||
@for (attendanceRegistration of attendanceRegistrations(); track $index) {
|
||||
<div class="flex flex-col mx-2 my-4 p-4 rounded-2xl" style="box-shadow: var(--mat-sys-level2);">
|
||||
@for (item of attendanceRegistration | keyvalue; track $index) {
|
||||
<div>
|
||||
<span>{{item.key}}</span>: <span>{{item.value}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if(employees().length > 0) {
|
||||
@for (employee of employees(); track $index) {
|
||||
<div class="flex flex-col mx-2 my-4 p-4 rounded-2xl" style="box-shadow: var(--mat-sys-level2);">
|
||||
@for (item of employee | keyvalue; track $index) {
|
||||
<div>
|
||||
<span>{{item.key}}</span>: <span>{{item.value}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AnomalyViewComponent } from './anomaly-view.component';
|
||||
|
||||
describe('AnomalyViewComponent', () => {
|
||||
let component: AnomalyViewComponent;
|
||||
let fixture: ComponentFixture<AnomalyViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AnomalyViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AnomalyViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
Anomaly,
|
||||
AttendanceRegistration,
|
||||
Employee,
|
||||
GetAnomaliesAssociatedEntitiesGQL,
|
||||
GetAnomalyGQL,
|
||||
PlannedTime,
|
||||
Tour,
|
||||
WorkTime,
|
||||
} from '../../../core/data-access/graphql/generated/generated';
|
||||
import { map, tap } from 'rxjs';
|
||||
import { KeyValuePipe } from '@angular/common';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-anomaly-view',
|
||||
imports: [KeyValuePipe],
|
||||
templateUrl: './anomaly-view.component.html',
|
||||
styles: ``,
|
||||
})
|
||||
export class AnomalyViewComponent {
|
||||
anomalyId = input.required<string>();
|
||||
|
||||
anomalyByIdGql = inject(GetAnomalyGQL);
|
||||
associatedEntitiesGql = inject(GetAnomaliesAssociatedEntitiesGQL);
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
anomaly = signal<Anomaly | undefined>(undefined);
|
||||
tours = signal<Partial<Tour>[]>([]);
|
||||
plannedTimes = signal<PlannedTime[]>([]);
|
||||
workTimes = signal<WorkTime[]>([]);
|
||||
attendanceRegistrations = signal<AttendanceRegistration[]>([]);
|
||||
employees = signal<Employee[]>([]);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.anomalyByIdGql
|
||||
.fetch({ id: this.anomalyId() })
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
tap((res) => this.anomaly.set(res.data.anomaly))
|
||||
)
|
||||
.subscribe();
|
||||
this.associatedEntitiesGql
|
||||
.fetch({ id: this.anomalyId() })
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((res) => res.data.associatedEntities),
|
||||
tap(
|
||||
({
|
||||
tour,
|
||||
attendanceRegistration,
|
||||
plannedTime,
|
||||
workTime,
|
||||
employee,
|
||||
}) => {
|
||||
if (tour) this.tours.set(tour as Tour[]);
|
||||
if (attendanceRegistration)
|
||||
this.attendanceRegistrations.set(attendanceRegistration);
|
||||
if (plannedTime) this.plannedTimes.set(plannedTime);
|
||||
if (workTime) this.workTimes.set(workTime);
|
||||
if (employee) this.employees.set(employee);
|
||||
}
|
||||
)
|
||||
)
|
||||
.subscribe();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
console.log(this.anomalyId());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1 @@
|
|||
<div class="flex flex-col m-4">
|
||||
<div class="flex flex-wrap lg:flex-nowrap">
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'BTW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'TSW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'KTW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
</div>
|
||||
<div class="flex lg:flex-nowrap flex-wrap-reverse">
|
||||
<mat-card appearance="outlined" class="w-full md:flex-grow lg:w-2/5 m-2 !flex flex-col">
|
||||
<mat-card-header>
|
||||
<mat-card-title> Anomalien </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow">
|
||||
<dks-anomaly-tree></dks-anomaly-tree>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="w-full md:flex-grow lg:w-2/5 m-2 !flex flex-col">
|
||||
<mat-card-header>
|
||||
<mat-card-title> Mitarbeiter ohne Tour </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow">
|
||||
<dks-anomaly-list></dks-anomaly-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="w-full lg:w-1/5 m-2">
|
||||
<mat-card-header>
|
||||
<mat-card-title> Nächste Touren ohne Anfahrt </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<dks-upcoming-tours></dks-upcoming-tours>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ControlCenterDashboardComponent } from './control-center-dashboard.component';
|
||||
|
||||
describe('ControlCenterDashboardComponent', () => {
|
||||
let component: ControlCenterDashboardComponent;
|
||||
let fixture: ComponentFixture<ControlCenterDashboardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ControlCenterDashboardComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ControlCenterDashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||
@Component({
|
||||
selector: 'dks-control-center-dashboard',
|
||||
templateUrl: './control-center-dashboard.component.html',
|
||||
styleUrls: ['./control-center-dashboard.component.scss'],
|
||||
styles: [],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,16 +10,19 @@ import { MatMenuModule } from '@angular/material/menu';
|
|||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/confirm-dialog.component';
|
||||
import { AnomaliesService } from './anomalies.service';
|
||||
import { AnomalyListItemComponent } from './anomaly-list/anomaly-list-item/anomaly-list-item.component';
|
||||
import { AnomalyListComponent } from './anomaly-list/anomaly-list.component';
|
||||
import { AnomalySolutionDialogComponent } from './anomaly-list/anomaly-solution-dialog/anomaly-solution-dialog.component';
|
||||
import { AnomalyTreeComponent } from './anomaly-tree/anomaly-tree.component';
|
||||
import { AnomalyViewComponent } from './anomaly-view/anomaly-view.component';
|
||||
import { ControlCenterDashboardComponent } from './control-center-dashboard.component';
|
||||
import { ToursPerTimeKPIComponent } from './kpis/tours-per-time-kpi/tours-per-time-kpi.component';
|
||||
import { TourViewDialogComponent } from './upcoming-tours/tour-view-dialog/tour-view-dialog.component';
|
||||
import { UpcomingToursComponent } from './upcoming-tours/upcoming-tours.component';
|
||||
import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/confirm-dialog.component';
|
||||
import { OverviewDashboardComponent } from './overview-dashboard/overview-dashboard.component';
|
||||
import { RoleDirective } from '../../core/components/ms-authentication/role.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -28,6 +31,21 @@ import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/con
|
|||
{
|
||||
path: '',
|
||||
component: ControlCenterDashboardComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'overview',
|
||||
component: OverviewDashboardComponent,
|
||||
},
|
||||
{
|
||||
path: ':anomalyId',
|
||||
component: AnomalyViewComponent,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'overview'
|
||||
}
|
||||
],
|
||||
},
|
||||
]),
|
||||
MatGridListModule,
|
||||
|
|
@ -40,9 +58,11 @@ import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/con
|
|||
MatTreeModule,
|
||||
MatPaginatorModule,
|
||||
ConfirmDialogComponent,
|
||||
RoleDirective,
|
||||
],
|
||||
declarations: [
|
||||
ControlCenterDashboardComponent,
|
||||
OverviewDashboardComponent,
|
||||
AnomalyListComponent,
|
||||
AnomalyListItemComponent,
|
||||
AnomalySolutionDialogComponent,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OverviewDashboardComponent } from './overview-dashboard.component';
|
||||
|
||||
describe('OverviewDashboardComponent', () => {
|
||||
let component: OverviewDashboardComponent;
|
||||
let fixture: ComponentFixture<OverviewDashboardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [OverviewDashboardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OverviewDashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-overview-dashboard',
|
||||
standalone: false,
|
||||
template: `
|
||||
<div class="flex flex-col m-4">
|
||||
<div class="flex flex-wrap lg:flex-nowrap">
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'BTW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'TSW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
<dks-tours-per-time-kpi
|
||||
class="w-full md:w-1/2 p-2"
|
||||
[ordinanceType]="'KTW'"
|
||||
></dks-tours-per-time-kpi>
|
||||
</div>
|
||||
<div class="flex lg:flex-nowrap flex-wrap-reverse">
|
||||
<mat-card
|
||||
appearance="outlined"
|
||||
class="w-full md:flex-grow lg:w-2/5 m-2 !flex flex-col"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title> Anomalien </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow">
|
||||
<dks-anomaly-tree></dks-anomaly-tree>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
appearance="outlined"
|
||||
class="w-full md:flex-grow lg:w-2/5 m-2 !flex flex-col"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title> Mitarbeiter ohne Tour </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow">
|
||||
<dks-anomaly-list></dks-anomaly-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="w-full lg:w-1/5 m-2">
|
||||
<mat-card-header>
|
||||
<mat-card-title> Nächste Touren ohne Anfahrt </mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<dks-upcoming-tours></dks-upcoming-tours>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
styles: ``,
|
||||
})
|
||||
export class OverviewDashboardComponent {}
|
||||
|
|
@ -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!
|
||||
|
|
@ -338,3 +378,15 @@ type TourStateMeta {
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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, {}];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[]> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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[] = [],
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue