bette use of value objects
This commit is contained in:
parent
4731020e8a
commit
74fb2c120e
|
@ -14,7 +14,6 @@ import {
|
||||||
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
|
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
|
||||||
import { AD_DIRECTION_ENCODER } from './ad.di-tokens';
|
import { AD_DIRECTION_ENCODER } from './ad.di-tokens';
|
||||||
import { ExtendedMapper } from '@mobicoop/ddd-library';
|
import { ExtendedMapper } from '@mobicoop/ddd-library';
|
||||||
import { Waypoint } from './core/domain/value-objects/waypoint.value-object';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapper constructs objects that are used in different layers:
|
* Mapper constructs objects that are used in different layers:
|
||||||
|
@ -112,13 +111,12 @@ export class AdMapper
|
||||||
margin: scheduleItem.margin,
|
margin: scheduleItem.margin,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
waypoints: this.directionEncoder.decode(record.waypoints).map(
|
waypoints: this.directionEncoder
|
||||||
(coordinates, index) =>
|
.decode(record.waypoints)
|
||||||
new Waypoint({
|
.map((coordinates, index) => ({
|
||||||
position: index,
|
position: index,
|
||||||
...coordinates,
|
...coordinates,
|
||||||
}),
|
})),
|
||||||
),
|
|
||||||
fwdAzimuth: record.fwdAzimuth,
|
fwdAzimuth: record.fwdAzimuth,
|
||||||
backAzimuth: record.backAzimuth,
|
backAzimuth: record.backAzimuth,
|
||||||
points: [],
|
points: [],
|
||||||
|
|
|
@ -13,8 +13,10 @@ import {
|
||||||
PathCreator,
|
PathCreator,
|
||||||
PathType,
|
PathType,
|
||||||
TypedRoute,
|
TypedRoute,
|
||||||
} from '@modules/ad/core/domain/patch-creator.service';
|
} from '@modules/ad/core/domain/path-creator.service';
|
||||||
import { Point } from '../../types/point.type';
|
import { Point } from '../../types/point.type';
|
||||||
|
import { Waypoint } from '../../types/waypoint.type';
|
||||||
|
import { Waypoint as WaypointValueObject } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
@CommandHandler(CreateAdCommand)
|
@CommandHandler(CreateAdCommand)
|
||||||
export class CreateAdService implements ICommandHandler {
|
export class CreateAdService implements ICommandHandler {
|
||||||
|
@ -29,11 +31,21 @@ export class CreateAdService implements ICommandHandler {
|
||||||
const roles: Role[] = [];
|
const roles: Role[] = [];
|
||||||
if (command.driver) roles.push(Role.DRIVER);
|
if (command.driver) roles.push(Role.DRIVER);
|
||||||
if (command.passenger) roles.push(Role.PASSENGER);
|
if (command.passenger) roles.push(Role.PASSENGER);
|
||||||
const pathCreator: PathCreator = new PathCreator(roles, command.waypoints);
|
const pathCreator: PathCreator = new PathCreator(
|
||||||
|
roles,
|
||||||
|
command.waypoints.map(
|
||||||
|
(waypoint: Waypoint) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
let typedRoutes: TypedRoute[];
|
let typedRoutes: TypedRoute[];
|
||||||
try {
|
try {
|
||||||
typedRoutes = await Promise.all(
|
typedRoutes = await Promise.all(
|
||||||
pathCreator.getPaths().map(async (path: Path) => ({
|
pathCreator.getBasePaths().map(async (path: Path) => ({
|
||||||
type: path.type,
|
type: path.type,
|
||||||
route: await this.routeProvider.getBasic(path.waypoints),
|
route: await this.routeProvider.getBasic(path.waypoints),
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Route } from '@modules/geography/core/domain/route.types';
|
import { Route } from '@modules/geography/core/domain/route.types';
|
||||||
import { Waypoint } from '../types/waypoint.type';
|
import { Waypoint } from '../../domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
export interface RouteProviderPort {
|
export interface RouteProviderPort {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import { Completer } from './completer.abstract';
|
import { Completer } from './completer.abstract';
|
||||||
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import {
|
||||||
|
Waypoint as WaypointValueObject,
|
||||||
|
WaypointProps,
|
||||||
|
} from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
import { Waypoint } from '../../../types/waypoint.type';
|
||||||
|
import { WayStepsCreator } from '@modules/ad/core/domain/waysteps-creator.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete candidates by setting driver and crew waypoints
|
* Complete candidates by setting driver and crew waypoints
|
||||||
|
@ -7,7 +14,53 @@ import { Completer } from './completer.abstract';
|
||||||
export class PassengerOrientedWaypointsCompleter extends Completer {
|
export class PassengerOrientedWaypointsCompleter extends Completer {
|
||||||
complete = async (
|
complete = async (
|
||||||
candidates: CandidateEntity[],
|
candidates: CandidateEntity[],
|
||||||
): Promise<CandidateEntity[]> => candidates;
|
): Promise<CandidateEntity[]> => {
|
||||||
|
candidates.forEach((candidate: CandidateEntity) => {
|
||||||
|
const carpoolPathCreator = new WayStepsCreator(
|
||||||
|
candidate.getProps().role == Role.DRIVER
|
||||||
|
? candidate.getProps().waypoints.map(
|
||||||
|
(waypoint: WaypointProps) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: this.query.waypoints.map(
|
||||||
|
(waypoint: Waypoint) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
candidate.getProps().role == Role.PASSENGER
|
||||||
|
? candidate.getProps().waypoints.map(
|
||||||
|
(waypoint: WaypointProps) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: this.query.waypoints.map(
|
||||||
|
(waypoint: Waypoint) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
candidate.setWaySteps(carpoolPathCreator.getCrewCarpoolPath());
|
||||||
|
});
|
||||||
|
// console.log(
|
||||||
|
// candidates[0]
|
||||||
|
// .getProps()
|
||||||
|
// .waySteps?.map((waystep: WayStep) => waystep.actors),
|
||||||
|
// );
|
||||||
|
return candidates;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete = async (candidates: Candidate[]): Promise<Candidate[]> => {
|
// complete = async (candidates: Candidate[]): Promise<Candidate[]> => {
|
||||||
|
|
|
@ -11,7 +11,8 @@ import {
|
||||||
PathCreator,
|
PathCreator,
|
||||||
PathType,
|
PathType,
|
||||||
TypedRoute,
|
TypedRoute,
|
||||||
} from '@modules/ad/core/domain/patch-creator.service';
|
} from '@modules/ad/core/domain/path-creator.service';
|
||||||
|
import { Waypoint as WaypointValueObject } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
export class MatchQuery extends QueryBase {
|
export class MatchQuery extends QueryBase {
|
||||||
driver?: boolean;
|
driver?: boolean;
|
||||||
|
@ -37,6 +38,7 @@ export class MatchQuery extends QueryBase {
|
||||||
driverRoute?: Route;
|
driverRoute?: Route;
|
||||||
passengerRoute?: Route;
|
passengerRoute?: Route;
|
||||||
backAzimuth?: number;
|
backAzimuth?: number;
|
||||||
|
private readonly originWaypoint: Waypoint;
|
||||||
|
|
||||||
constructor(props: MatchRequestDto) {
|
constructor(props: MatchRequestDto) {
|
||||||
super();
|
super();
|
||||||
|
@ -60,6 +62,9 @@ export class MatchQuery extends QueryBase {
|
||||||
this.maxDetourDurationRatio = props.maxDetourDurationRatio;
|
this.maxDetourDurationRatio = props.maxDetourDurationRatio;
|
||||||
this.page = props.page ?? 1;
|
this.page = props.page ?? 1;
|
||||||
this.perPage = props.perPage ?? 10;
|
this.perPage = props.perPage ?? 10;
|
||||||
|
this.originWaypoint = this.waypoints.filter(
|
||||||
|
(waypoint: Waypoint) => waypoint.position == 0,
|
||||||
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => {
|
setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => {
|
||||||
|
@ -126,8 +131,8 @@ export class MatchQuery extends QueryBase {
|
||||||
date: initialFromDate,
|
date: initialFromDate,
|
||||||
time: this.schedule[0].time,
|
time: this.schedule[0].time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.originWaypoint.lon,
|
||||||
lat: this.waypoints[0].lat,
|
lat: this.originWaypoint.lat,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.frequency,
|
this.frequency,
|
||||||
|
@ -138,8 +143,8 @@ export class MatchQuery extends QueryBase {
|
||||||
date: initialFromDate,
|
date: initialFromDate,
|
||||||
time: this.schedule[0].time,
|
time: this.schedule[0].time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.originWaypoint.lon,
|
||||||
lat: this.waypoints[0].lat,
|
lat: this.originWaypoint.lat,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.frequency,
|
this.frequency,
|
||||||
|
@ -151,8 +156,8 @@ export class MatchQuery extends QueryBase {
|
||||||
date: this.fromDate,
|
date: this.fromDate,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.originWaypoint.lon,
|
||||||
lat: this.waypoints[0].lat,
|
lat: this.originWaypoint.lat,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.frequency,
|
this.frequency,
|
||||||
|
@ -162,8 +167,8 @@ export class MatchQuery extends QueryBase {
|
||||||
date: this.fromDate,
|
date: this.fromDate,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
coordinates: {
|
coordinates: {
|
||||||
lon: this.waypoints[0].lon,
|
lon: this.originWaypoint.lon,
|
||||||
lat: this.waypoints[0].lat,
|
lat: this.originWaypoint.lat,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
this.frequency,
|
this.frequency,
|
||||||
|
@ -177,11 +182,21 @@ export class MatchQuery extends QueryBase {
|
||||||
const roles: Role[] = [];
|
const roles: Role[] = [];
|
||||||
if (this.driver) roles.push(Role.DRIVER);
|
if (this.driver) roles.push(Role.DRIVER);
|
||||||
if (this.passenger) roles.push(Role.PASSENGER);
|
if (this.passenger) roles.push(Role.PASSENGER);
|
||||||
const pathCreator: PathCreator = new PathCreator(roles, this.waypoints);
|
const pathCreator: PathCreator = new PathCreator(
|
||||||
|
roles,
|
||||||
|
this.waypoints.map(
|
||||||
|
(waypoint: Waypoint) =>
|
||||||
|
new WaypointValueObject({
|
||||||
|
position: waypoint.position,
|
||||||
|
lon: waypoint.lon,
|
||||||
|
lat: waypoint.lat,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
(
|
(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pathCreator.getPaths().map(async (path: Path) => ({
|
pathCreator.getBasePaths().map(async (path: Path) => ({
|
||||||
type: path.type,
|
type: path.type,
|
||||||
route: await routeProvider.getBasic(path.waypoints),
|
route: await routeProvider.getBasic(path.waypoints),
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { CandidateProps, CreateCandidateProps } from './candidate.types';
|
import { CandidateProps, CreateCandidateProps } from './candidate.types';
|
||||||
|
import { WayStepProps } from './value-objects/waystep.value-object';
|
||||||
|
|
||||||
export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
protected readonly _id: AggregateID;
|
protected readonly _id: AggregateID;
|
||||||
|
@ -9,6 +10,10 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||||
return new CandidateEntity({ id: create.id, props });
|
return new CandidateEntity({ id: create.id, props });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setWaySteps = (waySteps: WayStepProps[]): void => {
|
||||||
|
this.props.waySteps = waySteps;
|
||||||
|
};
|
||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,29 @@
|
||||||
import { Role } from './ad.types';
|
import { Role } from './ad.types';
|
||||||
|
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||||
|
import { WayStepProps } from './value-objects/waystep.value-object';
|
||||||
|
|
||||||
// All properties that a Candidate has
|
// All properties that a Candidate has
|
||||||
export interface CandidateProps {
|
export interface CandidateProps {
|
||||||
role: Role;
|
role: Role;
|
||||||
waypoints: Waypoint[];
|
waypoints: WaypointProps[]; // waypoints of the original Ad
|
||||||
|
waySteps?: WayStepProps[]; // carpool path for the crew (driver + passenger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties that are needed for a Candidate creation
|
// Properties that are needed for a Candidate creation
|
||||||
export interface CreateCandidateProps {
|
export interface CreateCandidateProps {
|
||||||
id: string;
|
id: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
waypoints: Waypoint[];
|
waypoints: WaypointProps[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Waypoint = {
|
export type Spacetime = {
|
||||||
lon: number;
|
duration: number;
|
||||||
lat: number;
|
distance?: number;
|
||||||
position: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum Target {
|
||||||
|
START = 'START',
|
||||||
|
INTERMEDIATE = 'INTERMEDIATE',
|
||||||
|
FINISH = 'FINISH',
|
||||||
|
NEUTRAL = 'NEUTRAL',
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Route } from '@modules/geography/core/domain/route.types';
|
import { Route } from '@modules/geography/core/domain/route.types';
|
||||||
import { Role } from './ad.types';
|
import { Role } from './ad.types';
|
||||||
import { Waypoint } from './candidate.types';
|
import { Waypoint } from './value-objects/waypoint.value-object';
|
||||||
|
|
||||||
export class PathCreator {
|
export class PathCreator {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -8,7 +8,7 @@ export class PathCreator {
|
||||||
private readonly waypoints: Waypoint[],
|
private readonly waypoints: Waypoint[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public getPaths = (): Path[] => {
|
public getBasePaths = (): Path[] => {
|
||||||
const paths: Path[] = [];
|
const paths: Path[] = [];
|
||||||
if (
|
if (
|
||||||
this.roles.includes(Role.DRIVER) &&
|
this.roles.includes(Role.DRIVER) &&
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { ValueObject } from '@mobicoop/ddd-library';
|
||||||
|
import { Role } from '../ad.types';
|
||||||
|
import { Target } from '../candidate.types';
|
||||||
|
|
||||||
|
/** Note:
|
||||||
|
* Value Objects with multiple properties can contain
|
||||||
|
* other Value Objects inside if needed.
|
||||||
|
* */
|
||||||
|
|
||||||
|
export interface ActorProps {
|
||||||
|
role: Role;
|
||||||
|
target: Target;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Actor extends ValueObject<ActorProps> {
|
||||||
|
get role(): Role {
|
||||||
|
return this.props.role;
|
||||||
|
}
|
||||||
|
|
||||||
|
get target(): Target {
|
||||||
|
return this.props.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validate(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,13 @@
|
||||||
import {
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
ArgumentInvalidException,
|
import { PointProps } from './point.value-object';
|
||||||
ArgumentOutOfRangeException,
|
|
||||||
ValueObject,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
* other Value Objects inside if needed.
|
* other Value Objects inside if needed.
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface WaypointProps {
|
export interface WaypointProps extends PointProps {
|
||||||
position: number;
|
position: number;
|
||||||
lon: number;
|
|
||||||
lat: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Waypoint extends ValueObject<WaypointProps> {
|
export class Waypoint extends ValueObject<WaypointProps> {
|
||||||
|
@ -33,9 +28,5 @@ export class Waypoint extends ValueObject<WaypointProps> {
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
'position must be greater than or equal to 0',
|
'position must be greater than or equal to 0',
|
||||||
);
|
);
|
||||||
if (props.lon > 180 || props.lon < -180)
|
|
||||||
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
|
|
||||||
if (props.lat > 90 || props.lat < -90)
|
|
||||||
throw new ArgumentOutOfRangeException('lat must be between -90 and 90');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
ArgumentOutOfRangeException,
|
||||||
|
ValueObject,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { WaypointProps } from './waypoint.value-object';
|
||||||
|
import { Actor } from './actor.value-object';
|
||||||
|
import { Role } from '../ad.types';
|
||||||
|
|
||||||
|
/** Note:
|
||||||
|
* Value Objects with multiple properties can contain
|
||||||
|
* other Value Objects inside if needed.
|
||||||
|
* */
|
||||||
|
|
||||||
|
export interface WayStepProps extends WaypointProps {
|
||||||
|
actors: Actor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WayStep extends ValueObject<WayStepProps> {
|
||||||
|
get position(): number {
|
||||||
|
return this.props.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lon(): number {
|
||||||
|
return this.props.lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lat(): number {
|
||||||
|
return this.props.lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
get actors(): Actor[] {
|
||||||
|
return this.props.actors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validate(props: WayStepProps): void {
|
||||||
|
if (props.actors.length <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException('at least one actor is required');
|
||||||
|
if (
|
||||||
|
props.actors.filter((actor: Actor) => actor.role == Role.DRIVER).length >
|
||||||
|
1
|
||||||
|
)
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
'a waystep can contain only one driver',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { Role } from './ad.types';
|
||||||
|
import { Target } from './candidate.types';
|
||||||
|
import { Actor } from './value-objects/actor.value-object';
|
||||||
|
import { Waypoint } from './value-objects/waypoint.value-object';
|
||||||
|
import { WayStep } from './value-objects/waystep.value-object';
|
||||||
|
|
||||||
|
export class WayStepsCreator {
|
||||||
|
constructor(
|
||||||
|
private readonly driverWaypoints: Waypoint[],
|
||||||
|
private readonly passengerWaypoints: Waypoint[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public getCrewCarpoolPath = (): WayStep[] => this._createPassengerWaysteps();
|
||||||
|
|
||||||
|
private _createPassengerWaysteps = (): WayStep[] => {
|
||||||
|
const waysteps: WayStep[] = [];
|
||||||
|
this.passengerWaypoints.forEach((passengerWaypoint: Waypoint) => {
|
||||||
|
const waystep: WayStep = new WayStep({
|
||||||
|
lon: passengerWaypoint.lon,
|
||||||
|
lat: passengerWaypoint.lat,
|
||||||
|
position: passengerWaypoint.position,
|
||||||
|
actors: [
|
||||||
|
new Actor({
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
target: this._getTarget(
|
||||||
|
passengerWaypoint.position,
|
||||||
|
this.passengerWaypoints,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
this.driverWaypoints.filter((driverWaypoint: Waypoint) =>
|
||||||
|
this._isSameWaypoint(driverWaypoint, passengerWaypoint),
|
||||||
|
).length > 0
|
||||||
|
) {
|
||||||
|
waystep.actors.push(
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.NEUTRAL,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
waysteps.push(waystep);
|
||||||
|
});
|
||||||
|
return waysteps;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _isSameWaypoint = (
|
||||||
|
waypoint1: Waypoint,
|
||||||
|
waypoint2: Waypoint,
|
||||||
|
): boolean =>
|
||||||
|
waypoint1.lon === waypoint2.lon && waypoint1.lat === waypoint2.lat;
|
||||||
|
|
||||||
|
private _getTarget = (position: number, waypoints: Waypoint[]): Target =>
|
||||||
|
position == 0
|
||||||
|
? Target.START
|
||||||
|
: position ==
|
||||||
|
Math.max(...waypoints.map((waypoint: Waypoint) => waypoint.position))
|
||||||
|
? Target.FINISH
|
||||||
|
: Target.INTERMEDIATE;
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { RouteProviderPort } from '../core/application/ports/route-provider.port';
|
import { RouteProviderPort } from '../core/application/ports/route-provider.port';
|
||||||
import { Waypoint } from '../core/application/types/waypoint.type';
|
|
||||||
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
|
import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port';
|
||||||
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens';
|
import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens';
|
||||||
import { Route } from '@modules/geography/core/domain/route.types';
|
import { Route, Waypoint } from '@modules/geography/core/domain/route.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouteProvider implements RouteProviderPort {
|
export class RouteProvider implements RouteProviderPort {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
|
|
||||||
|
describe('Actor value object', () => {
|
||||||
|
it('should create an actor value object', () => {
|
||||||
|
const actorVO = new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.START,
|
||||||
|
});
|
||||||
|
expect(actorVO.role).toBe(Role.DRIVER);
|
||||||
|
expect(actorVO.target).toBe(Target.START);
|
||||||
|
});
|
||||||
|
});
|
|
@ -40,6 +40,16 @@ const mockAdRepository = {
|
||||||
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
|
||||||
getProps: jest.fn().mockImplementation(() => ({
|
getProps: jest.fn().mockImplementation(() => ({
|
||||||
role: Role.DRIVER,
|
role: Role.DRIVER,
|
||||||
|
waypoints: [
|
||||||
|
{
|
||||||
|
lat: 48.68787,
|
||||||
|
lon: 6.165871,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lat: 48.97878,
|
||||||
|
lon: 2.45787,
|
||||||
|
},
|
||||||
|
],
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -63,13 +63,13 @@ const candidates: CandidateEntity[] = [
|
||||||
waypoints: [
|
waypoints: [
|
||||||
{
|
{
|
||||||
position: 0,
|
position: 0,
|
||||||
lat: 48.668487,
|
lat: 48.689445,
|
||||||
lon: 6.178457,
|
lon: 6.17651,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
position: 1,
|
position: 1,
|
||||||
lat: 48.897457,
|
lat: 48.8566,
|
||||||
lon: 2.3688487,
|
lon: 2.3522,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
import { Role } from '@modules/ad/core/domain/ad.types';
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { Waypoint } from '@modules/ad/core/domain/candidate.types';
|
|
||||||
import {
|
import {
|
||||||
Path,
|
Path,
|
||||||
PathCreator,
|
PathCreator,
|
||||||
PathType,
|
PathType,
|
||||||
} from '@modules/ad/core/domain/patch-creator.service';
|
} from '@modules/ad/core/domain/path-creator.service';
|
||||||
|
import { Waypoint } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
const originWaypoint: Waypoint = {
|
const originWaypoint: Waypoint = new Waypoint({
|
||||||
position: 0,
|
position: 0,
|
||||||
lat: 48.689445,
|
lat: 48.689445,
|
||||||
lon: 6.17651,
|
lon: 6.17651,
|
||||||
};
|
});
|
||||||
const destinationWaypoint: Waypoint = {
|
const destinationWaypoint: Waypoint = new Waypoint({
|
||||||
position: 1,
|
position: 1,
|
||||||
lat: 48.8566,
|
lat: 48.8566,
|
||||||
lon: 2.3522,
|
lon: 2.3522,
|
||||||
};
|
});
|
||||||
const intermediateWaypoint: Waypoint = {
|
const intermediateWaypoint: Waypoint = new Waypoint({
|
||||||
position: 1,
|
position: 1,
|
||||||
lat: 48.74488,
|
lat: 48.74488,
|
||||||
lon: 4.8972,
|
lon: 4.8972,
|
||||||
};
|
});
|
||||||
|
const destinationWaypointWithIntermediateWaypoint: Waypoint = new Waypoint({
|
||||||
|
position: 2,
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
});
|
||||||
|
|
||||||
describe('Path Creator Service', () => {
|
describe('Path Creator Service', () => {
|
||||||
it('should create a path for a driver only', () => {
|
it('should create a path for a driver only', () => {
|
||||||
|
@ -28,7 +33,7 @@ describe('Path Creator Service', () => {
|
||||||
[Role.DRIVER],
|
[Role.DRIVER],
|
||||||
[originWaypoint, destinationWaypoint],
|
[originWaypoint, destinationWaypoint],
|
||||||
);
|
);
|
||||||
const paths: Path[] = pathCreator.getPaths();
|
const paths: Path[] = pathCreator.getBasePaths();
|
||||||
expect(paths).toHaveLength(1);
|
expect(paths).toHaveLength(1);
|
||||||
expect(paths[0].type).toBe(PathType.DRIVER);
|
expect(paths[0].type).toBe(PathType.DRIVER);
|
||||||
});
|
});
|
||||||
|
@ -37,7 +42,7 @@ describe('Path Creator Service', () => {
|
||||||
[Role.PASSENGER],
|
[Role.PASSENGER],
|
||||||
[originWaypoint, destinationWaypoint],
|
[originWaypoint, destinationWaypoint],
|
||||||
);
|
);
|
||||||
const paths: Path[] = pathCreator.getPaths();
|
const paths: Path[] = pathCreator.getBasePaths();
|
||||||
expect(paths).toHaveLength(1);
|
expect(paths).toHaveLength(1);
|
||||||
expect(paths[0].type).toBe(PathType.PASSENGER);
|
expect(paths[0].type).toBe(PathType.PASSENGER);
|
||||||
});
|
});
|
||||||
|
@ -46,7 +51,7 @@ describe('Path Creator Service', () => {
|
||||||
[Role.DRIVER, Role.PASSENGER],
|
[Role.DRIVER, Role.PASSENGER],
|
||||||
[originWaypoint, destinationWaypoint],
|
[originWaypoint, destinationWaypoint],
|
||||||
);
|
);
|
||||||
const paths: Path[] = pathCreator.getPaths();
|
const paths: Path[] = pathCreator.getBasePaths();
|
||||||
expect(paths).toHaveLength(1);
|
expect(paths).toHaveLength(1);
|
||||||
expect(paths[0].type).toBe(PathType.GENERIC);
|
expect(paths[0].type).toBe(PathType.GENERIC);
|
||||||
});
|
});
|
||||||
|
@ -56,10 +61,10 @@ describe('Path Creator Service', () => {
|
||||||
[
|
[
|
||||||
originWaypoint,
|
originWaypoint,
|
||||||
intermediateWaypoint,
|
intermediateWaypoint,
|
||||||
{ ...destinationWaypoint, position: 2 },
|
destinationWaypointWithIntermediateWaypoint,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
const paths: Path[] = pathCreator.getPaths();
|
const paths: Path[] = pathCreator.getBasePaths();
|
||||||
expect(paths).toHaveLength(2);
|
expect(paths).toHaveLength(2);
|
||||||
expect(
|
expect(
|
||||||
paths.filter((path: Path) => path.type == PathType.DRIVER),
|
paths.filter((path: Path) => path.type == PathType.DRIVER),
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
|
||||||
|
import { Role } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||||
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
|
import { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object';
|
||||||
|
|
||||||
|
describe('WayStep value object', () => {
|
||||||
|
it('should create a waystep value object', () => {
|
||||||
|
const wayStepVO = new WayStep({
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
position: 0,
|
||||||
|
actors: [
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.NEUTRAL,
|
||||||
|
}),
|
||||||
|
new Actor({
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
target: Target.START,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(wayStepVO.position).toBe(0);
|
||||||
|
expect(wayStepVO.lon).toBe(6.17651);
|
||||||
|
expect(wayStepVO.lat).toBe(48.689445);
|
||||||
|
expect(wayStepVO.actors).toHaveLength(2);
|
||||||
|
});
|
||||||
|
it('should throw an exception if actors is empty', () => {
|
||||||
|
try {
|
||||||
|
new WayStep({
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
position: 0,
|
||||||
|
actors: [],
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should throw an exception if actors contains more than one driver', () => {
|
||||||
|
try {
|
||||||
|
new WayStep({
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
position: 0,
|
||||||
|
actors: [
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.NEUTRAL,
|
||||||
|
}),
|
||||||
|
new Actor({
|
||||||
|
role: Role.DRIVER,
|
||||||
|
target: Target.START,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(ArgumentOutOfRangeException);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
import { QueryBase } from '@mobicoop/ddd-library';
|
import { QueryBase } from '@mobicoop/ddd-library';
|
||||||
import { Waypoint } from '@modules/geography/core/domain/route.types';
|
|
||||||
import { GeorouterSettings } from '../../types/georouter-settings.type';
|
import { GeorouterSettings } from '../../types/georouter-settings.type';
|
||||||
|
import { Waypoint } from '@modules/geography/core/domain/route.types';
|
||||||
|
|
||||||
export class GetRouteQuery extends QueryBase {
|
export class GetRouteQuery extends QueryBase {
|
||||||
readonly waypoints: Waypoint[];
|
readonly waypoints: Waypoint[];
|
||||||
|
|
|
@ -20,6 +20,7 @@ export interface CreateRouteProps {
|
||||||
georouterSettings: GeorouterSettings;
|
georouterSettings: GeorouterSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Types used outside the domain
|
||||||
export type Route = {
|
export type Route = {
|
||||||
distance: number;
|
distance: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
|
|
@ -1,30 +1,17 @@
|
||||||
import {
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
ArgumentInvalidException,
|
import { PointProps } from './point.value-object';
|
||||||
ArgumentOutOfRangeException,
|
|
||||||
ValueObject,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
* other Value Objects inside if needed.
|
* other Value Objects inside if needed.
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface StepProps {
|
export interface StepProps extends PointProps {
|
||||||
lon: number;
|
|
||||||
lat: number;
|
|
||||||
duration: number;
|
duration: number;
|
||||||
distance: number;
|
distance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Step extends ValueObject<StepProps> {
|
export class Step extends ValueObject<StepProps> {
|
||||||
get lon(): number {
|
|
||||||
return this.props.lon;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lat(): number {
|
|
||||||
return this.props.lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration(): number {
|
get duration(): number {
|
||||||
return this.props.duration;
|
return this.props.duration;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +20,14 @@ export class Step extends ValueObject<StepProps> {
|
||||||
return this.props.distance;
|
return this.props.distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get lon(): number {
|
||||||
|
return this.props.lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lat(): number {
|
||||||
|
return this.props.lat;
|
||||||
|
}
|
||||||
|
|
||||||
protected validate(props: StepProps): void {
|
protected validate(props: StepProps): void {
|
||||||
if (props.duration < 0)
|
if (props.duration < 0)
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
|
@ -42,9 +37,5 @@ export class Step extends ValueObject<StepProps> {
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
'distance must be greater than or equal to 0',
|
'distance must be greater than or equal to 0',
|
||||||
);
|
);
|
||||||
if (props.lon > 180 || props.lon < -180)
|
|
||||||
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
|
|
||||||
if (props.lat > 90 || props.lat < -90)
|
|
||||||
throw new ArgumentOutOfRangeException('lat must be between -90 and 90');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
import {
|
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
|
||||||
ArgumentInvalidException,
|
import { PointProps } from './point.value-object';
|
||||||
ArgumentOutOfRangeException,
|
|
||||||
ValueObject,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
|
|
||||||
/** Note:
|
/** Note:
|
||||||
* Value Objects with multiple properties can contain
|
* Value Objects with multiple properties can contain
|
||||||
* other Value Objects inside if needed.
|
* other Value Objects inside if needed.
|
||||||
* */
|
* */
|
||||||
|
|
||||||
export interface WaypointProps {
|
export interface WaypointProps extends PointProps {
|
||||||
position: number;
|
position: number;
|
||||||
lon: number;
|
|
||||||
lat: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Waypoint extends ValueObject<WaypointProps> {
|
export class Waypoint extends ValueObject<WaypointProps> {
|
||||||
|
@ -33,9 +28,5 @@ export class Waypoint extends ValueObject<WaypointProps> {
|
||||||
throw new ArgumentInvalidException(
|
throw new ArgumentInvalidException(
|
||||||
'position must be greater than or equal to 0',
|
'position must be greater than or equal to 0',
|
||||||
);
|
);
|
||||||
if (props.lon > 180 || props.lon < -180)
|
|
||||||
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');
|
|
||||||
if (props.lat > 90 || props.lat < -90)
|
|
||||||
throw new ArgumentOutOfRangeException('lat must be between -90 and 90');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue