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 { AD_DIRECTION_ENCODER } from './ad.di-tokens';
 | 
			
		||||
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:
 | 
			
		||||
| 
						 | 
				
			
			@ -112,13 +111,12 @@ export class AdMapper
 | 
			
		|||
              margin: scheduleItem.margin,
 | 
			
		||||
            }),
 | 
			
		||||
        ),
 | 
			
		||||
        waypoints: this.directionEncoder.decode(record.waypoints).map(
 | 
			
		||||
          (coordinates, index) =>
 | 
			
		||||
            new Waypoint({
 | 
			
		||||
              position: index,
 | 
			
		||||
              ...coordinates,
 | 
			
		||||
            }),
 | 
			
		||||
        ),
 | 
			
		||||
        waypoints: this.directionEncoder
 | 
			
		||||
          .decode(record.waypoints)
 | 
			
		||||
          .map((coordinates, index) => ({
 | 
			
		||||
            position: index,
 | 
			
		||||
            ...coordinates,
 | 
			
		||||
          })),
 | 
			
		||||
        fwdAzimuth: record.fwdAzimuth,
 | 
			
		||||
        backAzimuth: record.backAzimuth,
 | 
			
		||||
        points: [],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,8 +13,10 @@ import {
 | 
			
		|||
  PathCreator,
 | 
			
		||||
  PathType,
 | 
			
		||||
  TypedRoute,
 | 
			
		||||
} from '@modules/ad/core/domain/patch-creator.service';
 | 
			
		||||
} from '@modules/ad/core/domain/path-creator.service';
 | 
			
		||||
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)
 | 
			
		||||
export class CreateAdService implements ICommandHandler {
 | 
			
		||||
| 
						 | 
				
			
			@ -29,11 +31,21 @@ export class CreateAdService implements ICommandHandler {
 | 
			
		|||
    const roles: Role[] = [];
 | 
			
		||||
    if (command.driver) roles.push(Role.DRIVER);
 | 
			
		||||
    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[];
 | 
			
		||||
    try {
 | 
			
		||||
      typedRoutes = await Promise.all(
 | 
			
		||||
        pathCreator.getPaths().map(async (path: Path) => ({
 | 
			
		||||
        pathCreator.getBasePaths().map(async (path: Path) => ({
 | 
			
		||||
          type: path.type,
 | 
			
		||||
          route: await this.routeProvider.getBasic(path.waypoints),
 | 
			
		||||
        })),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
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 {
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,12 @@
 | 
			
		|||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +14,53 @@ import { Completer } from './completer.abstract';
 | 
			
		|||
export class PassengerOrientedWaypointsCompleter extends Completer {
 | 
			
		||||
  complete = async (
 | 
			
		||||
    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[]> => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,8 @@ import {
 | 
			
		|||
  PathCreator,
 | 
			
		||||
  PathType,
 | 
			
		||||
  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 {
 | 
			
		||||
  driver?: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +38,7 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
  driverRoute?: Route;
 | 
			
		||||
  passengerRoute?: Route;
 | 
			
		||||
  backAzimuth?: number;
 | 
			
		||||
  private readonly originWaypoint: Waypoint;
 | 
			
		||||
 | 
			
		||||
  constructor(props: MatchRequestDto) {
 | 
			
		||||
    super();
 | 
			
		||||
| 
						 | 
				
			
			@ -60,6 +62,9 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
    this.maxDetourDurationRatio = props.maxDetourDurationRatio;
 | 
			
		||||
    this.page = props.page ?? 1;
 | 
			
		||||
    this.perPage = props.perPage ?? 10;
 | 
			
		||||
    this.originWaypoint = this.waypoints.filter(
 | 
			
		||||
      (waypoint: Waypoint) => waypoint.position == 0,
 | 
			
		||||
    )[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => {
 | 
			
		||||
| 
						 | 
				
			
			@ -126,8 +131,8 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
        date: initialFromDate,
 | 
			
		||||
        time: this.schedule[0].time,
 | 
			
		||||
        coordinates: {
 | 
			
		||||
          lon: this.waypoints[0].lon,
 | 
			
		||||
          lat: this.waypoints[0].lat,
 | 
			
		||||
          lon: this.originWaypoint.lon,
 | 
			
		||||
          lat: this.originWaypoint.lat,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      this.frequency,
 | 
			
		||||
| 
						 | 
				
			
			@ -138,8 +143,8 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
        date: initialFromDate,
 | 
			
		||||
        time: this.schedule[0].time,
 | 
			
		||||
        coordinates: {
 | 
			
		||||
          lon: this.waypoints[0].lon,
 | 
			
		||||
          lat: this.waypoints[0].lat,
 | 
			
		||||
          lon: this.originWaypoint.lon,
 | 
			
		||||
          lat: this.originWaypoint.lat,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      this.frequency,
 | 
			
		||||
| 
						 | 
				
			
			@ -151,8 +156,8 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
          date: this.fromDate,
 | 
			
		||||
          time: scheduleItem.time,
 | 
			
		||||
          coordinates: {
 | 
			
		||||
            lon: this.waypoints[0].lon,
 | 
			
		||||
            lat: this.waypoints[0].lat,
 | 
			
		||||
            lon: this.originWaypoint.lon,
 | 
			
		||||
            lat: this.originWaypoint.lat,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        this.frequency,
 | 
			
		||||
| 
						 | 
				
			
			@ -162,8 +167,8 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
          date: this.fromDate,
 | 
			
		||||
          time: scheduleItem.time,
 | 
			
		||||
          coordinates: {
 | 
			
		||||
            lon: this.waypoints[0].lon,
 | 
			
		||||
            lat: this.waypoints[0].lat,
 | 
			
		||||
            lon: this.originWaypoint.lon,
 | 
			
		||||
            lat: this.originWaypoint.lat,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        this.frequency,
 | 
			
		||||
| 
						 | 
				
			
			@ -177,11 +182,21 @@ export class MatchQuery extends QueryBase {
 | 
			
		|||
    const roles: Role[] = [];
 | 
			
		||||
    if (this.driver) roles.push(Role.DRIVER);
 | 
			
		||||
    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 {
 | 
			
		||||
      (
 | 
			
		||||
        await Promise.all(
 | 
			
		||||
          pathCreator.getPaths().map(async (path: Path) => ({
 | 
			
		||||
          pathCreator.getBasePaths().map(async (path: Path) => ({
 | 
			
		||||
            type: path.type,
 | 
			
		||||
            route: await routeProvider.getBasic(path.waypoints),
 | 
			
		||||
          })),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
 | 
			
		||||
import { CandidateProps, CreateCandidateProps } from './candidate.types';
 | 
			
		||||
import { WayStepProps } from './value-objects/waystep.value-object';
 | 
			
		||||
 | 
			
		||||
export class CandidateEntity extends AggregateRoot<CandidateProps> {
 | 
			
		||||
  protected readonly _id: AggregateID;
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +10,10 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
 | 
			
		|||
    return new CandidateEntity({ id: create.id, props });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  setWaySteps = (waySteps: WayStepProps[]): void => {
 | 
			
		||||
    this.props.waySteps = waySteps;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  validate(): void {
 | 
			
		||||
    // 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 { WaypointProps } from './value-objects/waypoint.value-object';
 | 
			
		||||
import { WayStepProps } from './value-objects/waystep.value-object';
 | 
			
		||||
 | 
			
		||||
// All properties that a Candidate has
 | 
			
		||||
export interface CandidateProps {
 | 
			
		||||
  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
 | 
			
		||||
export interface CreateCandidateProps {
 | 
			
		||||
  id: string;
 | 
			
		||||
  role: Role;
 | 
			
		||||
  waypoints: Waypoint[];
 | 
			
		||||
  waypoints: WaypointProps[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Waypoint = {
 | 
			
		||||
  lon: number;
 | 
			
		||||
  lat: number;
 | 
			
		||||
  position: number;
 | 
			
		||||
export type Spacetime = {
 | 
			
		||||
  duration: number;
 | 
			
		||||
  distance?: 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 { Role } from './ad.types';
 | 
			
		||||
import { Waypoint } from './candidate.types';
 | 
			
		||||
import { Waypoint } from './value-objects/waypoint.value-object';
 | 
			
		||||
 | 
			
		||||
export class PathCreator {
 | 
			
		||||
  constructor(
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ export class PathCreator {
 | 
			
		|||
    private readonly waypoints: Waypoint[],
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  public getPaths = (): Path[] => {
 | 
			
		||||
  public getBasePaths = (): Path[] => {
 | 
			
		||||
    const paths: Path[] = [];
 | 
			
		||||
    if (
 | 
			
		||||
      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 {
 | 
			
		||||
  ArgumentInvalidException,
 | 
			
		||||
  ArgumentOutOfRangeException,
 | 
			
		||||
  ValueObject,
 | 
			
		||||
} from '@mobicoop/ddd-library';
 | 
			
		||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
 | 
			
		||||
import { PointProps } from './point.value-object';
 | 
			
		||||
 | 
			
		||||
/** Note:
 | 
			
		||||
 * Value Objects with multiple properties can contain
 | 
			
		||||
 * other Value Objects inside if needed.
 | 
			
		||||
 * */
 | 
			
		||||
 | 
			
		||||
export interface WaypointProps {
 | 
			
		||||
export interface WaypointProps extends PointProps {
 | 
			
		||||
  position: number;
 | 
			
		||||
  lon: number;
 | 
			
		||||
  lat: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Waypoint extends ValueObject<WaypointProps> {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,9 +28,5 @@ export class Waypoint extends ValueObject<WaypointProps> {
 | 
			
		|||
      throw new ArgumentInvalidException(
 | 
			
		||||
        '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 { 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 { 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()
 | 
			
		||||
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',
 | 
			
		||||
      getProps: jest.fn().mockImplementation(() => ({
 | 
			
		||||
        role: Role.DRIVER,
 | 
			
		||||
        waypoints: [
 | 
			
		||||
          {
 | 
			
		||||
            lat: 48.68787,
 | 
			
		||||
            lon: 6.165871,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            lat: 48.97878,
 | 
			
		||||
            lon: 2.45787,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      })),
 | 
			
		||||
    },
 | 
			
		||||
  ]),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,13 +63,13 @@ const candidates: CandidateEntity[] = [
 | 
			
		|||
    waypoints: [
 | 
			
		||||
      {
 | 
			
		||||
        position: 0,
 | 
			
		||||
        lat: 48.668487,
 | 
			
		||||
        lon: 6.178457,
 | 
			
		||||
        lat: 48.689445,
 | 
			
		||||
        lon: 6.17651,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        position: 1,
 | 
			
		||||
        lat: 48.897457,
 | 
			
		||||
        lon: 2.3688487,
 | 
			
		||||
        lat: 48.8566,
 | 
			
		||||
        lon: 2.3522,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  }),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,26 +1,31 @@
 | 
			
		|||
import { Role } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { Waypoint } from '@modules/ad/core/domain/candidate.types';
 | 
			
		||||
import {
 | 
			
		||||
  Path,
 | 
			
		||||
  PathCreator,
 | 
			
		||||
  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,
 | 
			
		||||
  lat: 48.689445,
 | 
			
		||||
  lon: 6.17651,
 | 
			
		||||
};
 | 
			
		||||
const destinationWaypoint: Waypoint = {
 | 
			
		||||
});
 | 
			
		||||
const destinationWaypoint: Waypoint = new Waypoint({
 | 
			
		||||
  position: 1,
 | 
			
		||||
  lat: 48.8566,
 | 
			
		||||
  lon: 2.3522,
 | 
			
		||||
};
 | 
			
		||||
const intermediateWaypoint: Waypoint = {
 | 
			
		||||
});
 | 
			
		||||
const intermediateWaypoint: Waypoint = new Waypoint({
 | 
			
		||||
  position: 1,
 | 
			
		||||
  lat: 48.74488,
 | 
			
		||||
  lon: 4.8972,
 | 
			
		||||
};
 | 
			
		||||
});
 | 
			
		||||
const destinationWaypointWithIntermediateWaypoint: Waypoint = new Waypoint({
 | 
			
		||||
  position: 2,
 | 
			
		||||
  lat: 48.8566,
 | 
			
		||||
  lon: 2.3522,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('Path Creator Service', () => {
 | 
			
		||||
  it('should create a path for a driver only', () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +33,7 @@ describe('Path Creator Service', () => {
 | 
			
		|||
      [Role.DRIVER],
 | 
			
		||||
      [originWaypoint, destinationWaypoint],
 | 
			
		||||
    );
 | 
			
		||||
    const paths: Path[] = pathCreator.getPaths();
 | 
			
		||||
    const paths: Path[] = pathCreator.getBasePaths();
 | 
			
		||||
    expect(paths).toHaveLength(1);
 | 
			
		||||
    expect(paths[0].type).toBe(PathType.DRIVER);
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +42,7 @@ describe('Path Creator Service', () => {
 | 
			
		|||
      [Role.PASSENGER],
 | 
			
		||||
      [originWaypoint, destinationWaypoint],
 | 
			
		||||
    );
 | 
			
		||||
    const paths: Path[] = pathCreator.getPaths();
 | 
			
		||||
    const paths: Path[] = pathCreator.getBasePaths();
 | 
			
		||||
    expect(paths).toHaveLength(1);
 | 
			
		||||
    expect(paths[0].type).toBe(PathType.PASSENGER);
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +51,7 @@ describe('Path Creator Service', () => {
 | 
			
		|||
      [Role.DRIVER, Role.PASSENGER],
 | 
			
		||||
      [originWaypoint, destinationWaypoint],
 | 
			
		||||
    );
 | 
			
		||||
    const paths: Path[] = pathCreator.getPaths();
 | 
			
		||||
    const paths: Path[] = pathCreator.getBasePaths();
 | 
			
		||||
    expect(paths).toHaveLength(1);
 | 
			
		||||
    expect(paths[0].type).toBe(PathType.GENERIC);
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -56,10 +61,10 @@ describe('Path Creator Service', () => {
 | 
			
		|||
      [
 | 
			
		||||
        originWaypoint,
 | 
			
		||||
        intermediateWaypoint,
 | 
			
		||||
        { ...destinationWaypoint, position: 2 },
 | 
			
		||||
        destinationWaypointWithIntermediateWaypoint,
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    const paths: Path[] = pathCreator.getPaths();
 | 
			
		||||
    const paths: Path[] = pathCreator.getBasePaths();
 | 
			
		||||
    expect(paths).toHaveLength(2);
 | 
			
		||||
    expect(
 | 
			
		||||
      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 { Waypoint } from '@modules/geography/core/domain/route.types';
 | 
			
		||||
import { GeorouterSettings } from '../../types/georouter-settings.type';
 | 
			
		||||
import { Waypoint } from '@modules/geography/core/domain/route.types';
 | 
			
		||||
 | 
			
		||||
export class GetRouteQuery extends QueryBase {
 | 
			
		||||
  readonly waypoints: Waypoint[];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ export interface CreateRouteProps {
 | 
			
		|||
  georouterSettings: GeorouterSettings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Types used outside the domain
 | 
			
		||||
export type Route = {
 | 
			
		||||
  distance: number;
 | 
			
		||||
  duration: number;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,17 @@
 | 
			
		|||
import {
 | 
			
		||||
  ArgumentInvalidException,
 | 
			
		||||
  ArgumentOutOfRangeException,
 | 
			
		||||
  ValueObject,
 | 
			
		||||
} from '@mobicoop/ddd-library';
 | 
			
		||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
 | 
			
		||||
import { PointProps } from './point.value-object';
 | 
			
		||||
 | 
			
		||||
/** Note:
 | 
			
		||||
 * Value Objects with multiple properties can contain
 | 
			
		||||
 * other Value Objects inside if needed.
 | 
			
		||||
 * */
 | 
			
		||||
 | 
			
		||||
export interface StepProps {
 | 
			
		||||
  lon: number;
 | 
			
		||||
  lat: number;
 | 
			
		||||
export interface StepProps extends PointProps {
 | 
			
		||||
  duration: number;
 | 
			
		||||
  distance: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Step extends ValueObject<StepProps> {
 | 
			
		||||
  get lon(): number {
 | 
			
		||||
    return this.props.lon;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get lat(): number {
 | 
			
		||||
    return this.props.lat;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get duration(): number {
 | 
			
		||||
    return this.props.duration;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +20,14 @@ export class Step extends ValueObject<StepProps> {
 | 
			
		|||
    return this.props.distance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get lon(): number {
 | 
			
		||||
    return this.props.lon;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get lat(): number {
 | 
			
		||||
    return this.props.lat;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected validate(props: StepProps): void {
 | 
			
		||||
    if (props.duration < 0)
 | 
			
		||||
      throw new ArgumentInvalidException(
 | 
			
		||||
| 
						 | 
				
			
			@ -42,9 +37,5 @@ export class Step extends ValueObject<StepProps> {
 | 
			
		|||
      throw new ArgumentInvalidException(
 | 
			
		||||
        '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 {
 | 
			
		||||
  ArgumentInvalidException,
 | 
			
		||||
  ArgumentOutOfRangeException,
 | 
			
		||||
  ValueObject,
 | 
			
		||||
} from '@mobicoop/ddd-library';
 | 
			
		||||
import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library';
 | 
			
		||||
import { PointProps } from './point.value-object';
 | 
			
		||||
 | 
			
		||||
/** Note:
 | 
			
		||||
 * Value Objects with multiple properties can contain
 | 
			
		||||
 * other Value Objects inside if needed.
 | 
			
		||||
 * */
 | 
			
		||||
 | 
			
		||||
export interface WaypointProps {
 | 
			
		||||
export interface WaypointProps extends PointProps {
 | 
			
		||||
  position: number;
 | 
			
		||||
  lon: number;
 | 
			
		||||
  lat: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Waypoint extends ValueObject<WaypointProps> {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,9 +28,5 @@ export class Waypoint extends ValueObject<WaypointProps> {
 | 
			
		|||
      throw new ArgumentInvalidException(
 | 
			
		||||
        '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