format response
This commit is contained in:
parent
528ecfb3f9
commit
d0285e265e
|
@ -62,4 +62,4 @@ SEATS_REQUESTED=1
|
||||||
STRICT_FREQUENCY=false
|
STRICT_FREQUENCY=false
|
||||||
|
|
||||||
# default timezone
|
# default timezone
|
||||||
DEFAULT_TIMEZONE=Europe/Paris
|
TIMEZONE=Europe/Paris
|
||||||
|
|
|
@ -12,3 +12,6 @@ export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
|
||||||
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
||||||
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
||||||
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
||||||
|
export const OUTPUT_DATETIME_TRANSFORMER = Symbol(
|
||||||
|
'OUTPUT_DATETIME_TRANSFORMER',
|
||||||
|
);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
TIME_CONVERTER,
|
TIME_CONVERTER,
|
||||||
INPUT_DATETIME_TRANSFORMER,
|
INPUT_DATETIME_TRANSFORMER,
|
||||||
AD_GET_DETAILED_ROUTE_CONTROLLER,
|
AD_GET_DETAILED_ROUTE_CONTROLLER,
|
||||||
|
OUTPUT_DATETIME_TRANSFORMER,
|
||||||
} from './ad.di-tokens';
|
} from './ad.di-tokens';
|
||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
import { AdRepository } from './infrastructure/ad.repository';
|
import { AdRepository } from './infrastructure/ad.repository';
|
||||||
|
@ -29,6 +30,8 @@ import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||||
import { TimeConverter } from './infrastructure/time-converter';
|
import { TimeConverter } from './infrastructure/time-converter';
|
||||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
||||||
import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller';
|
import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller';
|
||||||
|
import { MatchMapper } from './match.mapper';
|
||||||
|
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||||
|
|
||||||
const grpcControllers = [MatchGrpcController];
|
const grpcControllers = [MatchGrpcController];
|
||||||
|
|
||||||
|
@ -38,7 +41,7 @@ const commandHandlers: Provider[] = [CreateAdService];
|
||||||
|
|
||||||
const queryHandlers: Provider[] = [MatchQueryHandler];
|
const queryHandlers: Provider[] = [MatchQueryHandler];
|
||||||
|
|
||||||
const mappers: Provider[] = [AdMapper];
|
const mappers: Provider[] = [AdMapper, MatchMapper];
|
||||||
|
|
||||||
const repositories: Provider[] = [
|
const repositories: Provider[] = [
|
||||||
{
|
{
|
||||||
|
@ -89,6 +92,10 @@ const adapters: Provider[] = [
|
||||||
provide: INPUT_DATETIME_TRANSFORMER,
|
provide: INPUT_DATETIME_TRANSFORMER,
|
||||||
useClass: InputDateTimeTransformer,
|
useClass: InputDateTimeTransformer,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||||
|
useClass: OutputDateTimeTransformer,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { MatchEntity } from '../../../domain/match.entity';
|
import { MatchEntity } from '../../../domain/match.entity';
|
||||||
import { MatchQuery } from './match.query';
|
import { MatchQuery } from './match.query';
|
||||||
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
|
import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port';
|
||||||
import {
|
import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
|
||||||
Journey,
|
|
||||||
JourneyProps,
|
|
||||||
} from '@modules/ad/core/domain/value-objects/journey.value-object';
|
|
||||||
|
|
||||||
export abstract class Algorithm {
|
export abstract class Algorithm {
|
||||||
protected candidates: CandidateEntity[];
|
protected candidates: CandidateEntity[];
|
||||||
|
@ -29,8 +26,11 @@ export abstract class Algorithm {
|
||||||
MatchEntity.create({
|
MatchEntity.create({
|
||||||
adId: candidate.id,
|
adId: candidate.id,
|
||||||
role: candidate.getProps().role,
|
role: candidate.getProps().role,
|
||||||
|
frequency: candidate.getProps().frequency,
|
||||||
distance: candidate.getProps().distance as number,
|
distance: candidate.getProps().distance as number,
|
||||||
duration: candidate.getProps().duration as number,
|
duration: candidate.getProps().duration as number,
|
||||||
|
initialDistance: candidate.getProps().driverDistance,
|
||||||
|
initialDuration: candidate.getProps().driverDuration,
|
||||||
journeys: candidate.getProps().journeys as Journey[],
|
journeys: candidate.getProps().journeys as Journey[],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,6 +36,7 @@ export class PassengerOrientedSelector extends Selector {
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: adEntity.id,
|
id: adEntity.id,
|
||||||
role: adsRole.role == Role.DRIVER ? Role.PASSENGER : Role.DRIVER,
|
role: adsRole.role == Role.DRIVER ? Role.PASSENGER : Role.DRIVER,
|
||||||
|
frequency: adEntity.getProps().frequency,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: this._maxDateString(
|
lowerDate: this._maxDateString(
|
||||||
this.query.fromDate,
|
this.query.fromDate,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Role } from './ad.types';
|
import { Frequency, Role } from './ad.types';
|
||||||
import { PointProps } from './value-objects/point.value-object';
|
import { PointProps } from './value-objects/point.value-object';
|
||||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||||
import { CarpoolPathItemProps } from './value-objects/carpool-path-item.value-object';
|
import { CarpoolPathItemProps } from './value-objects/carpool-path-item.value-object';
|
||||||
|
@ -8,6 +8,7 @@ import { StepProps } from './value-objects/step.value-object';
|
||||||
// All properties that a Candidate has
|
// All properties that a Candidate has
|
||||||
export interface CandidateProps {
|
export interface CandidateProps {
|
||||||
role: Role;
|
role: Role;
|
||||||
|
frequency: Frequency;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
passengerWaypoints: PointProps[];
|
passengerWaypoints: PointProps[];
|
||||||
driverSchedule: ScheduleItemProps[];
|
driverSchedule: ScheduleItemProps[];
|
||||||
|
@ -27,6 +28,7 @@ export interface CandidateProps {
|
||||||
export interface CreateCandidateProps {
|
export interface CreateCandidateProps {
|
||||||
id: string;
|
id: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
frequency: Frequency;
|
||||||
driverDistance: number;
|
driverDistance: number;
|
||||||
driverDuration: number;
|
driverDuration: number;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
|
|
|
@ -7,7 +7,17 @@ export class MatchEntity extends AggregateRoot<MatchProps> {
|
||||||
|
|
||||||
static create = (create: CreateMatchProps): MatchEntity => {
|
static create = (create: CreateMatchProps): MatchEntity => {
|
||||||
const id = v4();
|
const id = v4();
|
||||||
const props: MatchProps = { ...create };
|
const props: MatchProps = {
|
||||||
|
...create,
|
||||||
|
distanceDetour: create.distance - create.initialDistance,
|
||||||
|
durationDetour: create.duration - create.initialDuration,
|
||||||
|
distanceDetourPercentage: parseFloat(
|
||||||
|
((100 * create.distance) / create.initialDistance - 100).toFixed(2),
|
||||||
|
),
|
||||||
|
durationDetourPercentage: parseFloat(
|
||||||
|
((100 * create.duration) / create.initialDuration - 100).toFixed(2),
|
||||||
|
),
|
||||||
|
};
|
||||||
return new MatchEntity({ id, props });
|
return new MatchEntity({ id, props });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
import { AlgorithmType } from '../application/types/algorithm.types';
|
import { AlgorithmType } from '../application/types/algorithm.types';
|
||||||
import { Role } from './ad.types';
|
import { Frequency, Role } from './ad.types';
|
||||||
import { JourneyProps } from './value-objects/journey.value-object';
|
import { JourneyProps } from './value-objects/journey.value-object';
|
||||||
|
|
||||||
// All properties that a Match has
|
// All properties that a Match has
|
||||||
export interface MatchProps {
|
export interface MatchProps {
|
||||||
adId: string;
|
adId: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
frequency: Frequency;
|
||||||
distance: number;
|
distance: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
initialDistance: number;
|
||||||
|
initialDuration: number;
|
||||||
|
distanceDetour: number;
|
||||||
|
durationDetour: number;
|
||||||
|
distanceDetourPercentage: number;
|
||||||
|
durationDetourPercentage: number;
|
||||||
journeys: JourneyProps[];
|
journeys: JourneyProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +22,11 @@ export interface MatchProps {
|
||||||
export interface CreateMatchProps {
|
export interface CreateMatchProps {
|
||||||
adId: string;
|
adId: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
|
frequency: Frequency;
|
||||||
distance: number;
|
distance: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
initialDistance: number;
|
||||||
|
initialDuration: number;
|
||||||
journeys: JourneyProps[];
|
journeys: JourneyProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
ArgumentOutOfRangeException,
|
ArgumentOutOfRangeException,
|
||||||
ValueObject,
|
ValueObject,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { Actor } from './actor.value-object';
|
import { Actor, ActorProps } from './actor.value-object';
|
||||||
import { Role } from '../ad.types';
|
import { Role } from '../ad.types';
|
||||||
import { Point, PointProps } from './point.value-object';
|
import { Point, PointProps } from './point.value-object';
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { Point, PointProps } from './point.value-object';
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface CarpoolPathItemProps extends PointProps {
|
export interface CarpoolPathItemProps extends PointProps {
|
||||||
actors: Actor[];
|
actors: ActorProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CarpoolPathItem extends ValueObject<CarpoolPathItemProps> {
|
export class CarpoolPathItem extends ValueObject<CarpoolPathItemProps> {
|
||||||
|
@ -24,7 +24,7 @@ export class CarpoolPathItem extends ValueObject<CarpoolPathItemProps> {
|
||||||
return this.props.lat;
|
return this.props.lat;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actors(): Actor[] {
|
get actors(): ActorProps[] {
|
||||||
return this.props.actors;
|
return this.props.actors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ import {
|
||||||
ArgumentOutOfRangeException,
|
ArgumentOutOfRangeException,
|
||||||
ValueObject,
|
ValueObject,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { ActorTime } from './actor-time.value-object';
|
import { ActorTime, ActorTimeProps } from './actor-time.value-object';
|
||||||
import { Step, StepProps } from './step.value-object';
|
import { Step, StepProps } from './step.value-object';
|
||||||
|
import { Role } from '../ad.types';
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
|
@ -11,7 +12,7 @@ import { Step, StepProps } from './step.value-object';
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface JourneyItemProps extends StepProps {
|
export interface JourneyItemProps extends StepProps {
|
||||||
actorTimes: ActorTime[];
|
actorTimes: ActorTimeProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JourneyItem extends ValueObject<JourneyItemProps> {
|
export class JourneyItem extends ValueObject<JourneyItemProps> {
|
||||||
|
@ -31,10 +32,22 @@ export class JourneyItem extends ValueObject<JourneyItemProps> {
|
||||||
return this.props.lat;
|
return this.props.lat;
|
||||||
}
|
}
|
||||||
|
|
||||||
get actorTimes(): ActorTime[] {
|
get actorTimes(): ActorTimeProps[] {
|
||||||
return this.props.actorTimes;
|
return this.props.actorTimes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
driverTime = (): string => {
|
||||||
|
const driverTime: Date = (
|
||||||
|
this.actorTimes.find(
|
||||||
|
(actorTime: ActorTime) => actorTime.role == Role.DRIVER,
|
||||||
|
) as ActorTime
|
||||||
|
).firstDatetime;
|
||||||
|
return `${driverTime.getHours().toString().padStart(2, '0')}:${driverTime
|
||||||
|
.getMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
protected validate(props: JourneyItemProps): void {
|
protected validate(props: JourneyItemProps): void {
|
||||||
// validate step props
|
// validate step props
|
||||||
new Step({
|
new Step({
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
import { JourneyItem } from './journey-item.value-object';
|
import { JourneyItem, JourneyItemProps } from './journey-item.value-object';
|
||||||
import { ActorTime } from './actor-time.value-object';
|
import { ActorTime } from './actor-time.value-object';
|
||||||
import { Role } from '../ad.types';
|
import { Role } from '../ad.types';
|
||||||
import { Target } from '../candidate.types';
|
import { Target } from '../candidate.types';
|
||||||
|
import { Point } from './point.value-object';
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
|
@ -12,7 +13,7 @@ import { Target } from '../candidate.types';
|
||||||
export interface JourneyProps {
|
export interface JourneyProps {
|
||||||
firstDate: Date;
|
firstDate: Date;
|
||||||
lastDate: Date;
|
lastDate: Date;
|
||||||
journeyItems: JourneyItem[];
|
journeyItems: JourneyItemProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Journey extends ValueObject<JourneyProps> {
|
export class Journey extends ValueObject<JourneyProps> {
|
||||||
|
@ -24,7 +25,7 @@ export class Journey extends ValueObject<JourneyProps> {
|
||||||
return this.props.lastDate;
|
return this.props.lastDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get journeyItems(): JourneyItem[] {
|
get journeyItems(): JourneyItemProps[] {
|
||||||
return this.props.journeyItems;
|
return this.props.journeyItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +60,37 @@ export class Journey extends ValueObject<JourneyProps> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
firstDriverDepartureTime = (): string => {
|
||||||
|
const firstDriverDepartureDatetime: Date = (
|
||||||
|
this._driverDepartureJourneyItem().actorTimes.find(
|
||||||
|
(actorTime: ActorTime) =>
|
||||||
|
actorTime.role == Role.DRIVER && actorTime.target == Target.START,
|
||||||
|
) as ActorTime
|
||||||
|
).firstDatetime;
|
||||||
|
return `${firstDriverDepartureDatetime
|
||||||
|
.getUTCHours()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}:${firstDriverDepartureDatetime
|
||||||
|
.getUTCMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
driverOrigin = (): Point =>
|
||||||
|
new Point({
|
||||||
|
lon: this._driverDepartureJourneyItem().lon,
|
||||||
|
lat: this._driverDepartureJourneyItem().lat,
|
||||||
|
});
|
||||||
|
|
||||||
|
private _driverDepartureJourneyItem = (): JourneyItem =>
|
||||||
|
this.journeyItems.find(
|
||||||
|
(journeyItem: JourneyItem) =>
|
||||||
|
journeyItem.actorTimes.find(
|
||||||
|
(actorTime: ActorTime) =>
|
||||||
|
actorTime.role == Role.DRIVER && actorTime.target == Target.START,
|
||||||
|
) as ActorTime,
|
||||||
|
) as JourneyItem;
|
||||||
|
|
||||||
protected validate(props: JourneyProps): void {
|
protected validate(props: JourneyProps): void {
|
||||||
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
if (props.firstDate.getUTCDay() != props.lastDate.getUTCDay())
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
DateTimeTransformerPort,
|
||||||
|
Frequency,
|
||||||
|
GeoDateTime,
|
||||||
|
} from '../core/application/ports/datetime-transformer.port';
|
||||||
|
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||||
|
import { TIMEZONE_FINDER, TIME_CONVERTER } from '../ad.di-tokens';
|
||||||
|
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OutputDateTimeTransformer implements DateTimeTransformerPort {
|
||||||
|
constructor(
|
||||||
|
@Inject(TIMEZONE_FINDER)
|
||||||
|
private readonly timezoneFinder: TimezoneFinderPort,
|
||||||
|
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the fromDate : if an ad is punctual, the departure date
|
||||||
|
* is converted from UTC to the local date with the time and timezone
|
||||||
|
*/
|
||||||
|
fromDate = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||||
|
if (frequency === Frequency.RECURRENT) return geoFromDate.date;
|
||||||
|
return this.timeConverter
|
||||||
|
.utcStringDateTimeToLocalIsoString(
|
||||||
|
geoFromDate.date,
|
||||||
|
geoFromDate.time,
|
||||||
|
this.timezoneFinder.timezones(
|
||||||
|
geoFromDate.coordinates.lon,
|
||||||
|
geoFromDate.coordinates.lat,
|
||||||
|
)[0],
|
||||||
|
)
|
||||||
|
.split('T')[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the toDate depending on frequency, time and timezone :
|
||||||
|
* if the ad is punctual, the toDate is equal to the fromDate
|
||||||
|
*/
|
||||||
|
toDate = (
|
||||||
|
toDate: string,
|
||||||
|
geoFromDate: GeoDateTime,
|
||||||
|
frequency: Frequency,
|
||||||
|
): string => {
|
||||||
|
if (frequency === Frequency.RECURRENT) return toDate;
|
||||||
|
return this.fromDate(geoFromDate, frequency);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the day for a schedule item :
|
||||||
|
* - if the ad is punctual, the day is infered from fromDate
|
||||||
|
* - if the ad is recurrent, the day is computed by converting the time from utc to local time
|
||||||
|
*/
|
||||||
|
day = (
|
||||||
|
day: number,
|
||||||
|
geoFromDate: GeoDateTime,
|
||||||
|
frequency: Frequency,
|
||||||
|
): number => {
|
||||||
|
if (frequency === Frequency.RECURRENT)
|
||||||
|
return this.recurrentDay(
|
||||||
|
day,
|
||||||
|
geoFromDate.time,
|
||||||
|
this.timezoneFinder.timezones(
|
||||||
|
geoFromDate.coordinates.lon,
|
||||||
|
geoFromDate.coordinates.lat,
|
||||||
|
)[0],
|
||||||
|
);
|
||||||
|
return new Date(this.fromDate(geoFromDate, frequency)).getDay();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the utc time
|
||||||
|
*/
|
||||||
|
time = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||||
|
if (frequency === Frequency.RECURRENT)
|
||||||
|
return this.timeConverter.utcStringTimeToLocalStringTime(
|
||||||
|
geoFromDate.time,
|
||||||
|
this.timezoneFinder.timezones(
|
||||||
|
geoFromDate.coordinates.lon,
|
||||||
|
geoFromDate.coordinates.lat,
|
||||||
|
)[0],
|
||||||
|
);
|
||||||
|
return this.timeConverter
|
||||||
|
.utcStringDateTimeToLocalIsoString(
|
||||||
|
geoFromDate.date,
|
||||||
|
geoFromDate.time,
|
||||||
|
this.timezoneFinder.timezones(
|
||||||
|
geoFromDate.coordinates.lon,
|
||||||
|
geoFromDate.coordinates.lat,
|
||||||
|
)[0],
|
||||||
|
)
|
||||||
|
.split('T')[1]
|
||||||
|
.split(':', 2)
|
||||||
|
.join(':');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the day for a schedule item for a recurrent ad
|
||||||
|
* The day may change when transforming from utc to local timezone
|
||||||
|
*/
|
||||||
|
private recurrentDay = (
|
||||||
|
day: number,
|
||||||
|
time: string,
|
||||||
|
timezone: string,
|
||||||
|
): number => {
|
||||||
|
const unixEpochDay = 4; // 1970-01-01 is a thursday !
|
||||||
|
const localBaseDay = this.timeConverter.localUnixEpochDayFromTime(
|
||||||
|
time,
|
||||||
|
timezone,
|
||||||
|
);
|
||||||
|
if (unixEpochDay == localBaseDay) return day;
|
||||||
|
if (unixEpochDay > localBaseDay) return day > 0 ? day - 1 : 6;
|
||||||
|
return day < 6 ? day + 1 : 0;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,10 +1,4 @@
|
||||||
export class ActorResponseDto {
|
export class ActorResponseDto {
|
||||||
role: string;
|
role: string;
|
||||||
target: string;
|
target: string;
|
||||||
firstDatetime: string;
|
|
||||||
firstMinDatetime: string;
|
|
||||||
firstMaxDatetime: string;
|
|
||||||
lastDatetime: string;
|
|
||||||
lastMinDatetime: string;
|
|
||||||
lastMaxDatetime: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { StepResponseDto } from './step.response.dto';
|
import { StepResponseDto } from './step.response.dto';
|
||||||
|
|
||||||
export class JourneyResponseDto {
|
export class JourneyResponseDto {
|
||||||
|
weekday: number;
|
||||||
firstDate: string;
|
firstDate: string;
|
||||||
lastDate: string;
|
lastDate: string;
|
||||||
steps: StepResponseDto[];
|
steps: StepResponseDto[];
|
||||||
|
|
|
@ -4,7 +4,14 @@ import { JourneyResponseDto } from './journey.response.dto';
|
||||||
export class MatchResponseDto extends ResponseBase {
|
export class MatchResponseDto extends ResponseBase {
|
||||||
adId: string;
|
adId: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
frequency: string;
|
||||||
distance: number;
|
distance: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
initialDistance: number;
|
||||||
|
initialDuration: number;
|
||||||
|
distanceDetour: number;
|
||||||
|
durationDetour: number;
|
||||||
|
distanceDetourPercentage: number;
|
||||||
|
durationDetourPercentage: number;
|
||||||
journeys: JourneyResponseDto[];
|
journeys: JourneyResponseDto[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { ActorResponseDto } from './actor.response.dto';
|
import { ActorResponseDto } from './actor.response.dto';
|
||||||
|
|
||||||
export class StepResponseDto {
|
export class StepResponseDto {
|
||||||
duration: number;
|
|
||||||
distance: number;
|
distance: number;
|
||||||
|
duration: number;
|
||||||
lon: number;
|
lon: number;
|
||||||
lat: number;
|
lat: number;
|
||||||
|
time: string;
|
||||||
actors: ActorResponseDto[];
|
actors: ActorResponseDto[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Controller, Inject, UsePipes } from '@nestjs/common';
|
import { Controller, Inject, UsePipes } from '@nestjs/common';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { ResponseBase, RpcValidationPipe } from '@mobicoop/ddd-library';
|
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
|
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
@ -9,9 +9,7 @@ import { MatchQuery } from '@modules/ad/core/application/queries/match/match.que
|
||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||||
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
|
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
|
||||||
import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
|
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||||
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(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
|
@ -25,6 +23,7 @@ export class MatchGrpcController {
|
||||||
private readonly queryBus: QueryBus,
|
private readonly queryBus: QueryBus,
|
||||||
@Inject(AD_ROUTE_PROVIDER)
|
@Inject(AD_ROUTE_PROVIDER)
|
||||||
private readonly routeProvider: RouteProviderPort,
|
private readonly routeProvider: RouteProviderPort,
|
||||||
|
private readonly matchMapper: MatchMapper,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@GrpcMethod('MatcherService', 'Match')
|
@GrpcMethod('MatcherService', 'Match')
|
||||||
|
@ -34,33 +33,9 @@ export class MatchGrpcController {
|
||||||
new MatchQuery(data, this.routeProvider),
|
new MatchQuery(data, this.routeProvider),
|
||||||
);
|
);
|
||||||
return new MatchPaginatedResponseDto({
|
return new MatchPaginatedResponseDto({
|
||||||
data: matches.map((match: MatchEntity) => ({
|
data: matches.map((match: MatchEntity) =>
|
||||||
...new ResponseBase(match),
|
this.matchMapper.toResponse(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,
|
page: 1,
|
||||||
perPage: 5,
|
perPage: 5,
|
||||||
total: matches.length,
|
total: matches.length,
|
||||||
|
|
|
@ -57,34 +57,36 @@ message Match {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string adId = 2;
|
string adId = 2;
|
||||||
string role = 3;
|
string role = 3;
|
||||||
int32 duration = 4;
|
int32 distance = 4;
|
||||||
int32 distance = 5;
|
int32 duration = 5;
|
||||||
repeated Journey journeys = 6;
|
int32 initialDistance = 6;
|
||||||
|
int32 initialDuration = 7;
|
||||||
|
int32 distanceDetour = 8;
|
||||||
|
int32 durationDetour = 9;
|
||||||
|
double distanceDetourPercentage = 10;
|
||||||
|
double durationDetourPercentage = 11;
|
||||||
|
repeated Journey journeys = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Journey {
|
message Journey {
|
||||||
string firstDate = 1;
|
int32 weekday = 1;
|
||||||
string lastDate = 2;
|
string firstDate = 2;
|
||||||
repeated Step steps = 3;
|
string lastDate = 3;
|
||||||
|
repeated Step steps = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Step {
|
message Step {
|
||||||
int32 duration = 1;
|
int32 distance = 1;
|
||||||
int32 distance = 2;
|
int32 duration = 2;
|
||||||
double lon = 3;
|
double lon = 3;
|
||||||
double lat = 4;
|
double lat = 4;
|
||||||
repeated Actor actors = 5;
|
string time = 5;
|
||||||
|
repeated Actor actors = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Actor {
|
message Actor {
|
||||||
string role = 1;
|
string role = 1;
|
||||||
string target = 2;
|
string target = 2;
|
||||||
string firstDatetime = 3;
|
|
||||||
string firstMinDatetime = 4;
|
|
||||||
string firstMaxDatetime = 5;
|
|
||||||
string lastDatetime = 6;
|
|
||||||
string lastMinDatetime = 7;
|
|
||||||
string lastMaxDatetime = 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Matches {
|
message Matches {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { MatchEntity } from './core/domain/match.entity';
|
||||||
|
import { MatchResponseDto } from './interface/dtos/match.response.dto';
|
||||||
|
import { ResponseBase } from '@mobicoop/ddd-library';
|
||||||
|
import { Journey } from './core/domain/value-objects/journey.value-object';
|
||||||
|
import { JourneyItem } from './core/domain/value-objects/journey-item.value-object';
|
||||||
|
import { ActorTime } from './core/domain/value-objects/actor-time.value-object';
|
||||||
|
import { OUTPUT_DATETIME_TRANSFORMER } from './ad.di-tokens';
|
||||||
|
import { DateTimeTransformerPort } from './core/application/ports/datetime-transformer.port';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MatchMapper {
|
||||||
|
constructor(
|
||||||
|
@Inject(OUTPUT_DATETIME_TRANSFORMER)
|
||||||
|
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
toResponse = (match: MatchEntity): MatchResponseDto => ({
|
||||||
|
...new ResponseBase(match),
|
||||||
|
adId: match.getProps().adId,
|
||||||
|
role: match.getProps().role,
|
||||||
|
frequency: match.getProps().frequency,
|
||||||
|
distance: match.getProps().distance,
|
||||||
|
duration: match.getProps().duration,
|
||||||
|
initialDistance: match.getProps().initialDistance,
|
||||||
|
initialDuration: match.getProps().initialDuration,
|
||||||
|
distanceDetour: match.getProps().distanceDetour,
|
||||||
|
durationDetour: match.getProps().durationDetour,
|
||||||
|
distanceDetourPercentage: match.getProps().distanceDetourPercentage,
|
||||||
|
durationDetourPercentage: match.getProps().durationDetourPercentage,
|
||||||
|
journeys: match.getProps().journeys.map((journey: Journey) => ({
|
||||||
|
weekday: new Date(
|
||||||
|
this.outputDatetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: journey.firstDate.toISOString().split('T')[0],
|
||||||
|
time: journey.firstDriverDepartureTime(),
|
||||||
|
coordinates: journey.driverOrigin(),
|
||||||
|
},
|
||||||
|
match.getProps().frequency,
|
||||||
|
),
|
||||||
|
).getDay(),
|
||||||
|
firstDate: this.outputDatetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: journey.firstDate.toISOString().split('T')[0],
|
||||||
|
time: journey.firstDriverDepartureTime(),
|
||||||
|
coordinates: journey.driverOrigin(),
|
||||||
|
},
|
||||||
|
match.getProps().frequency,
|
||||||
|
),
|
||||||
|
lastDate: this.outputDatetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: journey.lastDate.toISOString().split('T')[0],
|
||||||
|
time: journey.firstDriverDepartureTime(),
|
||||||
|
coordinates: journey.driverOrigin(),
|
||||||
|
},
|
||||||
|
match.getProps().frequency,
|
||||||
|
),
|
||||||
|
steps: journey.journeyItems.map((journeyItem: JourneyItem) => ({
|
||||||
|
duration: journeyItem.duration,
|
||||||
|
distance: journeyItem.distance as number,
|
||||||
|
lon: journeyItem.lon,
|
||||||
|
lat: journeyItem.lat,
|
||||||
|
time: this.outputDatetimeTransformer.time(
|
||||||
|
{
|
||||||
|
date: journey.firstDate.toISOString().split('T')[0],
|
||||||
|
time: journeyItem.driverTime(),
|
||||||
|
coordinates: journey.driverOrigin(),
|
||||||
|
},
|
||||||
|
match.getProps().frequency,
|
||||||
|
),
|
||||||
|
actors: journeyItem.actorTimes.map((actorTime: ActorTime) => ({
|
||||||
|
role: actorTime.role,
|
||||||
|
target: actorTime.target,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
|
@ -67,6 +67,7 @@ class SomeSelector extends Selector {
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import {
|
import {
|
||||||
SpacetimeDetourRatio,
|
SpacetimeDetourRatio,
|
||||||
|
@ -253,6 +253,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -272,6 +273,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -291,6 +293,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -312,6 +315,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -330,6 +334,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -351,6 +356,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -372,6 +378,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-09-01',
|
lowerDate: '2023-09-01',
|
||||||
higherDate: '2024-09-01',
|
higherDate: '2024-09-01',
|
||||||
|
@ -408,6 +415,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-09-01',
|
lowerDate: '2023-09-01',
|
||||||
higherDate: '2024-09-01',
|
higherDate: '2024-09-01',
|
||||||
|
@ -454,6 +462,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-09-01',
|
lowerDate: '2023-09-01',
|
||||||
higherDate: '2024-09-01',
|
higherDate: '2024-09-01',
|
||||||
|
@ -477,6 +486,7 @@ describe('Candidate entity', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-09-01',
|
lowerDate: '2023-09-01',
|
||||||
higherDate: '2024-09-01',
|
higherDate: '2024-09-01',
|
||||||
|
|
|
@ -65,6 +65,7 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -49,6 +49,7 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Role } 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 { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||||
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
|
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
|
||||||
|
@ -9,8 +9,11 @@ describe('Match entity create', () => {
|
||||||
const match: MatchEntity = MatchEntity.create({
|
const match: MatchEntity = MatchEntity.create({
|
||||||
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
distance: 356041,
|
distance: 356041,
|
||||||
duration: 12647,
|
duration: 12647,
|
||||||
|
initialDistance: 315478,
|
||||||
|
initialDuration: 12105,
|
||||||
journeys: [
|
journeys: [
|
||||||
{
|
{
|
||||||
firstDate: new Date('2023-09-01'),
|
firstDate: new Date('2023-09-01'),
|
||||||
|
|
|
@ -50,6 +50,7 @@ const candidates: CandidateEntity[] = [
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
@ -98,6 +99,7 @@ const candidates: CandidateEntity[] = [
|
||||||
CandidateEntity.create({
|
CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -49,6 +49,7 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -69,6 +69,7 @@ const matchQuery = new MatchQuery(
|
||||||
const candidate: CandidateEntity = CandidateEntity.create({
|
const candidate: CandidateEntity = CandidateEntity.create({
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
dateInterval: {
|
dateInterval: {
|
||||||
lowerDate: '2023-08-28',
|
lowerDate: '2023-08-28',
|
||||||
higherDate: '2023-08-28',
|
higherDate: '2023-08-28',
|
||||||
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
import {
|
||||||
|
PARAMS_PROVIDER,
|
||||||
|
TIMEZONE_FINDER,
|
||||||
|
TIME_CONVERTER,
|
||||||
|
} from '@modules/ad/ad.di-tokens';
|
||||||
|
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
|
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||||
|
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||||
|
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||||
|
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
||||||
|
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||||
|
getParams: () => {
|
||||||
|
return {
|
||||||
|
DEPARTURE_TIME_MARGIN: 900,
|
||||||
|
DRIVER: false,
|
||||||
|
SEATS_PROPOSED: 3,
|
||||||
|
PASSENGER: true,
|
||||||
|
SEATS_REQUESTED: 1,
|
||||||
|
STRICT: false,
|
||||||
|
TIMEZONE: 'Europe/Paris',
|
||||||
|
ALGORITHM_TYPE: AlgorithmType.PASSENGER_ORIENTED,
|
||||||
|
REMOTENESS: 15000,
|
||||||
|
USE_PROPORTION: true,
|
||||||
|
PROPORTION: 0.3,
|
||||||
|
USE_AZIMUTH: true,
|
||||||
|
AZIMUTH_MARGIN: 10,
|
||||||
|
MAX_DETOUR_DISTANCE_RATIO: 0.3,
|
||||||
|
MAX_DETOUR_DURATION_RATIO: 0.3,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||||
|
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTimeConverter: TimeConverterPort = {
|
||||||
|
localStringTimeToUtcStringTime: jest.fn(),
|
||||||
|
utcStringTimeToLocalStringTime: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => '00:15'),
|
||||||
|
localStringDateTimeToUtcDate: jest.fn(),
|
||||||
|
utcStringDateTimeToLocalIsoString: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => '2023-07-30T08:15:00.000+02:00')
|
||||||
|
.mockImplementationOnce(() => '2023-07-20T10:15:00.000+02:00')
|
||||||
|
.mockImplementationOnce(() => '2023-07-19T23:15:00.000+02:00')
|
||||||
|
.mockImplementationOnce(() => '2023-07-20T00:15:00.000+02:00'),
|
||||||
|
utcUnixEpochDayFromTime: jest.fn(),
|
||||||
|
localUnixEpochDayFromTime: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => 4)
|
||||||
|
.mockImplementationOnce(() => 5)
|
||||||
|
.mockImplementationOnce(() => 5)
|
||||||
|
.mockImplementationOnce(() => 3)
|
||||||
|
.mockImplementationOnce(() => 3),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Output Datetime Transformer', () => {
|
||||||
|
let outputDatetimeTransformer: OutputDateTimeTransformer;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PARAMS_PROVIDER,
|
||||||
|
useValue: mockDefaultParamsProvider,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TIMEZONE_FINDER,
|
||||||
|
useValue: mockTimezoneFinder,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TIME_CONVERTER,
|
||||||
|
useValue: mockTimeConverter,
|
||||||
|
},
|
||||||
|
OutputDateTimeTransformer,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
outputDatetimeTransformer = module.get<OutputDateTimeTransformer>(
|
||||||
|
OutputDateTimeTransformer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(outputDatetimeTransformer).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fromDate', () => {
|
||||||
|
it('should return fromDate as is if frequency is recurrent', () => {
|
||||||
|
const transformedFromDate: string = outputDatetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: '2023-07-30',
|
||||||
|
time: '07:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(transformedFromDate).toBe('2023-07-30');
|
||||||
|
});
|
||||||
|
it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => {
|
||||||
|
const transformedFromDate: string = outputDatetimeTransformer.fromDate(
|
||||||
|
{
|
||||||
|
date: '2023-07-30',
|
||||||
|
time: '07:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.PUNCTUAL,
|
||||||
|
);
|
||||||
|
expect(transformedFromDate).toBe('2023-07-30');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toDate', () => {
|
||||||
|
it('should return toDate as is if frequency is recurrent', () => {
|
||||||
|
const transformedToDate: string = outputDatetimeTransformer.toDate(
|
||||||
|
'2024-07-29',
|
||||||
|
{
|
||||||
|
date: '2023-07-20',
|
||||||
|
time: '10:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(transformedToDate).toBe('2024-07-29');
|
||||||
|
});
|
||||||
|
it('should return transformed fromDate if frequency is punctual', () => {
|
||||||
|
const transformedToDate: string = outputDatetimeTransformer.toDate(
|
||||||
|
'2024-07-30',
|
||||||
|
{
|
||||||
|
date: '2023-07-20',
|
||||||
|
time: '08:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.PUNCTUAL,
|
||||||
|
);
|
||||||
|
expect(transformedToDate).toBe('2023-07-20');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('day', () => {
|
||||||
|
it('should not change day if frequency is recurrent and converted local time is on the same day', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
date: '2023-07-24',
|
||||||
|
time: '00:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(day).toBe(1);
|
||||||
|
});
|
||||||
|
it('should change day if frequency is recurrent and converted local time is on the next day', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
date: '2023-07-23',
|
||||||
|
time: '23:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(day).toBe(1);
|
||||||
|
});
|
||||||
|
it('should change day if frequency is recurrent and converted local time is on the next day and given day is saturday', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
6,
|
||||||
|
{
|
||||||
|
date: '2023-07-23',
|
||||||
|
time: '23:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(day).toBe(0);
|
||||||
|
});
|
||||||
|
it('should change day if frequency is recurrent and converted local time is on the previous day', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
date: '2023-07-25',
|
||||||
|
time: '00:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 30.82,
|
||||||
|
lat: 49.37,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(day).toBe(0);
|
||||||
|
});
|
||||||
|
it('should change day if frequency is recurrent and converted local time is on the previous day and given day is sunday(0)', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
date: '2023-07-30',
|
||||||
|
time: '00:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 30.82,
|
||||||
|
lat: 49.37,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(day).toBe(6);
|
||||||
|
});
|
||||||
|
it('should return local fromDate day if frequency is punctual', () => {
|
||||||
|
const day: number = outputDatetimeTransformer.day(
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
date: '2023-07-20',
|
||||||
|
time: '00:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.PUNCTUAL,
|
||||||
|
);
|
||||||
|
expect(day).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('time', () => {
|
||||||
|
it('should transform utc time to local time if frequency is recurrent', () => {
|
||||||
|
const time: string = outputDatetimeTransformer.time(
|
||||||
|
{
|
||||||
|
date: '2023-07-23',
|
||||||
|
time: '23:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.RECURRENT,
|
||||||
|
);
|
||||||
|
expect(time).toBe('00:15');
|
||||||
|
});
|
||||||
|
it('should return local time if frequency is punctual', () => {
|
||||||
|
const time: string = outputDatetimeTransformer.time(
|
||||||
|
{
|
||||||
|
date: '2023-07-19',
|
||||||
|
time: '23:15',
|
||||||
|
coordinates: {
|
||||||
|
lon: 6.175,
|
||||||
|
lat: 48.685,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frequency.PUNCTUAL,
|
||||||
|
);
|
||||||
|
expect(time).toBe('00:15');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,7 @@ import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.
|
||||||
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
||||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||||
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
|
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
|
||||||
|
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
import { RpcException } from '@nestjs/microservices';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
@ -33,16 +34,16 @@ const destinationWaypoint: WaypointDto = {
|
||||||
country: 'France',
|
country: 'France',
|
||||||
};
|
};
|
||||||
|
|
||||||
const punctualMatchRequestDto: MatchRequestDto = {
|
const recurrentMatchRequestDto: MatchRequestDto = {
|
||||||
driver: false,
|
driver: false,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.RECURRENT,
|
||||||
fromDate: '2023-08-15',
|
fromDate: '2023-08-15',
|
||||||
toDate: '2023-08-15',
|
toDate: '2024-09-30',
|
||||||
schedule: [
|
schedule: [
|
||||||
{
|
{
|
||||||
time: '07:00',
|
time: '07:00',
|
||||||
day: 2,
|
day: 5,
|
||||||
margin: 900,
|
margin: 900,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -58,8 +59,11 @@ const mockQueryBus = {
|
||||||
MatchEntity.create({
|
MatchEntity.create({
|
||||||
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
distance: 356041,
|
distance: 356041,
|
||||||
duration: 12647,
|
duration: 12647,
|
||||||
|
initialDistance: 349251,
|
||||||
|
initialDuration: 12103,
|
||||||
journeys: [
|
journeys: [
|
||||||
{
|
{
|
||||||
firstDate: new Date('2023-09-01'),
|
firstDate: new Date('2023-09-01'),
|
||||||
|
@ -172,6 +176,116 @@ const mockRouteProvider: RouteProviderPort = {
|
||||||
getDetailed: jest.fn(),
|
getDetailed: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockMatchMapper = {
|
||||||
|
toResponse: jest.fn().mockImplementation(() => ({
|
||||||
|
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||||
|
role: 'DRIVER',
|
||||||
|
frequency: 'RECURRENT',
|
||||||
|
distance: 356041,
|
||||||
|
duration: 12647,
|
||||||
|
journeys: [
|
||||||
|
{
|
||||||
|
firstDate: '2023-09-01',
|
||||||
|
lastDate: '2024-08-30',
|
||||||
|
journeyItems: [
|
||||||
|
{
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
duration: 0,
|
||||||
|
distance: 0,
|
||||||
|
actorTimes: [
|
||||||
|
{
|
||||||
|
role: 'DRIVER',
|
||||||
|
target: 'START',
|
||||||
|
firstDatetime: '2023-09-01 07:00',
|
||||||
|
firstMinDatetime: '2023-09-01 06:45',
|
||||||
|
firstMaxDatetime: '2023-09-01 07:15',
|
||||||
|
lastDatetime: '2024-08-30 07:00',
|
||||||
|
lastMinDatetime: '2024-08-30 06:45',
|
||||||
|
lastMaxDatetime: '2024-08-30 07:15',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.369445,
|
||||||
|
lon: 6.67487,
|
||||||
|
duration: 2100,
|
||||||
|
distance: 56878,
|
||||||
|
actorTimes: [
|
||||||
|
{
|
||||||
|
role: 'DRIVER',
|
||||||
|
target: 'NEUTRAL',
|
||||||
|
firstDatetime: '2023-09-01 07:35',
|
||||||
|
firstMinDatetime: '2023-09-01 07:20',
|
||||||
|
firstMaxDatetime: '2023-09-01 07:50',
|
||||||
|
lastDatetime: '2024-08-30 07:35',
|
||||||
|
lastMinDatetime: '2024-08-30 07:20',
|
||||||
|
lastMaxDatetime: '2024-08-30 07:50',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'PASSENGER',
|
||||||
|
target: 'START',
|
||||||
|
firstDatetime: '2023-09-01 07:32',
|
||||||
|
firstMinDatetime: '2023-09-01 07:17',
|
||||||
|
firstMaxDatetime: '2023-09-01 07:47',
|
||||||
|
lastDatetime: '2024-08-30 07:32',
|
||||||
|
lastMinDatetime: '2024-08-30 07:17',
|
||||||
|
lastMaxDatetime: '2024-08-30 07:47',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.98487,
|
||||||
|
lon: 6.9427,
|
||||||
|
duration: 3840,
|
||||||
|
distance: 76491,
|
||||||
|
actorTimes: [
|
||||||
|
{
|
||||||
|
role: 'DRIVER',
|
||||||
|
target: 'NEUTRAL',
|
||||||
|
firstDatetime: '2023-09-01 08:04',
|
||||||
|
firstMinDatetime: '2023-09-01 07:51',
|
||||||
|
firstMaxDatetime: '2023-09-01 08:19',
|
||||||
|
lastDatetime: '2024-08-30 08:04',
|
||||||
|
lastMinDatetime: '2024-08-30 07:51',
|
||||||
|
lastMaxDatetime: '2024-08-30 08:19',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'PASSENGER',
|
||||||
|
target: 'FINISH',
|
||||||
|
firstDatetime: '2023-09-01 08:01',
|
||||||
|
firstMinDatetime: '2023-09-01 07:46',
|
||||||
|
firstMaxDatetime: '2023-09-01 08:16',
|
||||||
|
lastDatetime: '2024-08-30 08:01',
|
||||||
|
lastMinDatetime: '2024-08-30 07:46',
|
||||||
|
lastMaxDatetime: '2024-08-30 08:16',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 47.365987,
|
||||||
|
lon: 7.02154,
|
||||||
|
duration: 4980,
|
||||||
|
distance: 96475,
|
||||||
|
actorTimes: [
|
||||||
|
{
|
||||||
|
role: 'DRIVER',
|
||||||
|
target: 'FINISH',
|
||||||
|
firstDatetime: '2023-09-01 08:23',
|
||||||
|
firstMinDatetime: '2023-09-01 08:08',
|
||||||
|
firstMaxDatetime: '2023-09-01 08:38',
|
||||||
|
lastDatetime: '2024-08-30 08:23',
|
||||||
|
lastMinDatetime: '2024-08-30 08:08',
|
||||||
|
lastMaxDatetime: '2024-08-30 08:38',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
describe('Match Grpc Controller', () => {
|
describe('Match Grpc Controller', () => {
|
||||||
let matchGrpcController: MatchGrpcController;
|
let matchGrpcController: MatchGrpcController;
|
||||||
|
|
||||||
|
@ -187,6 +301,10 @@ describe('Match Grpc Controller', () => {
|
||||||
provide: AD_ROUTE_PROVIDER,
|
provide: AD_ROUTE_PROVIDER,
|
||||||
useValue: mockRouteProvider,
|
useValue: mockRouteProvider,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: MatchMapper,
|
||||||
|
useValue: mockMatchMapper,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
@ -204,7 +322,7 @@ describe('Match Grpc Controller', () => {
|
||||||
it('should return matches', async () => {
|
it('should return matches', async () => {
|
||||||
jest.spyOn(mockQueryBus, 'execute');
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
const matchPaginatedResponseDto = await matchGrpcController.match(
|
const matchPaginatedResponseDto = await matchGrpcController.match(
|
||||||
punctualMatchRequestDto,
|
recurrentMatchRequestDto,
|
||||||
);
|
);
|
||||||
expect(matchPaginatedResponseDto.data).toHaveLength(1);
|
expect(matchPaginatedResponseDto.data).toHaveLength(1);
|
||||||
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
@ -214,7 +332,7 @@ describe('Match Grpc Controller', () => {
|
||||||
jest.spyOn(mockQueryBus, 'execute');
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
try {
|
try {
|
||||||
await matchGrpcController.match(punctualMatchRequestDto);
|
await matchGrpcController.match(recurrentMatchRequestDto);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
expect(e).toBeInstanceOf(RpcException);
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
|
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 { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
|
||||||
|
import { MatchResponseDto } from '@modules/ad/interface/dtos/match.response.dto';
|
||||||
|
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const matchEntity: MatchEntity = MatchEntity.create({
|
||||||
|
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||||
|
role: Role.DRIVER,
|
||||||
|
frequency: Frequency.RECURRENT,
|
||||||
|
distance: 356041,
|
||||||
|
duration: 12647,
|
||||||
|
initialDistance: 315478,
|
||||||
|
initialDuration: 12105,
|
||||||
|
journeys: [
|
||||||
|
new Journey({
|
||||||
|
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'),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockOutputDatetimeTransformer: DateTimeTransformerPort = {
|
||||||
|
fromDate: jest.fn(),
|
||||||
|
toDate: jest.fn(),
|
||||||
|
day: jest.fn(),
|
||||||
|
time: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Match Mapper', () => {
|
||||||
|
let matchMapper: MatchMapper;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
MatchMapper,
|
||||||
|
{
|
||||||
|
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||||
|
useValue: mockOutputDatetimeTransformer,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
matchMapper = module.get<MatchMapper>(MatchMapper);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(matchMapper).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map domain entity to response', async () => {
|
||||||
|
const mapped: MatchResponseDto = matchMapper.toResponse(matchEntity);
|
||||||
|
expect(mapped.journeys).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue