feat: add associated entities to anomalies
This commit is contained in:
parent
b31df96a44
commit
d9b615fd47
|
|
@ -3,19 +3,17 @@ import {
|
||||||
ErrorHandler,
|
ErrorHandler,
|
||||||
importProvidersFrom,
|
importProvidersFrom,
|
||||||
provideZoneChangeDetection,
|
provideZoneChangeDetection,
|
||||||
provideAppInitializer,
|
|
||||||
} from '@angular/core';
|
} 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 { 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 { 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 { GlobalErrorHandler } from './core/telemetry/global-error-handler';
|
||||||
|
import { graphqlProvider } from './graphql.provider';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
|
@ -28,7 +26,7 @@ export const appConfig: ApplicationConfig = {
|
||||||
useClass: GlobalErrorHandler,
|
useClass: GlobalErrorHandler,
|
||||||
},
|
},
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes),
|
provideRouter(routes, withComponentInputBinding()),
|
||||||
provideAnimationsAsync(),
|
provideAnimationsAsync(),
|
||||||
importProvidersFrom(
|
importProvidersFrom(
|
||||||
MsAuthenticationModule.forRoot({
|
MsAuthenticationModule.forRoot({
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ export type Anomaly = {
|
||||||
sleepSince?: Maybe<Scalars['DateTime']['output']>;
|
sleepSince?: Maybe<Scalars['DateTime']['output']>;
|
||||||
sleepTimer: Scalars['Int']['output'];
|
sleepTimer: Scalars['Int']['output'];
|
||||||
solution: Array<SolutionTarget>;
|
solution: Array<SolutionTarget>;
|
||||||
tour?: Maybe<Tour>;
|
|
||||||
type: Scalars['String']['output'];
|
type: Scalars['String']['output'];
|
||||||
workedOnBy: Scalars['String']['output'];
|
workedOnBy: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
@ -76,12 +75,41 @@ export enum ApprovalState {
|
||||||
Unknown = 'UNKNOWN'
|
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 = {
|
export type DocumentInfo = {
|
||||||
__typename?: 'DocumentInfo';
|
__typename?: 'DocumentInfo';
|
||||||
approval: ApprovalInfo;
|
approval: ApprovalInfo;
|
||||||
tdLocation?: Maybe<Scalars['String']['output']>;
|
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 = {
|
export type HomeDashboardKpIs = {
|
||||||
__typename?: 'HomeDashboardKPIs';
|
__typename?: 'HomeDashboardKPIs';
|
||||||
anomalyCount: Scalars['Int']['output'];
|
anomalyCount: Scalars['Int']['output'];
|
||||||
|
|
@ -194,6 +222,20 @@ export type PaginatedTour = {
|
||||||
totalCount: Scalars['Int']['output'];
|
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 = {
|
export type Query = {
|
||||||
__typename?: 'Query';
|
__typename?: 'Query';
|
||||||
accountingStateMeta: TourStateMeta;
|
accountingStateMeta: TourStateMeta;
|
||||||
|
|
@ -201,6 +243,7 @@ export type Query = {
|
||||||
anomalies: PaginatedAnomaly;
|
anomalies: PaginatedAnomaly;
|
||||||
anomaliesProto: Array<Anomaly>;
|
anomaliesProto: Array<Anomaly>;
|
||||||
anomaly: Anomaly;
|
anomaly: Anomaly;
|
||||||
|
associatedEntities: AssociatedEntities;
|
||||||
groupedAnomalies: PaginatedAnomalyGroup;
|
groupedAnomalies: PaginatedAnomalyGroup;
|
||||||
homeDashboardKpis: HomeDashboardKpIs;
|
homeDashboardKpis: HomeDashboardKpIs;
|
||||||
managerKpi: KpiInfo;
|
managerKpi: KpiInfo;
|
||||||
|
|
@ -242,6 +285,11 @@ export type QueryAnomalyArgs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryAssociatedEntitiesArgs = {
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryGroupedAnomaliesArgs = {
|
export type QueryGroupedAnomaliesArgs = {
|
||||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||||
take?: Scalars['Int']['input'];
|
take?: Scalars['Int']['input'];
|
||||||
|
|
@ -492,8 +540,28 @@ export type TourStateMeta = {
|
||||||
revenueSum?: Maybe<Scalars['Float']['output']>;
|
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 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<{
|
export type GetAnomaliesQueryVariables = Exact<{
|
||||||
cursor?: InputMaybe<Scalars['String']['input']>;
|
cursor?: InputMaybe<Scalars['String']['input']>;
|
||||||
take: Scalars['Int']['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 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; }>;
|
export type HomeDashboardQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -669,6 +754,130 @@ export const AnomalyAllFragmentDoc = gql`
|
||||||
createdAt
|
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`
|
export const GetAnomaliesDocument = gql`
|
||||||
query GetAnomalies($cursor: String, $take: Int!) {
|
query GetAnomalies($cursor: String, $take: Int!) {
|
||||||
anomalies(cursor: $cursor, take: $take) {
|
anomalies(cursor: $cursor, take: $take) {
|
||||||
|
|
@ -752,6 +961,42 @@ export const AcceptAnomalyDocument = gql`
|
||||||
export class AcceptAnomalyGQL extends Apollo.Mutation<AcceptAnomalyMutation, AcceptAnomalyMutationVariables> {
|
export class AcceptAnomalyGQL extends Apollo.Mutation<AcceptAnomalyMutation, AcceptAnomalyMutationVariables> {
|
||||||
override document = AcceptAnomalyDocument;
|
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) {
|
constructor(apollo: Apollo.Apollo) {
|
||||||
super(apollo);
|
super(apollo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ fragment AnomalyAll on Anomaly {
|
||||||
createdAt
|
createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query GetAnomaly($id: String!) {
|
||||||
|
anomaly(id: $id) {
|
||||||
|
...AnomalyAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query GetAnomalies($cursor: String, $take: Int!) {
|
query GetAnomalies($cursor: String, $take: Int!) {
|
||||||
anomalies(cursor: $cursor, take: $take) {
|
anomalies(cursor: $cursor, take: $take) {
|
||||||
prevCursor
|
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({
|
@Component({
|
||||||
selector: 'dks-anomaly-list-item',
|
selector: 'dks-anomaly-list-item',
|
||||||
templateUrl: './anomaly-list-item.component.html',
|
templateUrl: './anomaly-list-item.component.html',
|
||||||
styleUrls: ['./anomaly-list-item.component.css'],
|
styleUrls: ['./anomaly-list-item.component.css'],
|
||||||
host: {
|
host: {
|
||||||
class: 'w-full rounded flex items-center',
|
class: 'w-full rounded flex items-center',
|
||||||
},
|
},
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class AnomalyListItemComponent {
|
export class AnomalyListItemComponent {
|
||||||
isHovering = false;
|
isHovering = false;
|
||||||
|
|
@ -76,7 +76,7 @@ export class AnomalyListItemComponent {
|
||||||
title: 'Ist dies kein Fehler?',
|
title: 'Ist dies kein Fehler?',
|
||||||
body: 'Sind sie sicher, dass diese Anomalie kein Fehler ist? Dann bestätigen Sie diesen Dialog',
|
body: 'Sind sie sicher, dass diese Anomalie kein Fehler ist? Dann bestätigen Sie diesen Dialog',
|
||||||
},
|
},
|
||||||
width: 500
|
width: 500,
|
||||||
})
|
})
|
||||||
.afterClosed$.subscribe(
|
.afterClosed$.subscribe(
|
||||||
(confirmed) => confirmed && this.anomalyAccepted.emit(id)
|
(confirmed) => confirmed && this.anomalyAccepted.emit(id)
|
||||||
|
|
@ -88,6 +88,7 @@ export class AnomalyListItemComponent {
|
||||||
data: {
|
data: {
|
||||||
title: this.anomaly.description,
|
title: this.anomaly.description,
|
||||||
solution: this.anomaly.solution,
|
solution: this.anomaly.solution,
|
||||||
|
anomalyId: this.anomaly.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dialogRef.afterClosed$.subscribe((result) => {
|
dialogRef.afterClosed$.subscribe((result) => {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
<h2>{{ ref.data.title }}</h2>
|
<h2>{{ ref.data.title }}</h2>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<article class="my-4 mx-2">
|
<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) {
|
@for (target of ref.data.solution; track target) {
|
||||||
<div class="pb-4">
|
<div class="pb-4">
|
||||||
<span class="font-bold text-base">{{ target.name }}</span>
|
<span class="font-bold text-base">{{ target.name }}</span>
|
||||||
<ul class="ml-4">
|
<ul class="ml-4">
|
||||||
@for (step of target.steps; track step) {
|
@for (step of target.steps; track step) {
|
||||||
<li class="list-disc">
|
<li class="list-disc">
|
||||||
{{ step.description }}
|
{{ step.description }}
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</article>
|
</article>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,19 @@ interface SolutionTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dks-anomaly-solution-dialog',
|
selector: 'dks-anomaly-solution-dialog',
|
||||||
templateUrl: './anomaly-solution-dialog.component.html',
|
templateUrl: './anomaly-solution-dialog.component.html',
|
||||||
styleUrls: ['./anomaly-solution-dialog.component.scss'],
|
styleUrls: ['./anomaly-solution-dialog.component.scss'],
|
||||||
host: {
|
host: {
|
||||||
class: 'm-4',
|
class: 'm-4',
|
||||||
},
|
},
|
||||||
standalone: false
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class AnomalySolutionDialogComponent {
|
export class AnomalySolutionDialogComponent {
|
||||||
constructor(
|
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">
|
<router-outlet></router-outlet>
|
||||||
<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>
|
|
||||||
|
|
|
||||||
|
|
@ -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({
|
@Component({
|
||||||
selector: 'dks-control-center-dashboard',
|
selector: 'dks-control-center-dashboard',
|
||||||
templateUrl: './control-center-dashboard.component.html',
|
templateUrl: './control-center-dashboard.component.html',
|
||||||
styleUrls: ['./control-center-dashboard.component.scss'],
|
styles: [],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false
|
standalone: false
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,19 @@ import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import { MatTreeModule } from '@angular/material/tree';
|
import { MatTreeModule } from '@angular/material/tree';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/confirm-dialog.component';
|
||||||
import { AnomaliesService } from './anomalies.service';
|
import { AnomaliesService } from './anomalies.service';
|
||||||
import { AnomalyListItemComponent } from './anomaly-list/anomaly-list-item/anomaly-list-item.component';
|
import { AnomalyListItemComponent } from './anomaly-list/anomaly-list-item/anomaly-list-item.component';
|
||||||
import { AnomalyListComponent } from './anomaly-list/anomaly-list.component';
|
import { AnomalyListComponent } from './anomaly-list/anomaly-list.component';
|
||||||
import { AnomalySolutionDialogComponent } from './anomaly-list/anomaly-solution-dialog/anomaly-solution-dialog.component';
|
import { AnomalySolutionDialogComponent } from './anomaly-list/anomaly-solution-dialog/anomaly-solution-dialog.component';
|
||||||
import { AnomalyTreeComponent } from './anomaly-tree/anomaly-tree.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 { ControlCenterDashboardComponent } from './control-center-dashboard.component';
|
||||||
import { ToursPerTimeKPIComponent } from './kpis/tours-per-time-kpi/tours-per-time-kpi.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 { TourViewDialogComponent } from './upcoming-tours/tour-view-dialog/tour-view-dialog.component';
|
||||||
import { UpcomingToursComponent } from './upcoming-tours/upcoming-tours.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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -28,6 +31,21 @@ import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/con
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ControlCenterDashboardComponent,
|
component: ControlCenterDashboardComponent,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'overview',
|
||||||
|
component: OverviewDashboardComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':anomalyId',
|
||||||
|
component: AnomalyViewComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
redirectTo: 'overview'
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
MatGridListModule,
|
MatGridListModule,
|
||||||
|
|
@ -40,9 +58,11 @@ import { ConfirmDialogComponent } from '../../core/components/confirm-dialog/con
|
||||||
MatTreeModule,
|
MatTreeModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
ConfirmDialogComponent,
|
ConfirmDialogComponent,
|
||||||
|
RoleDirective,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ControlCenterDashboardComponent,
|
ControlCenterDashboardComponent,
|
||||||
|
OverviewDashboardComponent,
|
||||||
AnomalyListComponent,
|
AnomalyListComponent,
|
||||||
AnomalyListItemComponent,
|
AnomalyListItemComponent,
|
||||||
AnomalySolutionDialogComponent,
|
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 {
|
model DyflexisEmployee {
|
||||||
id Int @id
|
id Int @id
|
||||||
firstname String
|
firstname String
|
||||||
surname String
|
surname String
|
||||||
contractStart DateTime
|
contractStart DateTime
|
||||||
|
|
@ -580,18 +580,19 @@ model Trace {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Anomaly {
|
model Anomaly {
|
||||||
id String @id
|
id String @id
|
||||||
type String
|
type String
|
||||||
description String
|
description String
|
||||||
groupKey String
|
groupKey String
|
||||||
groupDescription String
|
groupDescription String
|
||||||
workedOnBy String
|
associatedEntities Json? @default("{}")
|
||||||
sleepTimer Int
|
workedOnBy String
|
||||||
sleepSince DateTime?
|
sleepTimer Int
|
||||||
solution SolutionTarget[] @relation()
|
sleepSince DateTime?
|
||||||
resolvedAt DateTime?
|
solution SolutionTarget[] @relation()
|
||||||
manuallyResolved Boolean @default(false)
|
resolvedAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
manuallyResolved Boolean @default(false)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model SolutionTarget {
|
model SolutionTarget {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,22 @@ enum ApprovalState {
|
||||||
UNKNOWN
|
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.
|
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
|
tdLocation: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Employee {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
occuptationalHealthExamination: DateTime
|
||||||
|
personTransportCertificate: DateTime
|
||||||
|
personnelNumber: String!
|
||||||
|
qualification: String
|
||||||
|
surname: String!
|
||||||
|
}
|
||||||
|
|
||||||
type HomeDashboardKPIs {
|
type HomeDashboardKPIs {
|
||||||
anomalyCount: Int!
|
anomalyCount: Int!
|
||||||
billableTourCount: Int!
|
billableTourCount: Int!
|
||||||
|
|
@ -148,12 +174,26 @@ type PaginatedTour {
|
||||||
totalCount: Int!
|
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 {
|
type Query {
|
||||||
accountingStateMeta(state: AccountingState!): TourStateMeta!
|
accountingStateMeta(state: AccountingState!): TourStateMeta!
|
||||||
accountingTours(cursor: String, filters: AccountingFilterArgs, state: AccountingState!, take: Int! = 10): PaginatedTour!
|
accountingTours(cursor: String, filters: AccountingFilterArgs, state: AccountingState!, take: Int! = 10): PaginatedTour!
|
||||||
anomalies(cursor: String, take: Int! = 10): PaginatedAnomaly!
|
anomalies(cursor: String, take: Int! = 10): PaginatedAnomaly!
|
||||||
anomaliesProto: [Anomaly!]!
|
anomaliesProto: [Anomaly!]!
|
||||||
anomaly(id: String!): Anomaly!
|
anomaly(id: String!): Anomaly!
|
||||||
|
associatedEntities(id: String!): AssociatedEntities!
|
||||||
groupedAnomalies(cursor: String, take: Int! = 10): PaginatedAnomalyGroup!
|
groupedAnomalies(cursor: String, take: Int! = 10): PaginatedAnomalyGroup!
|
||||||
homeDashboardKpis: HomeDashboardKPIs!
|
homeDashboardKpis: HomeDashboardKPIs!
|
||||||
managerKpi(ordinanceType: String!): KpiInfo!
|
managerKpi(ordinanceType: String!): KpiInfo!
|
||||||
|
|
@ -338,3 +378,15 @@ type TourStateMeta {
|
||||||
positiveRevenueDeviation: Float
|
positiveRevenueDeviation: Float
|
||||||
revenueSum: 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;
|
description: string;
|
||||||
groupKey: string;
|
groupKey: string;
|
||||||
groupDescription: string;
|
groupDescription: string;
|
||||||
|
associatedEntities: Generated<unknown | null>;
|
||||||
workedOnBy: string;
|
workedOnBy: string;
|
||||||
sleepTimer: number;
|
sleepTimer: number;
|
||||||
sleepSince: Timestamp | null;
|
sleepSince: Timestamp | null;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,115 @@
|
||||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
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 { 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')
|
@ObjectType('SolutionTarget')
|
||||||
export class SolutionTargetObjectType implements SolutionTarget {
|
export class SolutionTargetObjectType implements SolutionTarget {
|
||||||
|
|
@ -32,6 +141,8 @@ export class AnomalyObjectType implements Anomaly {
|
||||||
groupKey: string;
|
groupKey: string;
|
||||||
@Field()
|
@Field()
|
||||||
groupDescription: string;
|
groupDescription: string;
|
||||||
|
// @Field()
|
||||||
|
associatedEntities: JsonValue;
|
||||||
@Field()
|
@Field()
|
||||||
workedOnBy: string;
|
workedOnBy: string;
|
||||||
@Field(() => Int)
|
@Field(() => Int)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomal
|
||||||
import { KpiService } from '../feat-kpis/kpi.service';
|
import { KpiService } from '../feat-kpis/kpi.service';
|
||||||
import {
|
import {
|
||||||
AnomalyObjectType,
|
AnomalyObjectType,
|
||||||
|
AssociatedEntitiesObjectType,
|
||||||
PaginatedAnomalyGroupObjectType,
|
PaginatedAnomalyGroupObjectType,
|
||||||
PaginatedAnomalyObjectType,
|
PaginatedAnomalyObjectType,
|
||||||
SolutionTargetObjectType,
|
SolutionTargetObjectType,
|
||||||
|
|
@ -99,6 +100,11 @@ export class ControlCenterResolver {
|
||||||
return this.anomalyService.acceptAnomaly(anomalyId);
|
return this.anomalyService.acceptAnomaly(anomalyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(() => AssociatedEntitiesObjectType)
|
||||||
|
async associatedEntities(@Args({ name: 'id' }) anomalyId: string) {
|
||||||
|
return this.anomalyService.getAssociatedEntities(anomalyId);
|
||||||
|
}
|
||||||
|
|
||||||
//! Used when creating anomaly list
|
//! Used when creating anomaly list
|
||||||
// @ResolveField(() => TourObjectType, { nullable: true })
|
// @ResolveField(() => TourObjectType, { nullable: true })
|
||||||
// tour(@Parent() anomaly: AnomalyObjectType) {
|
// tour(@Parent() anomaly: AnomalyObjectType) {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ import { AnomalyDetectorService } from './anomaly-detector.service';
|
||||||
|
|
||||||
export type AnomalySkeleton = Pick<
|
export type AnomalySkeleton = Pick<
|
||||||
Prisma.AnomalyCreateInput,
|
Prisma.AnomalyCreateInput,
|
||||||
'createdAt' | 'workedOnBy' | 'sleepTimer' | 'sleepSince'
|
| 'associatedEntities'
|
||||||
|
| 'createdAt'
|
||||||
|
| 'workedOnBy'
|
||||||
|
| 'sleepTimer'
|
||||||
|
| 'sleepSince'
|
||||||
>;
|
>;
|
||||||
export abstract class AnomalyDetector {
|
export abstract class AnomalyDetector {
|
||||||
|
private associatedEntities: Record<string, { id: string }[]> = {};
|
||||||
|
|
||||||
constructor(protected readonly dataService: AnomalyDetectorService) {}
|
constructor(protected readonly dataService: AnomalyDetectorService) {}
|
||||||
/**
|
/**
|
||||||
* Unique identifier of the Anomaly Type which is implemented by the class
|
* 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 createDescription(data: unknown): string;
|
||||||
abstract createSolution(data: unknown): Prisma.SolutionTargetCreateInput[];
|
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 {
|
protected getAnomalySkeleton(): AnomalySkeleton {
|
||||||
|
const associatedEntities = this.associatedEntities;
|
||||||
|
this.flushAssociatedEntities();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
associatedEntities,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
workedOnBy: '',
|
workedOnBy: '',
|
||||||
sleepTimer: 60 * 60 * 1000,
|
sleepTimer: 60 * 60 * 1000,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,8 @@ export class AnomalyDetectorService {
|
||||||
],
|
],
|
||||||
): Promise<AttendanceRegistration[]> {
|
): Promise<AttendanceRegistration[]> {
|
||||||
let startOfGivenDay: Date;
|
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);
|
startOfGivenDay = addDays(day, -1);
|
||||||
} else {
|
} else {
|
||||||
startOfGivenDay = startOfDay(day);
|
startOfGivenDay = startOfDay(day);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,14 @@ export class DriverCertificateDetector extends AnomalyDetector {
|
||||||
toursInRange
|
toursInRange
|
||||||
.filter(({ driverId }) => !!driverId)
|
.filter(({ driverId }) => !!driverId)
|
||||||
.map(
|
.map(
|
||||||
async ({ driverId, startDate, driverName, carName, operationId }) => {
|
async ({
|
||||||
|
id: tourId,
|
||||||
|
driverId,
|
||||||
|
startDate,
|
||||||
|
driverName,
|
||||||
|
carName,
|
||||||
|
operationId,
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const driverEmployee =
|
const driverEmployee =
|
||||||
await this.dataService.findEmployeeById(driverId);
|
await this.dataService.findEmployeeById(driverId);
|
||||||
|
|
@ -37,6 +44,9 @@ export class DriverCertificateDetector extends AnomalyDetector {
|
||||||
startDate,
|
startDate,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
this.addAssociatedEntity('employee', driverEmployee.id);
|
||||||
|
this.addAssociatedEntity('tour', tourId);
|
||||||
|
|
||||||
return this.createAnomaly(
|
return this.createAnomaly(
|
||||||
`${operationId}-${driverId}-${this.type}`,
|
`${operationId}-${driverId}-${this.type}`,
|
||||||
operationId,
|
operationId,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,18 @@ export class DriverCodriverSameDetector extends AnomalyDetector {
|
||||||
|
|
||||||
const anomalies = await Promise.all(
|
const anomalies = await Promise.all(
|
||||||
toursInRange.map(
|
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(
|
return this.createAnomaly(
|
||||||
`${operationId}-${this.type}`,
|
`${operationId}-${this.type}`,
|
||||||
operationId,
|
operationId,
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
|
|
||||||
private async detectAnomalyForEmployee(
|
private async detectAnomalyForEmployee(
|
||||||
{
|
{
|
||||||
|
id: tourId,
|
||||||
driverId,
|
driverId,
|
||||||
codriverId,
|
codriverId,
|
||||||
startDate,
|
startDate,
|
||||||
|
|
@ -174,15 +175,15 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
|
|
||||||
const startOfTour = this.getStartOfTour(
|
const startOfTour = this.getStartOfTour(
|
||||||
new Date(startDate),
|
new Date(startDate),
|
||||||
[startBegin, startEnd, target, targetBegin, empty].filter(
|
[startBegin, startEnd, target, targetBegin, empty]
|
||||||
(val) => !!val,
|
.filter((val) => !!val)
|
||||||
)[0],
|
.at(0),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!startOfTour) return Promise.resolve(undefined);
|
if (!startOfTour) return Promise.resolve(undefined);
|
||||||
|
|
||||||
if (this.validateEmployee(employee)) {
|
if (this.validateEmployee(employee)) {
|
||||||
const [isEmployeeLoggedIn, checkedInterval] =
|
const [isEmployeeLoggedIn, checkedInterval, associatedEntities] =
|
||||||
await this.isEmployeeLoggedInAtDateTime(
|
await this.isEmployeeLoggedInAtDateTime(
|
||||||
employee.personnelNumber,
|
employee.personnelNumber,
|
||||||
startOfTour,
|
startOfTour,
|
||||||
|
|
@ -190,8 +191,14 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
|
|
||||||
if (!isEmployeeLoggedIn) {
|
if (!isEmployeeLoggedIn) {
|
||||||
const employeeName = `${employee.name} ${employee.surname}`;
|
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(
|
return this.createAnomaly(
|
||||||
`${operationId}-${driverId}-${this.type}`,
|
`${operationId}-${employeeId}-${this.type}`,
|
||||||
operationId,
|
operationId,
|
||||||
employeeName,
|
employeeName,
|
||||||
employee.mobile,
|
employee.mobile,
|
||||||
|
|
@ -258,7 +265,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
private async isEmployeeLoggedInAtDateTime(
|
private async isEmployeeLoggedInAtDateTime(
|
||||||
personnelNumber: string,
|
personnelNumber: string,
|
||||||
datetimeToCheck: Date,
|
datetimeToCheck: Date,
|
||||||
): Promise<[boolean, Interval]> {
|
): Promise<[boolean, Interval, Record<string, string[]>]> {
|
||||||
const workTime = await this.dataService.findWorkTimeByPersonnelNumberForDay(
|
const workTime = await this.dataService.findWorkTimeByPersonnelNumberForDay(
|
||||||
personnelNumber,
|
personnelNumber,
|
||||||
datetimeToCheck,
|
datetimeToCheck,
|
||||||
|
|
@ -288,6 +295,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
||||||
isWithinInterval(datetimeToCheck, interval),
|
isWithinInterval(datetimeToCheck, interval),
|
||||||
interval,
|
interval,
|
||||||
|
{ workTime: [workTime.id] },
|
||||||
];
|
];
|
||||||
} else if (
|
} else if (
|
||||||
firstClockedIn &&
|
firstClockedIn &&
|
||||||
|
|
@ -300,6 +308,12 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
start: firstClockedIn.dateTime,
|
start: firstClockedIn.dateTime,
|
||||||
end: undefined,
|
end: undefined,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
attendanceRegistration: [
|
||||||
|
`${firstClockedIn.attendanceRegistrationId}`,
|
||||||
|
`${lastClockedOut.attendanceRegistrationId}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
} else if (firstClockedIn && lastClockedOut) {
|
} else if (firstClockedIn && lastClockedOut) {
|
||||||
const interval = {
|
const interval = {
|
||||||
|
|
@ -310,6 +324,12 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
isWithinInterval(datetimeToCheckWithBuffer, interval) ||
|
||||||
isWithinInterval(datetimeToCheck, interval),
|
isWithinInterval(datetimeToCheck, interval),
|
||||||
interval,
|
interval,
|
||||||
|
{
|
||||||
|
attendanceRegistration: [
|
||||||
|
`${firstClockedIn.attendanceRegistrationId}`,
|
||||||
|
`${lastClockedOut.attendanceRegistrationId}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
} else if (firstClockedIn && !lastClockedOut) {
|
} else if (firstClockedIn && !lastClockedOut) {
|
||||||
return [
|
return [
|
||||||
|
|
@ -319,9 +339,14 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
datetimeToCheckWithBuffer,
|
datetimeToCheckWithBuffer,
|
||||||
),
|
),
|
||||||
{ start: firstClockedIn.dateTime, end: undefined },
|
{ start: firstClockedIn.dateTime, end: undefined },
|
||||||
|
{
|
||||||
|
attendanceRegistration: [
|
||||||
|
`${firstClockedIn.attendanceRegistrationId}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [false, undefined];
|
return [false, undefined, {}];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error, {
|
this.logger.error(error, {
|
||||||
|
|
@ -330,7 +355,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||||
firstClockedIn,
|
firstClockedIn,
|
||||||
lastClockedOut,
|
lastClockedOut,
|
||||||
});
|
});
|
||||||
return [false, undefined];
|
return [false, undefined, {}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,15 @@ export class WorkingEmployeeNotInDistributionSystemDetector extends AnomalyDetec
|
||||||
employee.id,
|
employee.id,
|
||||||
);
|
);
|
||||||
if (toursWithEmployee.length === 0) {
|
if (toursWithEmployee.length === 0) {
|
||||||
|
this.addAssociatedEntity(
|
||||||
|
'attendanceRegistration',
|
||||||
|
`${attendanceRegistrationId}`,
|
||||||
|
);
|
||||||
|
this.addAssociatedEntity('employee', employee.id);
|
||||||
|
plannedTime.map(({ id }) =>
|
||||||
|
this.addAssociatedEntity('plannedTime', id),
|
||||||
|
);
|
||||||
|
|
||||||
return this.createAnomaly(
|
return this.createAnomaly(
|
||||||
`${attendanceRegistrationId}-${employee.id}`,
|
`${attendanceRegistrationId}-${employee.id}`,
|
||||||
format(new Date(dateTime), 'yyyy-MM-dd'),
|
format(new Date(dateTime), 'yyyy-MM-dd'),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
import {
|
||||||
|
AttendanceRegistration,
|
||||||
|
Employee,
|
||||||
|
PlannedTime,
|
||||||
|
Tour,
|
||||||
|
WorkTime,
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
export interface AnomalyCreateDto {
|
export interface AnomalyCreateDto {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -25,3 +33,11 @@ export interface AnomalyUpdateDto {
|
||||||
resolvedAt: Date;
|
resolvedAt: Date;
|
||||||
manuallyResolved: boolean;
|
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 { Anomaly, SolutionTarget, SolutionTargetStep } from '@prisma/client';
|
||||||
import { AnomalyCreateDto } from './anomalies.dto';
|
import { AnomalyAsssociatedEntities, AnomalyCreateDto } from './anomalies.dto';
|
||||||
import { AnomalyTypes } from './anomaly-types.enum';
|
import { AnomalyTypes } from './anomaly-types.enum';
|
||||||
import { AnomalyRepository } from './anomaly.repository';
|
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()
|
@Injectable()
|
||||||
export class AnomalyService {
|
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> {
|
async createEvenIfExists(anomaly: AnomalyCreateDto): Promise<Anomaly> {
|
||||||
return this.repository.createEvenIfExists(anomaly);
|
return this.repository.createEvenIfExists(anomaly);
|
||||||
|
|
@ -44,6 +58,54 @@ export class AnomalyService {
|
||||||
return this.repository.countVisibleAnomalies(types);
|
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(
|
async findSolutionTargetsByAnomalyId(
|
||||||
anomalyId: string,
|
anomalyId: string,
|
||||||
): Promise<SolutionTarget[]> {
|
): Promise<SolutionTarget[]> {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ import { PrismaService } from 'src/core/database/prisma.service';
|
||||||
export class PlannedTimeRepository {
|
export class PlannedTimeRepository {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<PlannedTime> {
|
||||||
|
return await this.prisma.plannedTime.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async findByPersonnelNumberInInterval(
|
async findByPersonnelNumberInInterval(
|
||||||
personnelNumber: string,
|
personnelNumber: string,
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import { PlannedTimeRepository } from './planned-time.repository';
|
||||||
export class PlannedTimeService {
|
export class PlannedTimeService {
|
||||||
constructor(private readonly repository: PlannedTimeRepository) {}
|
constructor(private readonly repository: PlannedTimeRepository) {}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<PlannedTime> {
|
||||||
|
return this.repository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
async findByPersonnelNumberInInterval(
|
async findByPersonnelNumberInInterval(
|
||||||
personnelNumber: string,
|
personnelNumber: string,
|
||||||
interval: Interval,
|
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(
|
async findInInterval(
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
ordinanceTypes: string[] = [],
|
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> {
|
async findByOperationId(operationId: string): Promise<Tour> {
|
||||||
return this.repository.findByOperationId(operationId);
|
return this.repository.findByOperationId(operationId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ import { PrismaService } from 'src/core/database/prisma.service';
|
||||||
export class WorkTimeRepository {
|
export class WorkTimeRepository {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<WorkTime> {
|
||||||
|
return await this.prisma.workTime.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async findByPersonnelNumberInInterval(
|
async findByPersonnelNumberInInterval(
|
||||||
personnelNumber: string,
|
personnelNumber: string,
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import { WorkTimeRepository } from './work-time.repository';
|
||||||
export class WorkTimeService {
|
export class WorkTimeService {
|
||||||
constructor(private readonly repository: WorkTimeRepository) {}
|
constructor(private readonly repository: WorkTimeRepository) {}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<WorkTime> {
|
||||||
|
return this.repository.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
async findByPersonnelNumberInInterval(
|
async findByPersonnelNumberInInterval(
|
||||||
personnelNumber: string,
|
personnelNumber: string,
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,10 @@ export class KpiService {
|
||||||
include: {
|
include: {
|
||||||
state: true,
|
state: true,
|
||||||
},
|
},
|
||||||
|
orderBy: {
|
||||||
|
id: 'asc'
|
||||||
|
},
|
||||||
|
take: 1000
|
||||||
});
|
});
|
||||||
const timePerTickets = movedTickets
|
const timePerTickets = movedTickets
|
||||||
.map(({ state }) => {
|
.map(({ state }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue