diff --git a/src/modules/ad/core/application/queries/match/algorithm.abstract.ts b/src/modules/ad/core/application/queries/match/algorithm.abstract.ts index faeb9bc..a31a7f5 100644 --- a/src/modules/ad/core/application/queries/match/algorithm.abstract.ts +++ b/src/modules/ad/core/application/queries/match/algorithm.abstract.ts @@ -2,6 +2,10 @@ import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { MatchEntity } from '../../../domain/match.entity'; import { MatchQuery } from './match.query'; import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port'; +import { + Journey, + JourneyProps, +} from '@modules/ad/core/domain/value-objects/journey.value-object'; export abstract class Algorithm { protected candidates: CandidateEntity[]; @@ -22,7 +26,13 @@ export abstract class Algorithm { } // console.log(JSON.stringify(this.candidates, null, 2)); return this.candidates.map((candidate: CandidateEntity) => - MatchEntity.create({ adId: candidate.id }), + MatchEntity.create({ + adId: candidate.id, + role: candidate.getProps().role, + distance: candidate.getProps().distance as number, + duration: candidate.getProps().duration as number, + journeys: candidate.getProps().journeys as Journey[], + }), ); }; } diff --git a/src/modules/ad/core/domain/match.types.ts b/src/modules/ad/core/domain/match.types.ts index 911029b..055bce3 100644 --- a/src/modules/ad/core/domain/match.types.ts +++ b/src/modules/ad/core/domain/match.types.ts @@ -1,13 +1,23 @@ import { AlgorithmType } from '../application/types/algorithm.types'; +import { Role } from './ad.types'; +import { JourneyProps } from './value-objects/journey.value-object'; // All properties that a Match has export interface MatchProps { adId: string; + role: Role; + distance: number; + duration: number; + journeys: JourneyProps[]; } // Properties that are needed for a Match creation export interface CreateMatchProps { adId: string; + role: Role; + distance: number; + duration: number; + journeys: JourneyProps[]; } export interface DefaultMatchQueryProps { diff --git a/src/modules/ad/interface/dtos/actor.response.dto.ts b/src/modules/ad/interface/dtos/actor.response.dto.ts new file mode 100644 index 0000000..bb86ff3 --- /dev/null +++ b/src/modules/ad/interface/dtos/actor.response.dto.ts @@ -0,0 +1,10 @@ +export class ActorResponseDto { + role: string; + target: string; + firstDatetime: string; + firstMinDatetime: string; + firstMaxDatetime: string; + lastDatetime: string; + lastMinDatetime: string; + lastMaxDatetime: string; +} diff --git a/src/modules/ad/interface/dtos/journey.response.dto.ts b/src/modules/ad/interface/dtos/journey.response.dto.ts new file mode 100644 index 0000000..f4c5736 --- /dev/null +++ b/src/modules/ad/interface/dtos/journey.response.dto.ts @@ -0,0 +1,7 @@ +import { StepResponseDto } from './step.response.dto'; + +export class JourneyResponseDto { + firstDate: string; + lastDate: string; + steps: StepResponseDto[]; +} diff --git a/src/modules/ad/interface/dtos/match.response.dto.ts b/src/modules/ad/interface/dtos/match.response.dto.ts index bc01e6f..ce22d4b 100644 --- a/src/modules/ad/interface/dtos/match.response.dto.ts +++ b/src/modules/ad/interface/dtos/match.response.dto.ts @@ -1,5 +1,10 @@ import { ResponseBase } from '@mobicoop/ddd-library'; +import { JourneyResponseDto } from './journey.response.dto'; export class MatchResponseDto extends ResponseBase { adId: string; + role: string; + distance: number; + duration: number; + journeys: JourneyResponseDto[]; } diff --git a/src/modules/ad/interface/dtos/step.response.dto.ts b/src/modules/ad/interface/dtos/step.response.dto.ts new file mode 100644 index 0000000..b2240ac --- /dev/null +++ b/src/modules/ad/interface/dtos/step.response.dto.ts @@ -0,0 +1,9 @@ +import { ActorResponseDto } from './actor.response.dto'; + +export class StepResponseDto { + duration: number; + distance: number; + lon: number; + lat: number; + actors: ActorResponseDto[]; +} diff --git a/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts b/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts index 1f80b69..e21c784 100644 --- a/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts +++ b/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts @@ -9,6 +9,9 @@ import { MatchQuery } from '@modules/ad/core/application/queries/match/match.que import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens'; import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; +import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object'; +import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object'; +import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object'; @UsePipes( new RpcValidationPipe({ @@ -34,6 +37,29 @@ export class MatchGrpcController { data: matches.map((match: MatchEntity) => ({ ...new ResponseBase(match), adId: match.getProps().adId, + role: match.getProps().role, + distance: match.getProps().distance, + duration: match.getProps().duration, + journeys: match.getProps().journeys.map((journey: Journey) => ({ + firstDate: journey.firstDate.toUTCString(), + lastDate: journey.lastDate.toUTCString(), + steps: journey.journeyItems.map((journeyItem: JourneyItem) => ({ + duration: journeyItem.duration, + distance: journeyItem.distance as number, + lon: journeyItem.lon, + lat: journeyItem.lat, + actors: journeyItem.actorTimes.map((actorTime: ActorTime) => ({ + role: actorTime.role, + target: actorTime.target, + firstDatetime: actorTime.firstMinDatetime.toUTCString(), + firstMinDatetime: actorTime.firstMinDatetime.toUTCString(), + firstMaxDatetime: actorTime.firstMaxDatetime.toUTCString(), + lastDatetime: actorTime.lastDatetime.toUTCString(), + lastMinDatetime: actorTime.lastMinDatetime.toUTCString(), + lastMaxDatetime: actorTime.lastMaxDatetime.toUTCString(), + })), + })), + })), })), page: 1, perPage: 5, diff --git a/src/modules/ad/interface/grpc-controllers/matcher.proto b/src/modules/ad/interface/grpc-controllers/matcher.proto index 11d9a4d..29ac5ff 100644 --- a/src/modules/ad/interface/grpc-controllers/matcher.proto +++ b/src/modules/ad/interface/grpc-controllers/matcher.proto @@ -56,6 +56,35 @@ enum AlgorithmType { message Match { string id = 1; string adId = 2; + string role = 3; + int32 duration = 4; + int32 distance = 5; + repeated Journey journeys = 6; +} + +message Journey { + string firstDate = 1; + string lastDate = 2; + repeated Step steps = 3; +} + +message Step { + int32 duration = 1; + int32 distance = 2; + double lon = 3; + double lat = 4; + repeated Actor actors = 5; +} + +message Actor { + string role = 1; + string target = 2; + string firstDatetime = 3; + string firstMinDatetime = 4; + string firstMaxDatetime = 5; + string lastDatetime = 6; + string lastMinDatetime = 7; + string lastMaxDatetime = 8; } message Matches { diff --git a/src/modules/ad/tests/unit/core/match.entity.spec.ts b/src/modules/ad/tests/unit/core/match.entity.spec.ts index 5f2f4e5..fe57511 100644 --- a/src/modules/ad/tests/unit/core/match.entity.spec.ts +++ b/src/modules/ad/tests/unit/core/match.entity.spec.ts @@ -1,9 +1,116 @@ +import { Role } from '@modules/ad/core/domain/ad.types'; +import { Target } from '@modules/ad/core/domain/candidate.types'; import { MatchEntity } from '@modules/ad/core/domain/match.entity'; +import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object'; +import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object'; describe('Match entity create', () => { it('should create a new match entity', async () => { const match: MatchEntity = MatchEntity.create({ adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1', + role: Role.DRIVER, + distance: 356041, + duration: 12647, + journeys: [ + { + firstDate: new Date('2023-09-01'), + lastDate: new Date('2024-08-30'), + journeyItems: [ + new JourneyItem({ + lat: 48.689445, + lon: 6.17651, + duration: 0, + distance: 0, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.START, + firstDatetime: new Date('2023-09-01 07:00'), + firstMinDatetime: new Date('2023-09-01 06:45'), + firstMaxDatetime: new Date('2023-09-01 07:15'), + lastDatetime: new Date('2024-08-30 07:00'), + lastMinDatetime: new Date('2024-08-30 06:45'), + lastMaxDatetime: new Date('2024-08-30 07:15'), + }), + ], + }), + new JourneyItem({ + lat: 48.369445, + lon: 6.67487, + duration: 2100, + distance: 56878, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.NEUTRAL, + firstDatetime: new Date('2023-09-01 07:35'), + firstMinDatetime: new Date('2023-09-01 07:20'), + firstMaxDatetime: new Date('2023-09-01 07:50'), + lastDatetime: new Date('2024-08-30 07:35'), + lastMinDatetime: new Date('2024-08-30 07:20'), + lastMaxDatetime: new Date('2024-08-30 07:50'), + }), + new ActorTime({ + role: Role.PASSENGER, + target: Target.START, + firstDatetime: new Date('2023-09-01 07:32'), + firstMinDatetime: new Date('2023-09-01 07:17'), + firstMaxDatetime: new Date('2023-09-01 07:47'), + lastDatetime: new Date('2024-08-30 07:32'), + lastMinDatetime: new Date('2024-08-30 07:17'), + lastMaxDatetime: new Date('2024-08-30 07:47'), + }), + ], + }), + new JourneyItem({ + lat: 47.98487, + lon: 6.9427, + duration: 3840, + distance: 76491, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.NEUTRAL, + firstDatetime: new Date('2023-09-01 08:04'), + firstMinDatetime: new Date('2023-09-01 07:51'), + firstMaxDatetime: new Date('2023-09-01 08:19'), + lastDatetime: new Date('2024-08-30 08:04'), + lastMinDatetime: new Date('2024-08-30 07:51'), + lastMaxDatetime: new Date('2024-08-30 08:19'), + }), + new ActorTime({ + role: Role.PASSENGER, + target: Target.FINISH, + firstDatetime: new Date('2023-09-01 08:01'), + firstMinDatetime: new Date('2023-09-01 07:46'), + firstMaxDatetime: new Date('2023-09-01 08:16'), + lastDatetime: new Date('2024-08-30 08:01'), + lastMinDatetime: new Date('2024-08-30 07:46'), + lastMaxDatetime: new Date('2024-08-30 08:16'), + }), + ], + }), + new JourneyItem({ + lat: 47.365987, + lon: 7.02154, + duration: 4980, + distance: 96475, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.FINISH, + firstDatetime: new Date('2023-09-01 08:23'), + firstMinDatetime: new Date('2023-09-01 08:08'), + firstMaxDatetime: new Date('2023-09-01 08:38'), + lastDatetime: new Date('2024-08-30 08:23'), + lastMinDatetime: new Date('2024-08-30 08:08'), + lastMaxDatetime: new Date('2024-08-30 08:38'), + }), + ], + }), + ], + }, + ], }); expect(match.id.length).toBe(36); }); diff --git a/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts b/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts index 6f75433..9d39955 100644 --- a/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts +++ b/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts @@ -2,8 +2,11 @@ import { RpcExceptionCode } from '@mobicoop/ddd-library'; import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens'; import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; -import { Frequency } from '@modules/ad/core/domain/ad.types'; +import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; +import { Target } from '@modules/ad/core/domain/candidate.types'; import { MatchEntity } from '@modules/ad/core/domain/match.entity'; +import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object'; +import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object'; import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto'; import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller'; @@ -53,10 +56,110 @@ const mockQueryBus = { .fn() .mockImplementationOnce(() => [ MatchEntity.create({ - adId: '0cc87f3b-7a27-4eff-9850-a5d642c2a0c3', - }), - MatchEntity.create({ - adId: 'e4cc156f-aaa5-4270-bf6f-82f5a230d748', + adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1', + role: Role.DRIVER, + distance: 356041, + duration: 12647, + journeys: [ + { + firstDate: new Date('2023-09-01'), + lastDate: new Date('2024-08-30'), + journeyItems: [ + new JourneyItem({ + lat: 48.689445, + lon: 6.17651, + duration: 0, + distance: 0, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.START, + firstDatetime: new Date('2023-09-01 07:00'), + firstMinDatetime: new Date('2023-09-01 06:45'), + firstMaxDatetime: new Date('2023-09-01 07:15'), + lastDatetime: new Date('2024-08-30 07:00'), + lastMinDatetime: new Date('2024-08-30 06:45'), + lastMaxDatetime: new Date('2024-08-30 07:15'), + }), + ], + }), + new JourneyItem({ + lat: 48.369445, + lon: 6.67487, + duration: 2100, + distance: 56878, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.NEUTRAL, + firstDatetime: new Date('2023-09-01 07:35'), + firstMinDatetime: new Date('2023-09-01 07:20'), + firstMaxDatetime: new Date('2023-09-01 07:50'), + lastDatetime: new Date('2024-08-30 07:35'), + lastMinDatetime: new Date('2024-08-30 07:20'), + lastMaxDatetime: new Date('2024-08-30 07:50'), + }), + new ActorTime({ + role: Role.PASSENGER, + target: Target.START, + firstDatetime: new Date('2023-09-01 07:32'), + firstMinDatetime: new Date('2023-09-01 07:17'), + firstMaxDatetime: new Date('2023-09-01 07:47'), + lastDatetime: new Date('2024-08-30 07:32'), + lastMinDatetime: new Date('2024-08-30 07:17'), + lastMaxDatetime: new Date('2024-08-30 07:47'), + }), + ], + }), + new JourneyItem({ + lat: 47.98487, + lon: 6.9427, + duration: 3840, + distance: 76491, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.NEUTRAL, + firstDatetime: new Date('2023-09-01 08:04'), + firstMinDatetime: new Date('2023-09-01 07:51'), + firstMaxDatetime: new Date('2023-09-01 08:19'), + lastDatetime: new Date('2024-08-30 08:04'), + lastMinDatetime: new Date('2024-08-30 07:51'), + lastMaxDatetime: new Date('2024-08-30 08:19'), + }), + new ActorTime({ + role: Role.PASSENGER, + target: Target.FINISH, + firstDatetime: new Date('2023-09-01 08:01'), + firstMinDatetime: new Date('2023-09-01 07:46'), + firstMaxDatetime: new Date('2023-09-01 08:16'), + lastDatetime: new Date('2024-08-30 08:01'), + lastMinDatetime: new Date('2024-08-30 07:46'), + lastMaxDatetime: new Date('2024-08-30 08:16'), + }), + ], + }), + new JourneyItem({ + lat: 47.365987, + lon: 7.02154, + duration: 4980, + distance: 96475, + actorTimes: [ + new ActorTime({ + role: Role.DRIVER, + target: Target.FINISH, + firstDatetime: new Date('2023-09-01 08:23'), + firstMinDatetime: new Date('2023-09-01 08:08'), + firstMaxDatetime: new Date('2023-09-01 08:38'), + lastDatetime: new Date('2024-08-30 08:23'), + lastMinDatetime: new Date('2024-08-30 08:08'), + lastMaxDatetime: new Date('2024-08-30 08:38'), + }), + ], + }), + ], + }, + ], }), ]) .mockImplementationOnce(() => { @@ -103,7 +206,7 @@ describe('Match Grpc Controller', () => { const matchPaginatedResponseDto = await matchGrpcController.match( punctualMatchRequestDto, ); - expect(matchPaginatedResponseDto.data).toHaveLength(2); + expect(matchPaginatedResponseDto.data).toHaveLength(1); expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); });