├── .env.dist ├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── README.md ├── nest-cli.json ├── package.json ├── src ├── app.module.ts ├── common │ └── base.entity.ts ├── config │ └── app-properties.config.ts ├── controllers │ ├── awards-account.controller.ts │ ├── car-type.controller.ts │ ├── claim.controller.ts │ ├── client.controller.ts │ ├── contract.controller.ts │ ├── driver-report.controller.ts │ ├── driver-session.controller.ts │ ├── driver-tracking.controller.ts │ ├── driver.controller.ts │ ├── transit-analyzer.controller.ts │ └── transit.controller.ts ├── dto │ ├── address.dto.ts │ ├── analyzed-addresses.dto.ts │ ├── awards-account.dto.ts │ ├── car-type.dto.ts │ ├── claim.dto.ts │ ├── client.dto.ts │ ├── contract-attachment.dto.ts │ ├── contract.dto.ts │ ├── create-address.dto.ts │ ├── create-car-type.dto.ts │ ├── create-claim.dto.ts │ ├── create-client.dto.ts │ ├── create-contract-attachment.dto.ts │ ├── create-contract.dto.ts │ ├── create-driver-position.dto.ts │ ├── create-driver-session.dto.ts │ ├── create-driver.dto.ts │ ├── create-transit.dto.ts │ ├── driver-attribute.dto.ts │ ├── driver-position-v2.dto.ts │ ├── driver-position.dto.ts │ ├── driver-report.dto.ts │ ├── driver-session.dto.ts │ ├── driver.dto.ts │ └── transit.dto.ts ├── entity │ ├── address.entity.ts │ ├── awarded-miles.entity.ts │ ├── awards-account.entity.ts │ ├── car-type.entity.ts │ ├── claim-attachment.entity.ts │ ├── claim.entity.ts │ ├── client.entity.ts │ ├── contract-attachment.entity.ts │ ├── contract.entity.ts │ ├── driver-attribute.entity.ts │ ├── driver-fee.entity.ts │ ├── driver-position.entity.ts │ ├── driver-session.entity.ts │ ├── driver.entity.ts │ ├── invoice.entity.ts │ └── transit.entity.ts ├── main.ts ├── repository │ ├── address.repository.ts │ ├── awarded-miles.repository.ts │ ├── awards-account.repository.ts │ ├── car-type.repository.ts │ ├── claim-attachment.repository.ts │ ├── claim.repository.ts │ ├── client.repository.ts │ ├── contract-attachment.repository.ts │ ├── contract.repository.ts │ ├── driver-attribute.repository.ts │ ├── driver-fee.repository.ts │ ├── driver-position.repository.ts │ ├── driver-session.repository.ts │ ├── driver.repository.ts │ ├── invoice.repository.ts │ └── transit.repository.ts └── service │ ├── awards.service.ts │ ├── car-type.service.ts │ ├── claim-number-generator.service.ts │ ├── claim.service.ts │ ├── client-notification.service.ts │ ├── client.service.ts │ ├── contract.service.ts │ ├── distance-calculator.service.ts │ ├── driver-fee.service.ts │ ├── driver-notification.service.ts │ ├── driver-session.service.ts │ ├── driver-tracking.service.ts │ ├── driver.service.ts │ ├── geocoding.service.ts │ ├── invoice-generator.service.ts │ ├── transit-analyzer.service.ts │ └── transit.service.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.env.dist: -------------------------------------------------------------------------------- 1 | DATABASE_NAME= 2 | DATABASE_PORT= 3 | DATABASE_USERNAME= 4 | DATABASE_PASSWORD= 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-module-boundary-types': 'off', 21 | '@typescript-eslint/no-unused-vars': 2, 22 | '@typescript-eslint/explicit-member-accessibility': [ 23 | 2, 24 | { overrides: { constructors: 'off' } }, 25 | ], 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | .env 37 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn tsc 5 | yarn format 6 | yarn lint 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Fork of legacyfighter/cabs-java to Nest.js (TypeScript / Node) 2 | ____ 3 | 4 | # Rozwój kodu 5 | 6 | Kod będzie rozwijać się wraz z cotygodniową narracją szkoleniową. 7 | Zarówno pojawiać się w nim będą kolejne poprawki jak i odziedziczone po firmach partnerskich nowe moduły ;-) Jak to w prawdziwym legacy. 8 | 9 | # Przeglądanie kodu 10 | 11 | Poszczególne kroki refaktoryzacyjne najlepiej przeglądać używająć tagów. Każdy krok szkoleniowy, który opisany jest w odcinku Legacy Fighter posiada na końcu planszę z nazwą odpowiedniego taga. Porównać zmiany można robiąc diffa w stosunku do poprzedniego taga z narracji. 12 | 13 | 14 | _____________ 15 | 16 | 17 | 18 | ## Installation 19 | 20 | ```bash 21 | $ yarn install 22 | ``` 23 | 24 | ## Running the app 25 | 26 | ```bash 27 | # development 28 | $ yarn start 29 | 30 | # watch mode 31 | $ yarn start:dev 32 | 33 | # production mode 34 | $ yarn start:prod 35 | ``` 36 | 37 | ## Test 38 | 39 | ```bash 40 | # unit tests 41 | $ yarn test 42 | 43 | # e2e tests 44 | $ yarn test:e2e 45 | 46 | # test coverage 47 | $ yarn test:cov 48 | ``` 49 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cabs-ts", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json", 22 | "prepare": "husky install", 23 | "check-types": "tsc" 24 | }, 25 | "dependencies": { 26 | "@nestjs/common": "^8.0.0", 27 | "@nestjs/config": "^1.1.6", 28 | "@nestjs/core": "^8.0.0", 29 | "@nestjs/platform-express": "^8.0.0", 30 | "@nestjs/typeorm": "^8.0.3", 31 | "class-transformer": "^0.5.1", 32 | "class-validator": "^0.13.2", 33 | "dayjs": "^1.10.7", 34 | "lodash.orderby": "^4.6.0", 35 | "object-hash": "^2.2.0", 36 | "pg": "^8.7.1", 37 | "reflect-metadata": "^0.1.13", 38 | "rimraf": "^3.0.2", 39 | "rxjs": "^7.2.0", 40 | "typeorm": "^0.2.41" 41 | }, 42 | "devDependencies": { 43 | "@nestjs/cli": "^8.0.0", 44 | "@nestjs/schematics": "^8.0.0", 45 | "@nestjs/testing": "^8.0.0", 46 | "@types/express": "^4.17.13", 47 | "@types/jest": "27.0.2", 48 | "@types/lodash.orderby": "^4.6.6", 49 | "@types/node": "^16.0.0", 50 | "@types/object-hash": "^2.2.1", 51 | "@types/supertest": "^2.0.11", 52 | "@typescript-eslint/eslint-plugin": "^5.0.0", 53 | "@typescript-eslint/parser": "^5.0.0", 54 | "eslint": "^8.0.1", 55 | "eslint-config-prettier": "^8.3.0", 56 | "eslint-plugin-prettier": "^4.0.0", 57 | "husky": "^7.0.4", 58 | "jest": "^27.2.5", 59 | "prettier": "^2.3.2", 60 | "source-map-support": "^0.5.20", 61 | "supertest": "^6.1.3", 62 | "ts-jest": "^27.0.3", 63 | "ts-loader": "^9.2.3", 64 | "ts-node": "^10.0.0", 65 | "tsconfig-paths": "^3.10.1", 66 | "typescript": "^4.3.5" 67 | }, 68 | "jest": { 69 | "moduleFileExtensions": [ 70 | "js", 71 | "json", 72 | "ts" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": ".*\\.spec\\.ts$", 76 | "transform": { 77 | "^.+\\.(t|j)s$": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "../coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ConfigModule } from '@nestjs/config'; 4 | import { DriverController } from './controllers/driver.controller'; 5 | import { DriverService } from './service/driver.service'; 6 | import { DriverRepository } from './repository/driver.repository'; 7 | import { AppProperties } from './config/app-properties.config'; 8 | import { CarTypeRepository } from './repository/car-type.repository'; 9 | import { CarTypeController } from './controllers/car-type.controller'; 10 | import { CarTypeService } from './service/car-type.service'; 11 | import { DistanceCalculator } from './service/distance-calculator.service'; 12 | import { InvoiceRepository } from './repository/invoice.repository'; 13 | import { InvoiceGenerator } from './service/invoice-generator.service'; 14 | import { DriverNotificationService } from './service/driver-notification.service'; 15 | import { GeocodingService } from './service/geocoding.service'; 16 | import { ClaimNumberGenerator } from './service/claim-number-generator.service'; 17 | import { ClaimRepository } from './repository/claim.repository'; 18 | import { ClientNotificationService } from './service/client-notification.service'; 19 | import { ClientService } from './service/client.service'; 20 | import { ClientRepository } from './repository/client.repository'; 21 | import { ClientController } from './controllers/client.controller'; 22 | import { DriverSessionService } from './service/driver-session.service'; 23 | import { DriverSessionRepository } from './repository/driver-session.repository'; 24 | import { DriverSessionController } from './controllers/driver-session.controller'; 25 | import { DriverFeeRepository } from './repository/driver-fee.repository'; 26 | import { TransitRepository } from './repository/transit.repository'; 27 | import { DriverFeeService } from './service/driver-fee.service'; 28 | import { DriverPositionRepository } from './repository/driver-position.repository'; 29 | import { DriverTrackingService } from './service/driver-tracking.service'; 30 | import { DriverTrackingController } from './controllers/driver-tracking.controller'; 31 | import { ClaimAttachmentRepository } from './repository/claim-attachment.repository'; 32 | import { AddressRepository } from './repository/address.repository'; 33 | import { DriverAttributeRepository } from './repository/driver-attribute.repository'; 34 | import { AwardedMilesRepository } from './repository/awarded-miles.repository'; 35 | import { AwardsAccountRepository } from './repository/awards-account.repository'; 36 | import { ContractAttachmentRepository } from './repository/contract-attachment.repository'; 37 | import { ContractRepository } from './repository/contract.repository'; 38 | import { TransitAnalyzerService } from './service/transit-analyzer.service'; 39 | import { AwardsService } from './service/awards.service'; 40 | import { ClaimService } from './service/claim.service'; 41 | import { ContractService } from './service/contract.service'; 42 | import { TransitService } from './service/transit.service'; 43 | import { TransitAnalyzerController } from './controllers/transit-analyzer.controller'; 44 | import { TransitController } from './controllers/transit.controller'; 45 | import { AwardsAccountController } from './controllers/awards-account.controller'; 46 | import { ClaimController } from './controllers/claim.controller'; 47 | import { ContractController } from './controllers/contract.controller'; 48 | import { DriverReportController } from './controllers/driver-report.controller'; 49 | 50 | @Module({ 51 | imports: [ 52 | ConfigModule.forRoot(), 53 | TypeOrmModule.forRoot({ 54 | type: 'postgres', 55 | host: 'localhost', 56 | port: process.env.DATABASE_PORT 57 | ? parseInt(process.env.DATABASE_PORT, 10) 58 | : 3456, 59 | username: process.env.DATABASE_USERNAME, 60 | password: process.env.DATABASE_PASSWORD, 61 | database: process.env.DATABASE_NAME, 62 | autoLoadEntities: true, 63 | synchronize: true, 64 | }), 65 | TypeOrmModule.forFeature([ 66 | DriverRepository, 67 | CarTypeRepository, 68 | InvoiceRepository, 69 | ClaimRepository, 70 | ClientRepository, 71 | DriverSessionRepository, 72 | DriverFeeRepository, 73 | TransitRepository, 74 | DriverPositionRepository, 75 | ClaimAttachmentRepository, 76 | AddressRepository, 77 | DriverAttributeRepository, 78 | AwardedMilesRepository, 79 | AwardsAccountRepository, 80 | ContractAttachmentRepository, 81 | ContractRepository, 82 | ]), 83 | ], 84 | controllers: [ 85 | DriverController, 86 | CarTypeController, 87 | ClientController, 88 | DriverSessionController, 89 | DriverTrackingController, 90 | TransitAnalyzerController, 91 | TransitController, 92 | AwardsAccountController, 93 | ClaimController, 94 | ContractController, 95 | DriverReportController, 96 | ], 97 | providers: [ 98 | AppProperties, 99 | DriverService, 100 | CarTypeService, 101 | DistanceCalculator, 102 | InvoiceGenerator, 103 | DriverNotificationService, 104 | GeocodingService, 105 | ClaimNumberGenerator, 106 | ClientNotificationService, 107 | ClientService, 108 | DriverSessionService, 109 | DriverFeeService, 110 | DriverTrackingService, 111 | TransitAnalyzerService, 112 | AwardsService, 113 | ClaimService, 114 | ContractService, 115 | TransitService, 116 | ], 117 | }) 118 | export class AppModule {} 119 | -------------------------------------------------------------------------------- /src/common/base.entity.ts: -------------------------------------------------------------------------------- 1 | import { PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export class BaseEntity { 4 | @PrimaryGeneratedColumn('uuid') 5 | protected id: string; 6 | 7 | public getId(): string { 8 | return this.id; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/config/app-properties.config.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppProperties { 5 | private noOfTransitsForClaimAutomaticRefund: number; 6 | 7 | private automaticRefundForVipThreshold: number; 8 | 9 | private minNoOfCarsForEcoClass: number; 10 | 11 | private milesExpirationInDays = 365; 12 | 13 | private defaultMilesBonus = 10; 14 | 15 | public getAutomaticRefundForVipThreshold() { 16 | return this.automaticRefundForVipThreshold; 17 | } 18 | 19 | public getNoOfTransitsForClaimAutomaticRefund() { 20 | return this.noOfTransitsForClaimAutomaticRefund; 21 | } 22 | 23 | public setNoOfTransitsForClaimAutomaticRefund( 24 | noOfTransitsForClaimAutomaticRefund: number, 25 | ) { 26 | this.noOfTransitsForClaimAutomaticRefund = 27 | noOfTransitsForClaimAutomaticRefund; 28 | } 29 | 30 | public getMinNoOfCarsForEcoClass() { 31 | return this.minNoOfCarsForEcoClass; 32 | } 33 | 34 | public setMinNoOfCarsForEcoClass(minNoOfCarsForEcoClass: number) { 35 | this.minNoOfCarsForEcoClass = minNoOfCarsForEcoClass; 36 | } 37 | 38 | public getMilesExpirationInDays() { 39 | return this.milesExpirationInDays; 40 | } 41 | 42 | public getDefaultMilesBonus() { 43 | return this.defaultMilesBonus; 44 | } 45 | 46 | public setMilesExpirationInDays(milesExpirationInDays: number) { 47 | this.milesExpirationInDays = milesExpirationInDays; 48 | } 49 | 50 | public setDefaultMilesBonus(defaultMilesBonus: number) { 51 | this.defaultMilesBonus = defaultMilesBonus; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/controllers/awards-account.controller.ts: -------------------------------------------------------------------------------- 1 | import { AwardsService } from '../service/awards.service'; 2 | import { Controller, Get, Param, Post } from '@nestjs/common'; 3 | import { AwardsAccountDto } from '../dto/awards-account.dto'; 4 | 5 | @Controller('clients') 6 | export class AwardsAccountController { 7 | constructor(private awardsService: AwardsService) {} 8 | 9 | @Post(':clientId/awards') 10 | public async register( 11 | @Param('clientId') clientId: string, 12 | ): Promise { 13 | await this.awardsService.registerToProgram(clientId); 14 | return this.awardsService.findBy(clientId); 15 | } 16 | 17 | @Post(':clientId/awards/activate') 18 | public async activate( 19 | @Param('clientId') clientId: string, 20 | ): Promise { 21 | await this.awardsService.activateAccount(clientId); 22 | return this.awardsService.findBy(clientId); 23 | } 24 | 25 | @Post(':clientId/awards/deactivate') 26 | public async deactivate( 27 | @Param('clientId') clientId: string, 28 | ): Promise { 29 | await this.awardsService.deactivateAccount(clientId); 30 | return this.awardsService.findBy(clientId); 31 | } 32 | 33 | @Post(':clientId/awards/balance') 34 | public async calculateBalance( 35 | @Param('clientId') clientId: string, 36 | ): Promise { 37 | return this.awardsService.calculateBalance(clientId); 38 | } 39 | 40 | @Post(':clientId/awards/transfer/:toClientId/:howMuch') 41 | public async transferMiles( 42 | @Param('clientId') clientId: string, 43 | @Param('toClientId') toClientId: string, 44 | @Param('howMuch') howMuch: string, 45 | ): Promise { 46 | await this.awardsService.transferMiles( 47 | clientId, 48 | toClientId, 49 | Number(howMuch), 50 | ); 51 | return this.awardsService.findBy(clientId); 52 | } 53 | 54 | @Get(':clientId/awards') 55 | public async findBy( 56 | @Param('clientId') clientId: string, 57 | ): Promise { 58 | return this.awardsService.findBy(clientId); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/controllers/car-type.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Param, 5 | Post, 6 | UsePipes, 7 | ValidationPipe, 8 | Res, 9 | HttpStatus, 10 | Get, 11 | } from '@nestjs/common'; 12 | import { Response } from 'express'; 13 | import { CarTypeService } from '../service/car-type.service'; 14 | import { CreateCarTypeDto } from '../dto/create-car-type.dto'; 15 | import { CarTypeDto } from '../dto/car-type.dto'; 16 | import { CarClass } from '../entity/car-type.entity'; 17 | 18 | @Controller('cartypes') 19 | export class CarTypeController { 20 | constructor(private readonly carTypeService: CarTypeService) {} 21 | 22 | @Post() 23 | @UsePipes(ValidationPipe) 24 | public async create( 25 | @Body() createCarTypeDto: CreateCarTypeDto, 26 | ): Promise { 27 | const carType = await this.carTypeService.create(createCarTypeDto); 28 | 29 | return new CarTypeDto(carType); 30 | } 31 | 32 | @Post(':carClass/registerCar') 33 | public async registerCar( 34 | @Param('carClass') carClass: CarClass, 35 | @Res() res: Response, 36 | ): Promise { 37 | await this.carTypeService.registerCar(carClass); 38 | res.status(HttpStatus.OK).send(); 39 | } 40 | 41 | @Post(':carClass/unregisterCar') 42 | public async unregisterCar( 43 | @Param('carClass') carClass: CarClass, 44 | @Res() res: Response, 45 | ): Promise { 46 | await this.carTypeService.unregisterCar(carClass); 47 | res.status(HttpStatus.OK).send(); 48 | } 49 | 50 | @Post(':id/activate') 51 | public async activate( 52 | @Param('id') id: string, 53 | @Res() res: Response, 54 | ): Promise { 55 | await this.carTypeService.activate(id); 56 | res.status(HttpStatus.OK).send(); 57 | } 58 | 59 | @Post(':id/deactivate') 60 | public async deactivate( 61 | @Param('id') id: string, 62 | @Res() res: Response, 63 | ): Promise { 64 | await this.carTypeService.deactivate(id); 65 | res.status(HttpStatus.OK).send(); 66 | } 67 | 68 | @Get(':id') 69 | public async find(@Param('id') id: string): Promise { 70 | return this.carTypeService.loadDto(id); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/controllers/claim.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClaimService } from '../service/claim.service'; 2 | import { Body, Controller, Get, Param, Post } from '@nestjs/common'; 3 | import { ClaimDto } from '../dto/claim.dto'; 4 | import { Claim, ClaimStatus } from '../entity/claim.entity'; 5 | import { CreateClaimDto } from '../dto/create-claim.dto'; 6 | 7 | @Controller('claims') 8 | export class ClaimController { 9 | constructor(private claimService: ClaimService) {} 10 | 11 | @Post('createDraft') 12 | public async create(@Body() createClaimDto: CreateClaimDto) { 13 | const created = await this.claimService.create( 14 | new ClaimDto(createClaimDto), 15 | ); 16 | return this.toDto(created); 17 | } 18 | 19 | @Post('send') 20 | public async sendNew(@Body() createClaimDto: CreateClaimDto) { 21 | const claimDto = new ClaimDto(createClaimDto); 22 | claimDto.setDraft(false); 23 | const claim = await this.claimService.create(claimDto); 24 | return this.toDto(claim); 25 | } 26 | 27 | @Post(':claimId/markInProcess') 28 | public async markAsInProcess(@Param('claimId') claimId: string) { 29 | const claim = await this.claimService.setStatus( 30 | ClaimStatus.IN_PROCESS, 31 | claimId, 32 | ); 33 | return this.toDto(claim); 34 | } 35 | 36 | @Get(':claimId') 37 | public async find(@Param('claimId') claimId: string) { 38 | const claim = await this.claimService.find(claimId); 39 | const dto = this.toDto(claim); 40 | return dto; 41 | } 42 | 43 | @Post('/claims/:claimId') 44 | public async tryToAutomaticallyResolve(@Param('claimId') claimId: string) { 45 | const claim = await this.claimService.tryToResolveAutomatically(claimId); 46 | return this.toDto(claim); 47 | } 48 | 49 | private toDto(claim: Claim) { 50 | return new ClaimDto(claim); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/controllers/client.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | UsePipes, 8 | ValidationPipe, 9 | } from '@nestjs/common'; 10 | import { ClientService } from '../service/client.service'; 11 | import { CreateClientDto } from '../dto/create-client.dto'; 12 | import { ClientDto } from '../dto/client.dto'; 13 | 14 | @Controller('clients') 15 | export class ClientController { 16 | constructor(private readonly clientService: ClientService) {} 17 | 18 | @Post() 19 | @UsePipes(ValidationPipe) 20 | public async register( 21 | @Body() createClientDto: CreateClientDto, 22 | ): Promise { 23 | const client = await this.clientService.registerClient( 24 | createClientDto.name, 25 | createClientDto.lastName, 26 | createClientDto.type, 27 | createClientDto.defaultPaymentType, 28 | ); 29 | 30 | return this.clientService.load(client.getId()); 31 | } 32 | 33 | @Get(':id') 34 | public async find(@Param('id') id: string): Promise { 35 | return this.clientService.load(id); 36 | } 37 | 38 | @Post(':id/upgrade') 39 | public async upgradeToVIP(@Param('id') id: string): Promise { 40 | await this.clientService.upgradeToVIP(id); 41 | return this.clientService.load(id); 42 | } 43 | 44 | @Post(':id/downgrade') 45 | public async downgrade(@Param('id') id: string): Promise { 46 | await this.clientService.downgradeToRegular(id); 47 | return this.clientService.load(id); 48 | } 49 | 50 | @Post(':id/changeDefaultPaymentType') 51 | public async changeDefaultPaymentType( 52 | @Param('id') id: string, 53 | @Body() createClientDto: Pick, 54 | ): Promise { 55 | await this.clientService.changeDefaultPaymentType( 56 | id, 57 | createClientDto.defaultPaymentType, 58 | ); 59 | return this.clientService.load(id); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/controllers/contract.controller.ts: -------------------------------------------------------------------------------- 1 | import { ContractService } from '../service/contract.service'; 2 | import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; 3 | import { ContractDto } from '../dto/contract.dto'; 4 | import { CreateContractDto } from '../dto/create-contract.dto'; 5 | import { CreateContractAttachmentDto } from '../dto/create-contract-attachment.dto'; 6 | 7 | @Controller('contracts') 8 | export class ContractController { 9 | constructor(private contractService: ContractService) {} 10 | 11 | @Post() 12 | public async create(@Body() createContractDto: CreateContractDto) { 13 | const created = await this.contractService.createContract( 14 | createContractDto, 15 | ); 16 | return new ContractDto(created); 17 | } 18 | 19 | @Get(':contractId') 20 | public async find(@Param('contractId') contractId: string) { 21 | const contract = await this.contractService.findDto(contractId); 22 | return contract; 23 | } 24 | 25 | @Post(':contractId/attachment') 26 | public async proposeAttachment( 27 | @Param('contractId') contractId: string, 28 | @Body() createContractAttachmentDto: CreateContractAttachmentDto, 29 | ) { 30 | const dto = await this.contractService.proposeAttachment( 31 | contractId, 32 | createContractAttachmentDto, 33 | ); 34 | return dto; 35 | } 36 | 37 | @Post(':contractId/attachment/:attachmentId/reject') 38 | public async rejectAttachment( 39 | @Param('contractId') contractId: string, 40 | @Param('attachmentId') attachmentId: string, 41 | ) { 42 | await this.contractService.rejectAttachment(attachmentId); 43 | } 44 | 45 | @Post(':contractId/attachment/:attachmentId/accept') 46 | public async acceptAttachment( 47 | @Param('contractId') contractId: string, 48 | @Param('attachmentId') attachmentId: string, 49 | ) { 50 | await this.contractService.acceptAttachment(attachmentId); 51 | } 52 | 53 | @Delete(':contractId/attachment/:attachmentId') 54 | public async removeAttachment( 55 | @Param('contractId') contractId: string, 56 | @Param('attachmentId') attachmentId: string, 57 | ) { 58 | await this.contractService.removeAttachment(contractId, attachmentId); 59 | } 60 | 61 | @Post(':contractId/accept') 62 | public async acceptContract(@Param('contractId') contractId: string) { 63 | await this.contractService.acceptContract(contractId); 64 | } 65 | 66 | @Post(':contractId/reject') 67 | public async rejectContract(@Param('contractId') contractId: string) { 68 | await this.contractService.rejectContract(contractId); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/controllers/driver-report.controller.ts: -------------------------------------------------------------------------------- 1 | import { DriverService } from '../service/driver.service'; 2 | import { DriverRepository } from '../repository/driver.repository'; 3 | import { InjectRepository } from '@nestjs/typeorm'; 4 | import { ClaimRepository } from '../repository/claim.repository'; 5 | import { DriverSessionRepository } from '../repository/driver-session.repository'; 6 | import { 7 | Body, 8 | Controller, 9 | Get, 10 | NotFoundException, 11 | Param, 12 | } from '@nestjs/common'; 13 | import { DriverReport } from '../dto/driver-report.dto'; 14 | import { DriverAttributeName } from '../entity/driver-attribute.entity'; 15 | import { DriverAttributeDto } from '../dto/driver-attribute.dto'; 16 | import * as dayjs from 'dayjs'; 17 | import { DriverSessionDto } from '../dto/driver-session.dto'; 18 | import { TransitDto } from '../dto/transit.dto'; 19 | import { Status, Transit } from '../entity/transit.entity'; 20 | import { ClaimDto } from '../dto/claim.dto'; 21 | 22 | @Controller('driverreport') 23 | export class DriverReportController { 24 | constructor( 25 | private driverService: DriverService, 26 | @InjectRepository(DriverRepository) 27 | private driverRepository: DriverRepository, 28 | @InjectRepository(ClaimRepository) 29 | private claimRepository: ClaimRepository, 30 | @InjectRepository(DriverSessionRepository) 31 | private driverSessionRepository: DriverSessionRepository, 32 | ) {} 33 | 34 | @Get(':driverId') 35 | public async loadReportForDriver( 36 | @Param('driverId') driverId: string, 37 | @Body() body: { lastDays: number }, 38 | ): Promise { 39 | const driverReport = new DriverReport(); 40 | const driverDto = await this.driverService.loadDriver(driverId); 41 | 42 | driverReport.setDriverDTO(driverDto); 43 | const driver = await this.driverRepository.findOne(driverId); 44 | 45 | if (!driver) { 46 | throw new NotFoundException(`Driver with id ${driverId} not exists`); 47 | } 48 | 49 | driver 50 | .getAttributes() 51 | .filter( 52 | (attr) => 53 | attr.getName() !== DriverAttributeName.MEDICAL_EXAMINATION_REMARKS, 54 | ) 55 | .forEach((attr) => 56 | driverReport.getAttributes().push(new DriverAttributeDto(attr)), 57 | ); 58 | 59 | const beggingOfToday = dayjs().startOf('day'); 60 | const since = beggingOfToday.subtract(body.lastDays, 'days'); 61 | const allByDriverAndLoggedAtAfter = 62 | await this.driverSessionRepository.findAllByDriverAndLoggedAtAfter( 63 | driver, 64 | since.valueOf(), 65 | ); 66 | const sessionsWithTransits: Map = new Map(); 67 | for (const session of allByDriverAndLoggedAtAfter) { 68 | const dto = new DriverSessionDto(session); 69 | const transitsInSession: Transit[] = Array.from( 70 | driver?.getTransits(), 71 | ).filter( 72 | (t) => 73 | t.getStatus() === Status.COMPLETED && 74 | !dayjs(t.getCompleteAt()).isBefore(session.getLoggedAt()) && 75 | !dayjs(t.getCompleteAt()).isAfter(session.getLoggedOutAt()), 76 | ); 77 | 78 | const transitsDtosInSession: TransitDto[] = []; 79 | for (const t of transitsInSession) { 80 | const transitDTO = new TransitDto(t); 81 | const byOwnerAndTransit = 82 | await this.claimRepository.findByOwnerAndTransit(t.getClient(), t); 83 | if (byOwnerAndTransit.length) { 84 | const claim = new ClaimDto(byOwnerAndTransit[0]); 85 | transitDTO.setClaimDTO(claim); 86 | } 87 | transitsDtosInSession.push(transitDTO); 88 | } 89 | sessionsWithTransits.set(dto, transitsDtosInSession); 90 | } 91 | driverReport.setSessions(sessionsWithTransits); 92 | return driverReport; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/controllers/driver-session.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; 2 | import { DriverSessionService } from '../service/driver-session.service'; 3 | import { DriverSession } from '../entity/driver-session.entity'; 4 | import { CreateDriverSessionDto } from '../dto/create-driver-session.dto'; 5 | 6 | @Controller('drivers') 7 | export class DriverSessionController { 8 | constructor(private readonly driverSessionService: DriverSessionService) {} 9 | 10 | @Post(':driverId/driverSessions/login') 11 | public async logIn( 12 | @Param('driverId') driverId: string, 13 | @Body() createSessionDto: CreateDriverSessionDto, 14 | ): Promise { 15 | await this.driverSessionService.logIn( 16 | driverId, 17 | createSessionDto.platesNumber, 18 | createSessionDto.carClass, 19 | createSessionDto.carBrand, 20 | ); 21 | } 22 | 23 | @Delete(':driverId/driverSessions/:sessionId') 24 | public async logOut( 25 | @Param('driverId') driverId: string, 26 | @Param('sessionId') sessionId: string, 27 | ): Promise { 28 | await this.driverSessionService.logOut(sessionId); 29 | } 30 | 31 | @Delete(':driverId/driverSessions') 32 | public async logOutCurrent( 33 | @Param('driverId') driverId: string, 34 | ): Promise { 35 | await this.driverSessionService.logOutCurrentSession(driverId); 36 | } 37 | 38 | @Get(':driverId/driverSessions') 39 | public async list( 40 | @Param('driverId') driverId: string, 41 | ): Promise { 42 | return this.driverSessionService.findByDriver(driverId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/controllers/driver-tracking.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | UsePipes, 8 | ValidationPipe, 9 | } from '@nestjs/common'; 10 | import { DriverTrackingService } from '../service/driver-tracking.service'; 11 | import { DriverPositionDto } from '../dto/driver-position.dto'; 12 | import { CreateDriverPositionDto } from '../dto/create-driver-position.dto'; 13 | import { DriverPosition } from '../entity/driver-position.entity'; 14 | 15 | @Controller('driverPositions') 16 | export class DriverTrackingController { 17 | constructor(private readonly trackingService: DriverTrackingService) {} 18 | 19 | @Post() 20 | @UsePipes(ValidationPipe) 21 | public async create( 22 | @Body() createDriverPositionDto: CreateDriverPositionDto, 23 | ): Promise { 24 | console.log('dto', createDriverPositionDto); 25 | const driverPosition = await this.trackingService.registerPosition( 26 | createDriverPositionDto.driverId, 27 | createDriverPositionDto.latitude, 28 | createDriverPositionDto.longitude, 29 | ); 30 | 31 | return this.toDto(driverPosition); 32 | } 33 | 34 | @Get(':driverId/total') 35 | public async calculateTravelledDistance( 36 | @Param('driverId') driverId: string, 37 | @Body() params: { from: number; to: number }, 38 | ): Promise { 39 | return this.trackingService.calculateTravelledDistance( 40 | driverId, 41 | params.from, 42 | params.to, 43 | ); 44 | } 45 | 46 | private toDto(driverPosition: DriverPosition) { 47 | return new DriverPositionDto( 48 | driverPosition.getDriver().getId(), 49 | driverPosition.getLatitude(), 50 | driverPosition.getLongitude(), 51 | driverPosition.getSeenAt(), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/controllers/driver.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Param, 6 | Post, 7 | UsePipes, 8 | ValidationPipe, 9 | } from '@nestjs/common'; 10 | import { DriverService } from '../service/driver.service'; 11 | import { CreateDriverDto } from '../dto/create-driver.dto'; 12 | import { DriverDto } from '../dto/driver.dto'; 13 | import { DriverStatus } from '../entity/driver.entity'; 14 | 15 | @Controller('drivers') 16 | export class DriverController { 17 | constructor(private readonly driverService: DriverService) {} 18 | 19 | @Post() 20 | @UsePipes(ValidationPipe) 21 | public async createDriver( 22 | @Body() createDriverDto: CreateDriverDto, 23 | ): Promise { 24 | const driver = await this.driverService.createDriver(createDriverDto); 25 | 26 | return this.driverService.loadDriver(driver.getId()); 27 | } 28 | 29 | @Get(':id') 30 | public async getDriver(@Param('id') id: string): Promise { 31 | return this.driverService.loadDriver(id); 32 | } 33 | 34 | @Post(':id') 35 | public async updateDriver(@Param('id') id: string): Promise { 36 | return this.driverService.loadDriver(id); 37 | } 38 | 39 | @Post(':id/deactivate') 40 | public async deactivateDriver(@Param('id') id: string): Promise { 41 | await this.driverService.changeDriverStatus(id, DriverStatus.INACTIVE); 42 | return this.driverService.loadDriver(id); 43 | } 44 | 45 | @Post(':id/activate') 46 | public async activateDriver(@Param('id') id: string): Promise { 47 | await this.driverService.changeDriverStatus(id, DriverStatus.ACTIVE); 48 | return this.driverService.loadDriver(id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/controllers/transit-analyzer.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param } from '@nestjs/common'; 2 | import { TransitAnalyzerService } from '../service/transit-analyzer.service'; 3 | import { AnalyzedAddressesDto } from '../dto/analyzed-addresses.dto'; 4 | import { AddressDto } from '../dto/address.dto'; 5 | 6 | @Controller('transitAnalyze') 7 | export class TransitAnalyzerController { 8 | constructor(private readonly transitAnalyzer: TransitAnalyzerService) {} 9 | 10 | @Get(':clientId/:addressId') 11 | public async analyze( 12 | @Param('clientId') clientId: string, 13 | @Param('addressId') addressId: string, 14 | ): Promise { 15 | const addresses = await this.transitAnalyzer.analyze(clientId, addressId); 16 | const addressDtos = addresses.map((a) => new AddressDto(a)); 17 | 18 | return new AnalyzedAddressesDto(addressDtos); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/controllers/transit.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Param, Post } from '@nestjs/common'; 2 | import { TransitService } from '../service/transit.service'; 3 | import { TransitDto } from '../dto/transit.dto'; 4 | import { CreateAddressDto } from '../dto/create-address.dto'; 5 | import { AddressDto } from '../dto/address.dto'; 6 | import { CreateTransitDto } from '../dto/create-transit.dto'; 7 | 8 | @Controller('transits') 9 | export class TransitController { 10 | constructor(private readonly transitService: TransitService) {} 11 | 12 | @Get(':transitId') 13 | public async getTransit( 14 | @Param('transitId') transitId: string, 15 | ): Promise { 16 | return this.transitService.loadTransit(transitId); 17 | } 18 | 19 | @Post() 20 | public async createTransit( 21 | @Body() createTransitDto: CreateTransitDto, 22 | ): Promise { 23 | const transit = await this.transitService.createTransit(createTransitDto); 24 | return this.transitService.loadTransit(transit.getId()); 25 | } 26 | 27 | @Post(':transitId/changeAddressTo') 28 | public async changeAddressTo( 29 | @Param('transitId') transitId: string, 30 | @Body() createAddressDto: CreateAddressDto, 31 | ): Promise { 32 | await this.transitService.changeTransitAddressTo( 33 | transitId, 34 | new AddressDto(createAddressDto), 35 | ); 36 | return this.transitService.loadTransit(transitId); 37 | } 38 | 39 | @Post(':transitId/changeAddressFrom') 40 | public async changeAddressFrom( 41 | @Param('transitId') transitId: string, 42 | @Body() createAddressDto: CreateAddressDto, 43 | ): Promise { 44 | await this.transitService.changeTransitAddressFrom( 45 | transitId, 46 | new AddressDto(createAddressDto), 47 | ); 48 | return this.transitService.loadTransit(transitId); 49 | } 50 | 51 | @Post(':transitId/cancel') 52 | public async cancel( 53 | @Param('transitId') transitId: string, 54 | ): Promise { 55 | await this.transitService.cancelTransit(transitId); 56 | return this.transitService.loadTransit(transitId); 57 | } 58 | 59 | @Post(':transitId/publish') 60 | public async publishTransit( 61 | @Param('transitId') transitId: string, 62 | ): Promise { 63 | await this.transitService.publishTransit(transitId); 64 | return this.transitService.loadTransit(transitId); 65 | } 66 | 67 | @Post(':transitId/findDrivers') 68 | public async findDriversForTransit( 69 | @Param('transitId') transitId: string, 70 | ): Promise { 71 | await this.transitService.findDriversForTransit(transitId); 72 | return this.transitService.loadTransit(transitId); 73 | } 74 | 75 | @Post(':transitId/accept/:driverId') 76 | public async acceptTransit( 77 | @Param('transitId') transitId: string, 78 | @Param('driverId') driverId: string, 79 | ): Promise { 80 | await this.transitService.acceptTransit(driverId, transitId); 81 | return this.transitService.loadTransit(transitId); 82 | } 83 | 84 | @Post(':transitId/start/:driverId') 85 | public async start( 86 | @Param('transitId') transitId: string, 87 | @Param('driverId') driverId: string, 88 | ): Promise { 89 | await this.transitService.startTransit(driverId, transitId); 90 | return this.transitService.loadTransit(transitId); 91 | } 92 | 93 | @Post(':transitId/reject/:driverId') 94 | public async reject( 95 | @Param('transitId') transitId: string, 96 | @Param('driverId') driverId: string, 97 | ): Promise { 98 | await this.transitService.rejectTransit(driverId, transitId); 99 | return this.transitService.loadTransit(transitId); 100 | } 101 | 102 | @Post(':transitId/complete/:driverId') 103 | public async complete( 104 | @Param('transitId') transitId: string, 105 | @Param('driverId') driverId: string, 106 | @Body() createAddressDto: CreateAddressDto, 107 | ): Promise { 108 | await this.transitService.completeTransitFromDto( 109 | driverId, 110 | transitId, 111 | new AddressDto(createAddressDto), 112 | ); 113 | return this.transitService.loadTransit(transitId); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/dto/address.dto.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../entity/address.entity'; 2 | 3 | export class AddressDto { 4 | public country: string; 5 | 6 | public district: string | null; 7 | 8 | public city: string; 9 | 10 | public street: string; 11 | 12 | public buildingNumber: number; 13 | 14 | public additionalNumber: number | null; 15 | 16 | public postalCode: string; 17 | 18 | public name: string; 19 | 20 | public constructor( 21 | a: 22 | | Address 23 | | { 24 | country: string; 25 | city: string; 26 | street: string; 27 | buildingNumber: number; 28 | postalCode: string; 29 | }, 30 | ) { 31 | if (a instanceof Address) { 32 | this.country = a.getCountry(); 33 | this.district = a.getDistrict(); 34 | this.city = a.getCity(); 35 | this.street = a.getStreet(); 36 | this.buildingNumber = a.getBuildingNumber(); 37 | this.additionalNumber = a.getAdditionalNumber(); 38 | this.postalCode = a.getPostalCode(); 39 | this.name = a.getName(); 40 | } else { 41 | this.country = a.country; 42 | this.city = a.city; 43 | this.street = a.street; 44 | this.buildingNumber = a.buildingNumber; 45 | this.postalCode = a.postalCode; 46 | } 47 | } 48 | public getCountry() { 49 | return this.country; 50 | } 51 | 52 | public setCountry(country: string) { 53 | this.country = country; 54 | } 55 | 56 | public getDistrict() { 57 | return this.district; 58 | } 59 | 60 | public setDistrict(district: string | null) { 61 | this.district = district; 62 | } 63 | 64 | public getCity() { 65 | return this.city; 66 | } 67 | 68 | public setCity(city: string) { 69 | this.city = city; 70 | } 71 | 72 | public getStreet() { 73 | return this.street; 74 | } 75 | 76 | public setStreet(street: string) { 77 | this.street = street; 78 | } 79 | 80 | public getBuildingNumber() { 81 | return this.buildingNumber; 82 | } 83 | 84 | public setBuildingNumber(buildingNumber: number) { 85 | this.buildingNumber = buildingNumber; 86 | } 87 | 88 | public getAdditionalNumber() { 89 | return this.additionalNumber; 90 | } 91 | 92 | public setAdditionalNumber(additionalNumber: number) { 93 | this.additionalNumber = additionalNumber; 94 | } 95 | 96 | public getPostalCode() { 97 | return this.postalCode; 98 | } 99 | 100 | public setPostalCode(postalCode: string) { 101 | this.postalCode = postalCode; 102 | } 103 | 104 | public getName() { 105 | return this.name; 106 | } 107 | 108 | public setName(name: string) { 109 | this.name = name; 110 | } 111 | 112 | public toAddressEntity() { 113 | const address = new Address( 114 | this.getCountry(), 115 | this.getCity(), 116 | this.getStreet(), 117 | this.getBuildingNumber(), 118 | ); 119 | address.setAdditionalNumber(this.getAdditionalNumber()); 120 | address.setName(this.getName() ?? ''); 121 | address.setPostalCode(this.getPostalCode()); 122 | address.setDistrict(this.getDistrict()); 123 | address.setHash(); 124 | return address; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/dto/analyzed-addresses.dto.ts: -------------------------------------------------------------------------------- 1 | import { AddressDto } from './address.dto'; 2 | 3 | export class AnalyzedAddressesDto { 4 | public addresses: AddressDto[]; 5 | 6 | constructor(addresses: AddressDto[]) { 7 | this.addresses = addresses; 8 | } 9 | 10 | public getAddresses() { 11 | return this.addresses; 12 | } 13 | 14 | public setAddresses(addresses: AddressDto[]) { 15 | this.addresses = addresses; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/dto/awards-account.dto.ts: -------------------------------------------------------------------------------- 1 | import { ClientDto } from './client.dto'; 2 | import { AwardsAccount } from '../entity/awards-account.entity'; 3 | 4 | export class AwardsAccountDto { 5 | private client: ClientDto; 6 | 7 | private date: number; 8 | 9 | private isActive: boolean; 10 | 11 | private transactions: number; 12 | 13 | constructor(account: AwardsAccount) { 14 | this.isActive = account.isAwardActive(); 15 | this.client = new ClientDto(account.getClient()); 16 | this.transactions = account.getTransactions(); 17 | this.date = account.getDate(); 18 | } 19 | 20 | public setClient(client: ClientDto) { 21 | this.client = client; 22 | } 23 | 24 | public getClient() { 25 | return this.client; 26 | } 27 | 28 | public setDate(date: number) { 29 | this.date = date; 30 | } 31 | 32 | public setActive(active: boolean) { 33 | this.isActive = active; 34 | } 35 | 36 | public getTransactions() { 37 | return this.transactions; 38 | } 39 | 40 | public setTransactions(transactions: number) { 41 | this.transactions = transactions; 42 | } 43 | 44 | public getDate() { 45 | return this.date; 46 | } 47 | 48 | public getActive() { 49 | return this.isActive; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/dto/car-type.dto.ts: -------------------------------------------------------------------------------- 1 | import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; 2 | 3 | export class CarTypeDto { 4 | private id: string; 5 | private carClass: CarClass; 6 | 7 | private description: string | null; 8 | 9 | private status: CarStatus; 10 | 11 | private carsCounter: number; 12 | 13 | private minNoOfCarsToActivateClass: number; 14 | 15 | private activeCarsCounter: number; 16 | 17 | constructor(carType: CarType) { 18 | this.id = carType.getId(); 19 | this.carClass = carType.getCarClass(); 20 | this.status = carType.getStatus(); 21 | this.description = carType.getDescription(); 22 | this.carsCounter = carType.getCarsCounter(); 23 | this.minNoOfCarsToActivateClass = carType.getMinNoOfCarsToActivateClass(); 24 | this.activeCarsCounter = carType.getActiveCarsCounter(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dto/claim.dto.ts: -------------------------------------------------------------------------------- 1 | import { Claim, ClaimStatus, CompletionMode } from '../entity/claim.entity'; 2 | import { CreateClaimDto } from './create-claim.dto'; 3 | 4 | export class ClaimDto { 5 | private claimID: string; 6 | 7 | private clientId: string; 8 | 9 | private transitId: string; 10 | 11 | private reason: string; 12 | 13 | private incidentDescription: string | null; 14 | 15 | private _isDraft: boolean; 16 | 17 | private creationDate: number; 18 | 19 | private completionDate: number | null; 20 | 21 | private changeDate: number | null; 22 | 23 | private completionMode: CompletionMode | null; 24 | 25 | private status: ClaimStatus; 26 | 27 | private claimNo: string; 28 | 29 | public constructor(claim: Claim | CreateClaimDto) { 30 | if (!(claim instanceof Claim)) { 31 | this.setClaimID(claim.clientId); 32 | this.setReason(claim.reason); 33 | this.setDraft(true); 34 | this.setIncidentDescription(claim.incidentDescription); 35 | } else { 36 | if (claim.getStatus() === ClaimStatus.DRAFT) { 37 | this.setDraft(true); 38 | } else { 39 | this.setDraft(false); 40 | } 41 | this.setClaimID(claim.getId()); 42 | this.setReason(claim.getReason()); 43 | this.setIncidentDescription(claim.getIncidentDescription()); 44 | this.setTransitId(claim.getTransit().getId()); 45 | this.setClientId(claim.getOwner().getId()); 46 | this.setCompletionDate(claim.getCompletionDate()); 47 | this.setChangeDate(claim.getChangeDate()); 48 | this.setClaimNo(claim.getClaimNo()); 49 | this.setStatus(claim.getStatus()); 50 | this.setCompletionMode(claim.getCompletionMode()); 51 | this.setCreationDate(claim.getCreationDate()); 52 | } 53 | } 54 | 55 | public getCreationDate() { 56 | return this.creationDate; 57 | } 58 | 59 | public setCreationDate(creationDate: number) { 60 | this.creationDate = creationDate; 61 | } 62 | 63 | public getCompletionDate() { 64 | return this.completionDate; 65 | } 66 | 67 | public setCompletionDate(completionDate: number | null) { 68 | this.completionDate = completionDate; 69 | } 70 | 71 | public getChangeDate() { 72 | return this.changeDate; 73 | } 74 | 75 | public setChangeDate(changeDate: number | null) { 76 | this.changeDate = changeDate; 77 | } 78 | 79 | public getCompletionMode() { 80 | return this.completionMode; 81 | } 82 | 83 | public setCompletionMode(completionMode: CompletionMode | null) { 84 | this.completionMode = completionMode; 85 | } 86 | 87 | public getStatus() { 88 | return this.status; 89 | } 90 | 91 | public setStatus(status: ClaimStatus) { 92 | this.status = status; 93 | } 94 | 95 | public getClaimNo() { 96 | return this.claimNo; 97 | } 98 | 99 | public setClaimNo(claimNo: string) { 100 | this.claimNo = claimNo; 101 | } 102 | 103 | public getClaimID() { 104 | return this.claimID; 105 | } 106 | 107 | public setClaimID(claimID: string) { 108 | this.claimID = claimID; 109 | } 110 | 111 | public getClientId() { 112 | return this.clientId; 113 | } 114 | 115 | public setClientId(clientId: string) { 116 | this.clientId = clientId; 117 | } 118 | 119 | public getTransitId() { 120 | return this.transitId; 121 | } 122 | 123 | public setTransitId(transitId: string) { 124 | this.transitId = transitId; 125 | } 126 | 127 | public getReason() { 128 | return this.reason; 129 | } 130 | 131 | public setReason(reason: string) { 132 | this.reason = reason; 133 | } 134 | 135 | public getIncidentDescription() { 136 | return this.incidentDescription; 137 | } 138 | 139 | public setIncidentDescription(incidentDescription: string | null) { 140 | this.incidentDescription = incidentDescription; 141 | } 142 | 143 | public isDraft() { 144 | return this._isDraft; 145 | } 146 | 147 | public setDraft(draft: boolean) { 148 | this._isDraft = draft; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/dto/client.dto.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientType, PaymentType, Type } from '../entity/client.entity'; 2 | 3 | export class ClientDto { 4 | private id: string; 5 | 6 | private type: Type; 7 | 8 | private name: string; 9 | 10 | private lastName: string; 11 | 12 | private defaultPaymentType: PaymentType; 13 | 14 | private clientType: ClientType; 15 | 16 | constructor(client: Client) { 17 | this.id = client.getId(); 18 | this.type = client.getType(); 19 | this.name = client.getName(); 20 | this.lastName = client.getLastName(); 21 | this.defaultPaymentType = client.getDefaultPaymentType(); 22 | this.clientType = client.getClientType(); 23 | } 24 | 25 | public getName() { 26 | return this.name; 27 | } 28 | 29 | public setName(name: string) { 30 | this.name = name; 31 | } 32 | 33 | public getLastName() { 34 | return this.lastName; 35 | } 36 | 37 | public setLastName(lastName: string) { 38 | this.lastName = lastName; 39 | } 40 | 41 | public getClientType() { 42 | return this.clientType; 43 | } 44 | 45 | public setClientType(clientType: ClientType) { 46 | this.clientType = clientType; 47 | } 48 | 49 | public getType() { 50 | return this.type; 51 | } 52 | 53 | public setType(type: Type) { 54 | this.type = type; 55 | } 56 | 57 | public getDefaultPaymentType() { 58 | return this.defaultPaymentType; 59 | } 60 | 61 | public setDefaultPaymentType(defaultPaymentType: PaymentType) { 62 | this.defaultPaymentType = defaultPaymentType; 63 | } 64 | 65 | public getId() { 66 | return this.id; 67 | } 68 | 69 | public setId(id: string) { 70 | this.id = id; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/dto/contract-attachment.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContractAttachment, 3 | ContractAttachmentStatus, 4 | } from '../entity/contract-attachment.entity'; 5 | 6 | export class ContractAttachmentDto { 7 | private id: string; 8 | 9 | private contractId: string; 10 | 11 | private data: string; 12 | 13 | private creationDate: number; 14 | 15 | private acceptedAt: number | null; 16 | 17 | private rejectedAt: number | null; 18 | 19 | private changeDate: number | null; 20 | 21 | private status: ContractAttachmentStatus; 22 | 23 | constructor(attachment: ContractAttachment, contractId?: string) { 24 | this.id = attachment.getId(); 25 | this.data = attachment.getData().toString(); 26 | this.contractId = contractId || attachment.getContract().getId(); 27 | this.creationDate = attachment.getCreationDate(); 28 | this.rejectedAt = attachment.getRejectedAt(); 29 | this.acceptedAt = attachment.getAcceptedAt(); 30 | this.changeDate = attachment.getChangeDate(); 31 | this.status = attachment.getStatus(); 32 | } 33 | 34 | public getData() { 35 | return this.data; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/dto/contract.dto.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ContractStatus } from '../entity/contract.entity'; 2 | import { ContractAttachmentDto } from './contract-attachment.dto'; 3 | 4 | export class ContractDto { 5 | private id: string; 6 | 7 | private subject: string; 8 | 9 | private partnerName: string; 10 | 11 | private creationDate: number; 12 | 13 | private acceptedAt: number | null; 14 | 15 | private rejectedAt: number | null; 16 | 17 | private changeDate: number | null; 18 | 19 | private status: ContractStatus; 20 | 21 | private contractNo: string; 22 | 23 | private attachments: ContractAttachmentDto[] = []; 24 | 25 | constructor(contract: Contract) { 26 | this.setContractNo(contract.getContractNo()); 27 | this.setAcceptedAt(contract.getAcceptedAt()); 28 | this.setRejectedAt(contract.getRejectedAt()); 29 | this.setCreationDate(contract.getCreationDate()); 30 | this.setChangeDate(contract.getChangeDate()); 31 | this.setStatus(contract.getStatus()); 32 | this.setPartnerName(contract.getPartnerName()); 33 | this.setSubject(contract.getSubject()); 34 | for (const attachment of contract.getAttachments()) { 35 | this.attachments.push( 36 | new ContractAttachmentDto(attachment, contract.getId()), 37 | ); 38 | } 39 | this.setId(contract.getId()); 40 | } 41 | 42 | public getCreationDate() { 43 | return this.creationDate; 44 | } 45 | 46 | public setCreationDate(creationDate: number) { 47 | this.creationDate = creationDate; 48 | } 49 | 50 | public getAcceptedAt() { 51 | return this.acceptedAt; 52 | } 53 | 54 | public setAcceptedAt(acceptedAt: number | null) { 55 | this.acceptedAt = acceptedAt; 56 | } 57 | 58 | public getRejectedAt() { 59 | return this.rejectedAt; 60 | } 61 | 62 | public setRejectedAt(rejectedAt: number | null) { 63 | this.rejectedAt = rejectedAt; 64 | } 65 | 66 | public getChangeDate() { 67 | return this.changeDate; 68 | } 69 | 70 | public setChangeDate(changeDate: number | null) { 71 | this.changeDate = changeDate; 72 | } 73 | 74 | public getStatus() { 75 | return this.status; 76 | } 77 | 78 | public setStatus(status: ContractStatus) { 79 | this.status = status; 80 | } 81 | 82 | public getContractNo() { 83 | return this.contractNo; 84 | } 85 | 86 | public setContractNo(contractNo: string) { 87 | this.contractNo = contractNo; 88 | } 89 | 90 | public getPartnerName() { 91 | return this.partnerName; 92 | } 93 | 94 | public setPartnerName(partnerName: string) { 95 | this.partnerName = partnerName; 96 | } 97 | 98 | public getSubject() { 99 | return this.subject; 100 | } 101 | 102 | public setSubject(subject: string) { 103 | this.subject = subject; 104 | } 105 | 106 | public getId() { 107 | return this.id; 108 | } 109 | 110 | public setId(id: string) { 111 | this.id = id; 112 | } 113 | 114 | public getAttachments() { 115 | return this.attachments; 116 | } 117 | 118 | public setAttachments(attachments: ContractAttachmentDto[]) { 119 | this.attachments = attachments; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/dto/create-address.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateAddressDto { 4 | @IsNotEmpty() 5 | public country: string; 6 | 7 | public district: string | null; 8 | 9 | @IsNotEmpty() 10 | public city: string; 11 | 12 | @IsNotEmpty() 13 | public street: string; 14 | 15 | @IsNotEmpty() 16 | public buildingNumber: number; 17 | 18 | @IsNotEmpty() 19 | public additionalNumber: number | null; 20 | 21 | @IsNotEmpty() 22 | public postalCode: string; 23 | 24 | public name: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/dto/create-car-type.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEnum } from 'class-validator'; 2 | import { CarClass } from '../entity/car-type.entity'; 3 | 4 | export class CreateCarTypeDto { 5 | public description: string; 6 | 7 | @IsEnum(CarClass) 8 | public carClass: CarClass; 9 | } 10 | -------------------------------------------------------------------------------- /src/dto/create-claim.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateClaimDto { 4 | @IsNotEmpty() 5 | public reason: string; 6 | 7 | @IsNotEmpty() 8 | public transitId: string; 9 | 10 | @IsNotEmpty() 11 | public clientId: string; 12 | 13 | @IsNotEmpty() 14 | public incidentDescription: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/dto/create-client.dto.ts: -------------------------------------------------------------------------------- 1 | import { PaymentType, Type } from '../entity/client.entity'; 2 | import { IsEnum, IsNotEmpty } from 'class-validator'; 3 | 4 | export class CreateClientDto { 5 | @IsNotEmpty() 6 | public name: string; 7 | 8 | @IsNotEmpty() 9 | public lastName: string; 10 | 11 | @IsEnum(Type) 12 | public type: Type; 13 | 14 | @IsEnum(PaymentType) 15 | public defaultPaymentType: PaymentType; 16 | } 17 | -------------------------------------------------------------------------------- /src/dto/create-contract-attachment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateContractAttachmentDto { 4 | @IsNotEmpty() 5 | public data: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/dto/create-contract.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateContractDto { 4 | @IsNotEmpty() 5 | public subject: string; 6 | 7 | @IsNotEmpty() 8 | public partnerName: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/dto/create-driver-position.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateDriverPositionDto { 4 | public latitude: number; 5 | 6 | public longitude: number; 7 | 8 | @IsNotEmpty() 9 | public driverId: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/dto/create-driver-session.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEnum, IsNotEmpty } from 'class-validator'; 2 | import { CarClass } from '../entity/car-type.entity'; 3 | 4 | export class CreateDriverSessionDto { 5 | @IsNotEmpty() 6 | public carBrand: string; 7 | 8 | @IsNotEmpty() 9 | public platesNumber: string; 10 | 11 | @IsEnum(CarClass) 12 | public carClass: CarClass; 13 | } 14 | -------------------------------------------------------------------------------- /src/dto/create-driver.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class CreateDriverDto { 4 | @IsNotEmpty() 5 | public firstName: string; 6 | 7 | @IsNotEmpty() 8 | public lastName: string; 9 | 10 | @IsNotEmpty() 11 | public driverLicense: string; 12 | 13 | public photo: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/dto/create-transit.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEnum, IsNotEmpty } from 'class-validator'; 2 | import { CarClass } from '../entity/car-type.entity'; 3 | 4 | export interface Address { 5 | country: string; 6 | district?: string | null; 7 | city: string; 8 | street: string; 9 | buildingNumber: number; 10 | additionalNumber: number | null; 11 | postalCode: string; 12 | name?: string; 13 | } 14 | 15 | export class CreateTransitDto { 16 | @IsNotEmpty() 17 | public clientId: string; 18 | 19 | public from: Address; 20 | 21 | public to: Address; 22 | 23 | @IsEnum(CarClass) 24 | public carClass: CarClass; 25 | } 26 | -------------------------------------------------------------------------------- /src/dto/driver-attribute.dto.ts: -------------------------------------------------------------------------------- 1 | import objectHash from 'object-hash'; 2 | import { 3 | DriverAttribute, 4 | DriverAttributeName, 5 | } from '../entity/driver-attribute.entity'; 6 | 7 | export class DriverAttributeDto { 8 | public name: DriverAttributeName; 9 | 10 | public value: string; 11 | 12 | constructor(driverAttribute: DriverAttribute) { 13 | this.name = driverAttribute.getName(); 14 | this.value = driverAttribute.getValue(); 15 | } 16 | 17 | public createDriverAttribute(name: DriverAttributeName, value: string) { 18 | this.name = name; 19 | this.value = value; 20 | } 21 | 22 | public getName() { 23 | return this.name; 24 | } 25 | 26 | public setName(name: DriverAttributeName) { 27 | this.name = name; 28 | } 29 | 30 | public getValue() { 31 | return this.value; 32 | } 33 | 34 | public setValue(value: string) { 35 | this.value = value; 36 | } 37 | 38 | public hashCode() { 39 | return objectHash({ name: this.name, value: this.value }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/dto/driver-position-v2.dto.ts: -------------------------------------------------------------------------------- 1 | import { Driver } from '../entity/driver.entity'; 2 | 3 | export class DriverPositionV2Dto { 4 | private driver: Driver; 5 | 6 | private latitude: number; 7 | 8 | private longitude: number; 9 | 10 | private seenAt: number; 11 | 12 | constructor( 13 | driver: Driver, 14 | latitude: number, 15 | longitude: number, 16 | seenAt: number, 17 | ) { 18 | this.driver = driver; 19 | this.latitude = latitude; 20 | this.longitude = longitude; 21 | this.seenAt = seenAt; 22 | } 23 | 24 | public getDriver() { 25 | return this.driver; 26 | } 27 | 28 | public setDriver(driver: Driver) { 29 | this.driver = driver; 30 | } 31 | 32 | public getLatitude() { 33 | return this.latitude; 34 | } 35 | 36 | public setLatitude(latitude: number) { 37 | this.latitude = latitude; 38 | } 39 | 40 | public getLongitude() { 41 | return this.longitude; 42 | } 43 | 44 | public setLongitude(longitude: number) { 45 | this.longitude = longitude; 46 | } 47 | 48 | public getSeenAt() { 49 | return this.seenAt; 50 | } 51 | 52 | public setSeenAt(seenAt: number) { 53 | this.seenAt = seenAt; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/dto/driver-position.dto.ts: -------------------------------------------------------------------------------- 1 | export class DriverPositionDto { 2 | private driverId: string; 3 | 4 | private latitude: number; 5 | 6 | private longitude: number; 7 | 8 | private seenAt: number; 9 | 10 | constructor( 11 | driverId: string, 12 | latitude: number, 13 | longitude: number, 14 | seenAt: number, 15 | ) { 16 | this.driverId = driverId; 17 | this.latitude = latitude; 18 | this.longitude = longitude; 19 | this.seenAt = seenAt; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dto/driver-report.dto.ts: -------------------------------------------------------------------------------- 1 | import { DriverDto } from './driver.dto'; 2 | import { DriverAttributeDto } from './driver-attribute.dto'; 3 | import { DriverSessionDto } from './driver-session.dto'; 4 | import { TransitDto } from './transit.dto'; 5 | 6 | export class DriverReport { 7 | public driverDto: DriverDto; 8 | 9 | public attributes: DriverAttributeDto[] = []; 10 | 11 | public sessions: Map = new Map(); 12 | 13 | public getDriverDto() { 14 | return this.driverDto; 15 | } 16 | 17 | public setDriverDTO(driverDto: DriverDto) { 18 | this.driverDto = driverDto; 19 | } 20 | 21 | public getAttributes() { 22 | return this.attributes; 23 | } 24 | 25 | public setAttributes(attributes: DriverAttributeDto[]) { 26 | this.attributes = attributes; 27 | } 28 | 29 | public getSessions() { 30 | return this.sessions; 31 | } 32 | 33 | public setSessions(sessions: Map) { 34 | this.sessions = sessions; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/dto/driver-session.dto.ts: -------------------------------------------------------------------------------- 1 | import { CarClass } from '../entity/car-type.entity'; 2 | import { DriverSession } from '../entity/driver-session.entity'; 3 | 4 | export class DriverSessionDto { 5 | private loggedAt: number | null; 6 | 7 | private loggedOutAt: number | null; 8 | 9 | private platesNumber: string; 10 | 11 | private carClass: CarClass; 12 | 13 | private carBrand: string; 14 | 15 | constructor(session: DriverSession) { 16 | this.carBrand = session.getCarBrand(); 17 | this.platesNumber = session.getPlatesNumber(); 18 | this.loggedAt = session.getLoggedAt(); 19 | this.loggedOutAt = session.getLoggedOutAt(); 20 | this.carClass = session.getCarClass(); 21 | } 22 | 23 | public getCarBrand() { 24 | return this.carBrand; 25 | } 26 | 27 | public setCarBrand(carBrand: string) { 28 | this.carBrand = carBrand; 29 | } 30 | 31 | public getLoggedAt() { 32 | return this.loggedAt; 33 | } 34 | 35 | public setLoggedAt(loggedAt: number) { 36 | this.loggedAt = loggedAt; 37 | } 38 | 39 | public getLoggedOutAt() { 40 | return this.loggedOutAt; 41 | } 42 | 43 | public setLoggedOutAt(loggedOutAt: number) { 44 | this.loggedOutAt = loggedOutAt; 45 | } 46 | 47 | public getPlatesNumber() { 48 | return this.platesNumber; 49 | } 50 | 51 | public setPlatesNumber(platesNumber: string) { 52 | this.platesNumber = platesNumber; 53 | } 54 | 55 | public getCarClass() { 56 | return this.carClass; 57 | } 58 | 59 | public setCarClass(carClass: CarClass) { 60 | this.carClass = carClass; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/dto/driver.dto.ts: -------------------------------------------------------------------------------- 1 | import { Driver, DriverStatus, DriverType } from '../entity/driver.entity'; 2 | 3 | export class DriverDto { 4 | private id: string; 5 | 6 | private status: DriverStatus; 7 | 8 | private firstName: string; 9 | 10 | private lastName: string; 11 | 12 | private driverLicense: string; 13 | 14 | private photo: string | null; 15 | 16 | private type: DriverType; 17 | 18 | constructor(driver: Driver) { 19 | this.id = driver.getId(); 20 | this.firstName = driver.getFirstName(); 21 | this.lastName = driver.getLastName(); 22 | this.driverLicense = driver.getDriverLicense(); 23 | this.photo = driver.getPhoto(); 24 | this.status = driver.getStatus(); 25 | this.type = driver.getType(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/dto/transit.dto.ts: -------------------------------------------------------------------------------- 1 | import { DayOfWeek, Status, Transit } from '../entity/transit.entity'; 2 | import { DriverDto } from './driver.dto'; 3 | import { ClaimDto } from './claim.dto'; 4 | import { AddressDto } from './address.dto'; 5 | import { CarClass } from '../entity/car-type.entity'; 6 | import { ClientDto } from './client.dto'; 7 | import * as dayjs from 'dayjs'; 8 | import * as dayOfYear from 'dayjs/plugin/dayOfYear'; 9 | import { NotAcceptableException } from '@nestjs/common'; 10 | 11 | dayjs.extend(dayOfYear); 12 | 13 | export class TransitDto { 14 | public id: string; 15 | 16 | public tariff: string; 17 | 18 | public status: Status; 19 | 20 | public driver: DriverDto; 21 | 22 | public factor: number | null; 23 | 24 | public distance: number; 25 | 26 | public distanceUnit: string; 27 | 28 | public kmRate: number; 29 | 30 | public price: number; 31 | 32 | public driverFee: number; 33 | 34 | public estimatedPrice: number; 35 | 36 | public baseFee: number; 37 | 38 | public date: number; 39 | 40 | public dateTime: number; 41 | 42 | public published: number; 43 | 44 | public acceptedAt: number | null; 45 | 46 | public started: number | null; 47 | 48 | public completeAt: number | null; 49 | 50 | public claimDto: ClaimDto; 51 | 52 | public proposedDrivers: DriverDto[] = []; 53 | 54 | public to: AddressDto; 55 | 56 | public from: AddressDto; 57 | 58 | public carClass: CarClass; 59 | 60 | public clientDto: ClientDto; 61 | 62 | constructor(transit: Transit) { 63 | this.id = transit.getId(); 64 | this.distance = transit.getKm(); 65 | this.factor = transit.factor; 66 | const price = transit.getPrice(); 67 | if (price) { 68 | this.price = price; 69 | } 70 | this.date = transit.getDateTime(); 71 | this.status = transit.getStatus(); 72 | this.setTariff(); 73 | for (const d of transit.getProposedDrivers()) { 74 | this.proposedDrivers.push(new DriverDto(d)); 75 | } 76 | this.to = new AddressDto(transit.getTo()); 77 | this.from = new AddressDto(transit.getFrom()); 78 | this.carClass = transit.getCarType(); 79 | this.clientDto = new ClientDto(transit.getClient()); 80 | if (transit.getDriversFee() != null) { 81 | this.driverFee = transit.getDriversFee(); 82 | } 83 | const estimatedPrice = transit.getEstimatedPrice(); 84 | if (estimatedPrice) { 85 | this.estimatedPrice = estimatedPrice; 86 | } 87 | this.dateTime = transit.getDateTime(); 88 | this.published = transit.getPublished(); 89 | this.acceptedAt = transit.getAcceptedAt(); 90 | this.started = transit.getStarted(); 91 | this.completeAt = transit.getCompleteAt(); 92 | } 93 | 94 | public getKmRate() { 95 | return this.kmRate; 96 | } 97 | 98 | public setTariff() { 99 | const day = dayjs(); 100 | 101 | // wprowadzenie nowych cennikow od 1.01.2019 102 | if (day.get('year') <= 2018) { 103 | this.kmRate = 1.0; 104 | this.tariff = 'Standard'; 105 | return; 106 | } 107 | 108 | const year = day.get('year'); 109 | const leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; 110 | 111 | if ( 112 | (leap && day.dayOfYear() == 366) || 113 | (!leap && day.dayOfYear() == 365) || 114 | (day.dayOfYear() == 1 && day.get('hour') <= 6) 115 | ) { 116 | this.tariff = 'Sylwester'; 117 | this.kmRate = 3.5; 118 | } else { 119 | switch (day.get('day')) { 120 | case DayOfWeek.MONDAY: 121 | case DayOfWeek.TUESDAY: 122 | case DayOfWeek.WEDNESDAY: 123 | case DayOfWeek.THURSDAY: 124 | this.kmRate = 1.0; 125 | this.tariff = 'Standard'; 126 | break; 127 | case DayOfWeek.FRIDAY: 128 | if (day.get('hour') < 17) { 129 | this.tariff = 'Standard'; 130 | this.kmRate = 1.0; 131 | } else { 132 | this.tariff = 'Weekend+'; 133 | this.kmRate = 2.5; 134 | } 135 | break; 136 | case DayOfWeek.SATURDAY: 137 | if (day.get('hour') < 6 || day.get('hour') >= 17) { 138 | this.kmRate = 2.5; 139 | this.tariff = 'Weekend+'; 140 | } else if (day.get('hour') < 17) { 141 | this.kmRate = 1.5; 142 | this.tariff = 'Weekend'; 143 | } 144 | break; 145 | case DayOfWeek.SUNDAY: 146 | if (day.get('hour') < 6) { 147 | this.kmRate = 2.5; 148 | this.tariff = 'Weekend+'; 149 | } else { 150 | this.kmRate = 1.5; 151 | this.tariff = 'Weekend'; 152 | } 153 | break; 154 | } 155 | } 156 | } 157 | 158 | public getTariff() { 159 | return this.tariff; 160 | } 161 | 162 | public getDistance(unit: string) { 163 | this.distanceUnit = unit; 164 | if (unit === 'km') { 165 | if (this.distance == Math.ceil(this.distance)) { 166 | return new Intl.NumberFormat('en-US', { 167 | style: 'unit', 168 | unit: 'length-kilometer', 169 | }).format(Math.round(this.distance)); 170 | } 171 | return new Intl.NumberFormat('en-US', { 172 | style: 'unit', 173 | unit: 'length-kilometer', 174 | }).format(this.distance); 175 | } 176 | if (unit === 'miles') { 177 | const distance = this.distance / 1.609344; 178 | if (distance == Math.ceil(distance)) { 179 | return new Intl.NumberFormat('en-US', { 180 | style: 'unit', 181 | unit: 'length-mile', 182 | }).format(Math.round(this.distance)); 183 | } 184 | return new Intl.NumberFormat('en-US', { 185 | style: 'unit', 186 | unit: 'length-mile', 187 | }).format(this.distance); 188 | } 189 | if (unit === 'm') { 190 | return new Intl.NumberFormat('en-US', { 191 | style: 'unit', 192 | unit: 'length-meter', 193 | }).format(Math.round(this.distance * 1000)); 194 | } 195 | throw new NotAcceptableException('Invalid unit ' + unit); 196 | } 197 | 198 | public getProposedDrivers() { 199 | return this.proposedDrivers; 200 | } 201 | 202 | public setProposedDrivers(proposedDrivers: DriverDto[]) { 203 | this.proposedDrivers = proposedDrivers; 204 | } 205 | 206 | public getClaimDTO() { 207 | return this.claimDto; 208 | } 209 | 210 | public setClaimDTO(claimDto: ClaimDto) { 211 | this.claimDto = claimDto; 212 | } 213 | 214 | public getTo() { 215 | return this.to; 216 | } 217 | 218 | public setTo(to: AddressDto) { 219 | this.to = to; 220 | } 221 | 222 | public getFrom() { 223 | return this.from; 224 | } 225 | 226 | public setFrom(from: AddressDto) { 227 | this.from = from; 228 | } 229 | 230 | public getCarClass() { 231 | return this.carClass; 232 | } 233 | 234 | public setCarClass(carClass: CarClass) { 235 | this.carClass = carClass; 236 | } 237 | 238 | public getClientDto() { 239 | return this.clientDto; 240 | } 241 | 242 | public setClientDTO(clientDto: ClientDto) { 243 | this.clientDto = clientDto; 244 | } 245 | 246 | public getId() { 247 | return this.id; 248 | } 249 | 250 | public getStatus() { 251 | return this.status; 252 | } 253 | 254 | public setStatus(status: Status) { 255 | this.status = status; 256 | } 257 | 258 | public getPrice() { 259 | return this.price; 260 | } 261 | 262 | public getDriverFee() { 263 | return this.driverFee; 264 | } 265 | 266 | public setDriverFee(driverFee: number) { 267 | this.driverFee = driverFee; 268 | } 269 | 270 | public getDateTime() { 271 | return this.dateTime; 272 | } 273 | 274 | public setDateTime(dateTime: number) { 275 | this.dateTime = dateTime; 276 | } 277 | 278 | public getPublished() { 279 | return this.published; 280 | } 281 | 282 | public setPublished(published: number) { 283 | this.published = published; 284 | } 285 | 286 | public getAcceptedAt() { 287 | return this.acceptedAt; 288 | } 289 | 290 | public setAcceptedAt(acceptedAt: number) { 291 | this.acceptedAt = acceptedAt; 292 | } 293 | 294 | public getStarted() { 295 | return this.started; 296 | } 297 | 298 | public setStarted(started: number) { 299 | this.started = started; 300 | } 301 | 302 | public getCompleteAt() { 303 | return this.completeAt; 304 | } 305 | 306 | public setCompleteAt(completeAt: number) { 307 | this.completeAt = completeAt; 308 | } 309 | 310 | public getEstimatedPrice() { 311 | return this.estimatedPrice; 312 | } 313 | 314 | public setEstimatedPrice(estimatedPrice: number) { 315 | this.estimatedPrice = estimatedPrice; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/entity/address.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity } from 'typeorm'; 3 | import * as objectHash from 'object-hash'; 4 | 5 | @Entity() 6 | export class Address extends BaseEntity { 7 | @Column() 8 | private country: string; 9 | 10 | @Column({ nullable: true, type: 'varchar' }) 11 | private district: string | null; 12 | 13 | @Column() 14 | private city: string; 15 | 16 | @Column() 17 | private street: string; 18 | 19 | @Column() 20 | private buildingNumber: number; 21 | 22 | @Column({ nullable: true, type: 'integer' }) 23 | private additionalNumber: number | null; 24 | 25 | @Column() 26 | private postalCode: string; 27 | 28 | @Column() 29 | private name: string; 30 | 31 | @Column({ unique: true }) 32 | private hash: string; 33 | 34 | constructor( 35 | country: string, 36 | city: string, 37 | street: string, 38 | buildingNumber: number, 39 | ) { 40 | super(); 41 | this.country = country; 42 | this.city = city; 43 | this.street = street; 44 | this.buildingNumber = buildingNumber; 45 | } 46 | 47 | public getCountry() { 48 | return this.country; 49 | } 50 | 51 | public setCountry(country: string) { 52 | this.country = country; 53 | } 54 | 55 | public getDistrict() { 56 | return this.district; 57 | } 58 | 59 | public setDistrict(district: string | null) { 60 | this.district = district; 61 | } 62 | 63 | public getCity() { 64 | return this.city; 65 | } 66 | 67 | public setCity(city: string) { 68 | this.city = city; 69 | } 70 | 71 | public getStreet() { 72 | return this.street; 73 | } 74 | 75 | public setStreet(street: string) { 76 | this.street = street; 77 | } 78 | 79 | public getBuildingNumber() { 80 | return this.buildingNumber; 81 | } 82 | 83 | public setBuildingNumber(buildingNumber: number) { 84 | this.buildingNumber = buildingNumber; 85 | } 86 | 87 | public getAdditionalNumber() { 88 | return this.additionalNumber; 89 | } 90 | 91 | public setAdditionalNumber(additionalNumber: number | null) { 92 | this.additionalNumber = additionalNumber; 93 | } 94 | 95 | public getPostalCode() { 96 | return this.postalCode; 97 | } 98 | 99 | public setPostalCode(postalCode: string) { 100 | this.postalCode = postalCode; 101 | } 102 | 103 | public getName() { 104 | return this.name; 105 | } 106 | 107 | public setName(name: string) { 108 | this.name = name; 109 | } 110 | 111 | public setHash() { 112 | this.hash = objectHash({ 113 | country: this.country, 114 | district: this.district, 115 | city: this.city, 116 | street: this.street, 117 | buildingNumber: this.buildingNumber, 118 | additionalNumber: this.additionalNumber, 119 | postalCode: this.postalCode, 120 | name: this.name, 121 | }); 122 | } 123 | 124 | public getHash() { 125 | this.setHash(); 126 | return this.hash; 127 | } 128 | 129 | public toString() { 130 | return ( 131 | 'Address{' + 132 | "id='" + 133 | this.getId() + 134 | "'" + 135 | ", country='" + 136 | this.country + 137 | "'" + 138 | ", district='" + 139 | this.district + 140 | "'" + 141 | ", city='" + 142 | this.city + 143 | "'" + 144 | ", street='" + 145 | this.street + 146 | "'" + 147 | ', buildingNumber=' + 148 | this.buildingNumber + 149 | ', additionalNumber=' + 150 | this.additionalNumber + 151 | ", postalCode='" + 152 | this.postalCode + 153 | "'" + 154 | ", name='" + 155 | this.name + 156 | "'" + 157 | '}' 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/entity/awarded-miles.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, ManyToOne } from 'typeorm'; 3 | import { Client } from './client.entity'; 4 | import { Transit } from './transit.entity'; 5 | 6 | @Entity() 7 | export class AwardedMiles extends BaseEntity { 8 | // Aggregate 9 | // 1. mile celowo są osobno, aby się mogło rozjechać na ich wydawaniu -> docelowo: kolekcja VOs w agregacie 10 | // VO 11 | // 1. miles + expirationDate -> VO przykrywające logikę walidacji, czy nie przekroczono daty ważności punktów 12 | // 2. wydzielenie interfejsu Miles -> różne VO z różną logiką, np. ExpirableMiles, NonExpirableMiles, LinearExpirableMiles 13 | 14 | @ManyToOne(() => Client) 15 | public client: Client; 16 | 17 | @Column() 18 | private miles: number; 19 | 20 | @Column({ default: Date.now(), type: 'bigint' }) 21 | private date: number; 22 | 23 | @Column({ nullable: true, type: 'bigint' }) 24 | private expirationDate: number | null; 25 | 26 | @Column({ nullable: true, type: 'boolean' }) 27 | private isSpecial: boolean | null; 28 | 29 | @ManyToOne(() => Transit) 30 | public transit: Transit | null; 31 | 32 | public getClient() { 33 | return this.client; 34 | } 35 | 36 | public setClient(client: Client) { 37 | this.client = client; 38 | } 39 | 40 | public getMiles() { 41 | return this.miles; 42 | } 43 | 44 | public setMiles(miles: number) { 45 | this.miles = miles; 46 | } 47 | 48 | public getDate() { 49 | return this.date; 50 | } 51 | 52 | public setDate(date: number) { 53 | this.date = date; 54 | } 55 | 56 | public getExpirationDate() { 57 | return this.expirationDate; 58 | } 59 | 60 | public setExpirationDate(expirationDate: number) { 61 | this.expirationDate = expirationDate; 62 | } 63 | 64 | public getSpecial() { 65 | return this.isSpecial; 66 | } 67 | 68 | public setSpecial(special: boolean) { 69 | this.isSpecial = special; 70 | } 71 | 72 | public setTransit(transit: Transit | null) { 73 | this.transit = transit; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/entity/awards-account.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, JoinColumn, OneToOne } from 'typeorm'; 3 | import { Client } from './client.entity'; 4 | 5 | @Entity() 6 | export class AwardsAccount extends BaseEntity { 7 | @Column({ default: Date.now(), type: 'bigint' }) 8 | private date: number; 9 | 10 | @Column({ default: false, type: 'boolean' }) 11 | private isActive: boolean; 12 | 13 | @Column({ default: 0, type: 'integer' }) 14 | private transactions: number; 15 | 16 | @OneToOne(() => Client, { eager: true }) 17 | @JoinColumn() 18 | private client: Client; 19 | 20 | public setClient(client: Client) { 21 | this.client = client; 22 | } 23 | 24 | public getClient() { 25 | return this.client; 26 | } 27 | 28 | public setDate(date: number) { 29 | this.date = date; 30 | } 31 | 32 | public setActive(active: boolean) { 33 | this.isActive = active; 34 | } 35 | 36 | public isAwardActive() { 37 | return this.isActive; 38 | } 39 | 40 | public getTransactions() { 41 | return this.transactions; 42 | } 43 | 44 | public increaseTransactions() { 45 | this.transactions++; 46 | } 47 | 48 | public getDate() { 49 | return this.date; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/entity/car-type.entity.ts: -------------------------------------------------------------------------------- 1 | import { NotAcceptableException } from '@nestjs/common'; 2 | import { Column, Entity } from 'typeorm'; 3 | import { BaseEntity } from '../common/base.entity'; 4 | 5 | export enum CarClass { 6 | ECO = 'eco', 7 | REGULAR = 'regular', 8 | VAN = 'van', 9 | PREMIUM = 'premium', 10 | } 11 | 12 | export enum CarStatus { 13 | INACTIVE = 'inactive', 14 | ACTIVE = 'active', 15 | } 16 | 17 | @Entity() 18 | export class CarType extends BaseEntity { 19 | @Column({ enum: CarClass, type: 'enum' }) 20 | private carClass: CarClass; 21 | 22 | @Column({ nullable: true, type: 'varchar' }) 23 | private description: string | null; 24 | 25 | @Column({ default: CarStatus.INACTIVE }) 26 | private status: CarStatus; 27 | 28 | @Column({ type: 'int', default: 0 }) 29 | private carsCounter: number; 30 | 31 | @Column({ type: 'int', default: 0 }) 32 | private minNoOfCarsToActivateClass: number; 33 | 34 | @Column({ type: 'int', default: 0 }) 35 | private activeCarsCounter: number; 36 | 37 | constructor( 38 | carClass: CarClass, 39 | description: string, 40 | minNoOfCarsToActivateClass: number, 41 | ) { 42 | super(); 43 | this.carClass = carClass; 44 | this.description = description; 45 | this.minNoOfCarsToActivateClass = minNoOfCarsToActivateClass; 46 | } 47 | 48 | public registerActiveCar() { 49 | this.activeCarsCounter++; 50 | } 51 | 52 | public unregisterActiveCar() { 53 | this.activeCarsCounter--; 54 | } 55 | 56 | public registerCar() { 57 | this.carsCounter++; 58 | } 59 | 60 | public unregisterCar() { 61 | this.carsCounter--; 62 | if (this.carsCounter < 0) { 63 | throw new NotAcceptableException('Cars counter can not be below 0'); 64 | } 65 | } 66 | 67 | public activate() { 68 | if (this.carsCounter < this.minNoOfCarsToActivateClass) { 69 | throw new NotAcceptableException( 70 | `Cannot activate car class when less than ${this.minNoOfCarsToActivateClass} cars in the fleet`, 71 | ); 72 | } 73 | this.status = CarStatus.ACTIVE; 74 | } 75 | 76 | public deactivate() { 77 | this.status = CarStatus.INACTIVE; 78 | } 79 | 80 | public getCarClass() { 81 | return this.carClass; 82 | } 83 | 84 | public setCarClass(carClass: CarClass) { 85 | this.carClass = carClass; 86 | } 87 | 88 | public getDescription() { 89 | return this.description; 90 | } 91 | 92 | public setDescription(description: string) { 93 | this.description = description; 94 | } 95 | 96 | public getStatus() { 97 | return this.status; 98 | } 99 | 100 | public getCarsCounter() { 101 | return this.carsCounter; 102 | } 103 | 104 | public getActiveCarsCounter() { 105 | return this.activeCarsCounter; 106 | } 107 | 108 | public getMinNoOfCarsToActivateClass() { 109 | return this.minNoOfCarsToActivateClass; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/entity/claim-attachment.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, ManyToOne } from 'typeorm'; 3 | import { Claim } from './claim.entity'; 4 | 5 | @Entity() 6 | export class ClaimAttachment extends BaseEntity { 7 | @ManyToOne(() => Claim) 8 | private claim: Claim; 9 | 10 | @Column({ type: 'bigint' }) 11 | private creationDate: number; 12 | 13 | @Column({ nullable: true, type: 'varchar' }) 14 | private description: string | null; 15 | 16 | @Column({ type: 'bytea' }) 17 | private data: Buffer; 18 | 19 | public getClient() { 20 | return this.claim.getOwner(); 21 | } 22 | 23 | public getClaim() { 24 | return this.claim; 25 | } 26 | 27 | public setClaim(claim: Claim) { 28 | this.claim = claim; 29 | } 30 | 31 | public getCreationDate() { 32 | return this.creationDate; 33 | } 34 | 35 | public setCreationDate(creationDate: number) { 36 | this.creationDate = creationDate; 37 | } 38 | 39 | public getDescription() { 40 | return this.description; 41 | } 42 | 43 | public setDescription(description: string) { 44 | this.description = description; 45 | } 46 | 47 | public getData() { 48 | return this.data; 49 | } 50 | 51 | public setData(data: string) { 52 | this.data = Buffer.from( 53 | '\\x' + Buffer.from(data, 'base64').toString('hex'), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/entity/claim.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Client } from './client.entity'; 3 | import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; 4 | import { Transit } from './transit.entity'; 5 | 6 | export enum ClaimStatus { 7 | DRAFT = 'draft', 8 | NEW = 'new', 9 | IN_PROCESS = 'in_process', 10 | REFUNDED = 'refunded', 11 | ESCALATED = 'escalated', 12 | REJECTED = 'rejected', 13 | } 14 | 15 | export enum CompletionMode { 16 | MANUAL = 'manual', 17 | AUTOMATIC = 'automatic', 18 | } 19 | 20 | @Entity() 21 | export class Claim extends BaseEntity { 22 | @ManyToOne(() => Client, (client) => client.claims) 23 | public owner: Client; 24 | 25 | @OneToOne(() => Transit) 26 | @JoinColumn() 27 | private transit: Transit; 28 | 29 | @Column({ type: 'bigint' }) 30 | private creationDate: number; 31 | 32 | @Column({ nullable: true, type: 'bigint' }) 33 | private completionDate: number | null; 34 | 35 | @Column({ nullable: true, type: 'bigint' }) 36 | private changeDate: number | null; 37 | 38 | @Column() 39 | private reason: string; 40 | 41 | @Column({ nullable: true, type: 'varchar' }) 42 | private incidentDescription: string | null; 43 | 44 | @Column({ nullable: true, enum: CompletionMode, type: 'enum', default: null }) 45 | private completionMode: CompletionMode | null; 46 | 47 | @Column() 48 | private status: ClaimStatus; 49 | 50 | @Column() 51 | private claimNo: string; 52 | 53 | public getClaimNo() { 54 | return this.claimNo; 55 | } 56 | 57 | public setClaimNo(claimNo: string) { 58 | this.claimNo = claimNo; 59 | } 60 | 61 | public getOwner() { 62 | return this.owner; 63 | } 64 | 65 | public setOwner(owner: Client) { 66 | this.owner = owner; 67 | } 68 | 69 | public getTransit() { 70 | return this.transit; 71 | } 72 | 73 | public setTransit(transit: Transit) { 74 | this.transit = transit; 75 | } 76 | 77 | public getCreationDate() { 78 | return this.creationDate; 79 | } 80 | 81 | public setCreationDate(creationDate: number) { 82 | this.creationDate = creationDate; 83 | } 84 | 85 | public getCompletionDate() { 86 | return this.completionDate; 87 | } 88 | 89 | public setCompletionDate(completionDate: number) { 90 | this.completionDate = completionDate; 91 | } 92 | 93 | public getIncidentDescription() { 94 | return this.incidentDescription; 95 | } 96 | 97 | public setIncidentDescription(incidentDescription: string | null) { 98 | this.incidentDescription = incidentDescription; 99 | } 100 | 101 | public getCompletionMode() { 102 | return this.completionMode; 103 | } 104 | 105 | public setCompletionMode(completionMode: CompletionMode) { 106 | this.completionMode = completionMode; 107 | } 108 | 109 | public getStatus() { 110 | return this.status; 111 | } 112 | 113 | public setStatus(status: ClaimStatus) { 114 | this.status = status; 115 | } 116 | 117 | public getChangeDate() { 118 | return this.changeDate; 119 | } 120 | 121 | public setChangeDate(changeDate: number) { 122 | this.changeDate = changeDate; 123 | } 124 | 125 | public getReason() { 126 | return this.reason; 127 | } 128 | 129 | public setReason(reason: string) { 130 | this.reason = reason; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/entity/client.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, OneToMany } from 'typeorm'; 3 | import { Claim } from './claim.entity'; 4 | 5 | export enum ClientType { 6 | INDIVIDUAL = 'individual', 7 | COMPANY = 'company', 8 | } 9 | 10 | export enum PaymentType { 11 | PRE_PAID = 'pre_paid', 12 | POST_PAID = 'post_paid', 13 | MONTHLY_INVOICE = 'monthly_invoice', 14 | } 15 | 16 | export enum Type { 17 | NORMAL = 'normal', 18 | VIP = 'vip', 19 | } 20 | 21 | @Entity() 22 | export class Client extends BaseEntity { 23 | @Column() 24 | private type: Type; 25 | 26 | @Column() 27 | private name: string; 28 | 29 | @Column() 30 | private lastName: string; 31 | 32 | @Column() 33 | private defaultPaymentType: PaymentType; 34 | 35 | @Column({ type: 'enum', enum: ClientType, default: ClientType.INDIVIDUAL }) 36 | private clientType: ClientType; 37 | 38 | @OneToMany(() => Claim, (claim) => claim.owner) 39 | public claims: Claim[]; 40 | 41 | public getClaims() { 42 | return this.claims; 43 | } 44 | 45 | public setClaims(claims: Claim[]) { 46 | this.claims = claims; 47 | } 48 | 49 | public getName() { 50 | return this.name; 51 | } 52 | 53 | public setName(name: string) { 54 | this.name = name; 55 | } 56 | 57 | public getLastName() { 58 | return this.lastName; 59 | } 60 | 61 | public setLastName(lastName: string) { 62 | this.lastName = lastName; 63 | } 64 | 65 | public getClientType() { 66 | return this.clientType; 67 | } 68 | 69 | public setClientType(clientType: ClientType) { 70 | this.clientType = clientType; 71 | } 72 | 73 | public getType() { 74 | return this.type; 75 | } 76 | 77 | public setType(type: Type) { 78 | this.type = type; 79 | } 80 | 81 | public getDefaultPaymentType() { 82 | return this.defaultPaymentType; 83 | } 84 | 85 | public setDefaultPaymentType(defaultPaymentType: PaymentType) { 86 | this.defaultPaymentType = defaultPaymentType; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/entity/contract-attachment.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; 3 | import { Contract } from './contract.entity'; 4 | 5 | export enum ContractAttachmentStatus { 6 | PROPOSED = 'proposed', 7 | ACCEPTED_BY_ONE_SIDE = 'accepted_by_one_side', 8 | ACCEPTED_BY_BOTH_SIDES = 'accepted_by_both_side', 9 | REJECTED = 'rejected', 10 | } 11 | 12 | @Entity() 13 | export class ContractAttachment extends BaseEntity { 14 | @ManyToOne(() => Contract, (contract) => contract.attachments) 15 | @JoinColumn() 16 | public contract: Contract; 17 | 18 | @Column({ type: 'bytea' }) 19 | private data: Buffer; 20 | 21 | @Column({ default: Date.now(), type: 'bigint' }) 22 | private creationDate: number; 23 | 24 | @Column({ nullable: true, type: 'bigint' }) 25 | private acceptedAt: number | null; 26 | 27 | @Column({ nullable: true, type: 'bigint' }) 28 | private rejectedAt: number | null; 29 | 30 | @Column({ nullable: true, type: 'bigint' }) 31 | private changeDate: number; 32 | 33 | @Column({ default: ContractAttachmentStatus.PROPOSED }) 34 | private status: ContractAttachmentStatus; 35 | 36 | public getData() { 37 | return this.data; 38 | } 39 | 40 | public setData(data: string) { 41 | this.data = Buffer.from( 42 | '\\x' + Buffer.from(data, 'base64').toString('hex'), 43 | ); 44 | } 45 | 46 | public getCreationDate() { 47 | return this.creationDate; 48 | } 49 | 50 | public setCreationDate(creationDate: number) { 51 | this.creationDate = creationDate; 52 | } 53 | 54 | public getAcceptedAt() { 55 | return this.acceptedAt; 56 | } 57 | 58 | public setAcceptedAt(acceptedAt: number) { 59 | this.acceptedAt = acceptedAt; 60 | } 61 | 62 | public getRejectedAt() { 63 | return this.rejectedAt; 64 | } 65 | 66 | public setRejectedAt(rejectedAt: number) { 67 | this.rejectedAt = rejectedAt; 68 | } 69 | 70 | public getChangeDate() { 71 | return this.changeDate; 72 | } 73 | 74 | public setChangeDate(changeDate: number) { 75 | this.changeDate = changeDate; 76 | } 77 | 78 | public getStatus() { 79 | return this.status; 80 | } 81 | 82 | public setStatus(status: ContractAttachmentStatus) { 83 | this.status = status; 84 | } 85 | 86 | public getContract() { 87 | return this.contract; 88 | } 89 | 90 | public setContract(contract: Contract) { 91 | this.contract = contract; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/entity/contract.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, OneToMany } from 'typeorm'; 3 | import { ContractAttachment } from './contract-attachment.entity'; 4 | 5 | export enum ContractStatus { 6 | NEGOTIATIONS_IN_PROGRESS = 'negotiations_in_progress', 7 | REJECTED = 'rejected', 8 | ACCEPTED = 'accepted', 9 | } 10 | 11 | @Entity() 12 | export class Contract extends BaseEntity { 13 | @OneToMany( 14 | () => ContractAttachment, 15 | (contractAttachment) => contractAttachment.contract, 16 | { eager: true }, 17 | ) 18 | public attachments: ContractAttachment[]; 19 | 20 | @Column() 21 | private partnerName: string; 22 | 23 | @Column() 24 | private subject: string; 25 | 26 | @Column({ default: Date.now(), type: 'bigint' }) 27 | private creationDate: number; 28 | 29 | @Column({ nullable: true, type: 'bigint' }) 30 | private acceptedAt: number | null; 31 | 32 | @Column({ nullable: true, type: 'bigint' }) 33 | private rejectedAt: number | null; 34 | 35 | @Column({ nullable: true, type: 'bigint' }) 36 | private changeDate: number | null; 37 | 38 | @Column({ default: ContractStatus.NEGOTIATIONS_IN_PROGRESS }) 39 | private status: ContractStatus; 40 | 41 | @Column() 42 | private contractNo: string; 43 | 44 | public getCreationDate() { 45 | return this.creationDate; 46 | } 47 | 48 | public setCreationDate(creationDate: number) { 49 | this.creationDate = creationDate; 50 | } 51 | 52 | public getAcceptedAt() { 53 | return this.acceptedAt; 54 | } 55 | 56 | public setAcceptedAt(acceptedAt: number) { 57 | this.acceptedAt = acceptedAt; 58 | } 59 | 60 | public getRejectedAt() { 61 | return this.rejectedAt; 62 | } 63 | 64 | public setRejectedAt(rejectedAt: number) { 65 | this.rejectedAt = rejectedAt; 66 | } 67 | 68 | public getChangeDate() { 69 | return this.changeDate; 70 | } 71 | 72 | public setChangeDate(changeDate: number) { 73 | this.changeDate = changeDate; 74 | } 75 | 76 | public getStatus() { 77 | return this.status; 78 | } 79 | 80 | public setStatus(status: ContractStatus) { 81 | this.status = status; 82 | } 83 | 84 | public getContractNo() { 85 | return this.contractNo; 86 | } 87 | 88 | public setContractNo(contractNo: string) { 89 | this.contractNo = contractNo; 90 | } 91 | 92 | public getAttachments() { 93 | return this.attachments || []; 94 | } 95 | 96 | public setAttachments(attachments: ContractAttachment[]) { 97 | this.attachments = attachments; 98 | } 99 | 100 | public getPartnerName() { 101 | return this.partnerName; 102 | } 103 | 104 | public setPartnerName(partnerName: string) { 105 | this.partnerName = partnerName; 106 | } 107 | 108 | public getSubject() { 109 | return this.subject; 110 | } 111 | 112 | public setSubject(subject: string) { 113 | this.subject = subject; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/entity/driver-attribute.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; 3 | import { Driver } from './driver.entity'; 4 | 5 | export enum DriverAttributeName { 6 | PENALTY_POINTS = 'penalty_points', 7 | NATIONALITY = 'nationality', 8 | YEARS_OF_EXPERIENCE = 'years_of_experience', 9 | MEDICAL_EXAMINATION_EXPIRATION_DATE = 'medial_examination_expiration_date', 10 | MEDICAL_EXAMINATION_REMARKS = 'medical_examination_remarks', 11 | EMAIL = 'email', 12 | BIRTHPLACE = 'birthplace', 13 | COMPANY_NAME = 'companyName', 14 | } 15 | 16 | @Entity() 17 | export class DriverAttribute extends BaseEntity { 18 | @Column() 19 | private name: DriverAttributeName; 20 | 21 | @Column() 22 | private value: string; 23 | 24 | @ManyToOne(() => Driver, (driver) => driver) 25 | @JoinColumn({ name: 'DRIVER_ID' }) 26 | public driver: Driver; 27 | 28 | constructor(driver: Driver, attr: DriverAttributeName, value: string) { 29 | super(); 30 | this.driver = driver; 31 | this.name = attr; 32 | this.value = value; 33 | } 34 | 35 | public getName() { 36 | return this.name; 37 | } 38 | 39 | public setName(name: DriverAttributeName) { 40 | this.name = name; 41 | } 42 | 43 | public getValue() { 44 | return this.value; 45 | } 46 | 47 | public setValue(value: string) { 48 | this.value = value; 49 | } 50 | 51 | public getDriver() { 52 | return this.driver; 53 | } 54 | 55 | public setDriver(driver: Driver) { 56 | this.driver = driver; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/entity/driver-fee.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Driver } from './driver.entity'; 3 | import { Column, Entity, OneToOne } from 'typeorm'; 4 | 5 | export enum FeeType { 6 | FLAT = 'flat', 7 | PERCENTAGE = 'percentage', 8 | } 9 | 10 | @Entity() 11 | export class DriverFee extends BaseEntity { 12 | @Column() 13 | private feeType: FeeType; 14 | 15 | @Column() 16 | private amount: number; 17 | 18 | @Column({ default: 0 }) 19 | private min: number; 20 | 21 | @OneToOne(() => Driver, (driver) => driver.fee) 22 | public driver: Driver; 23 | 24 | constructor(feeType: FeeType, driver: Driver, amount: number, min: number) { 25 | super(); 26 | this.feeType = feeType; 27 | this.driver = driver; 28 | this.amount = amount; 29 | this.min = min; 30 | } 31 | 32 | public getFeeType() { 33 | return this.feeType; 34 | } 35 | 36 | public setFeeType(feeType: FeeType) { 37 | this.feeType = feeType; 38 | } 39 | 40 | public getDriver() { 41 | return this.driver; 42 | } 43 | 44 | public setDriver(driver: Driver) { 45 | this.driver = driver; 46 | } 47 | 48 | public getAmount() { 49 | return this.amount; 50 | } 51 | 52 | public setAmount(amount: number) { 53 | this.amount = amount; 54 | } 55 | 56 | public getMin() { 57 | return this.min; 58 | } 59 | 60 | public setMin(min: number) { 61 | this.min = min; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/entity/driver-position.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, ManyToOne } from 'typeorm'; 3 | import { Driver } from './driver.entity'; 4 | 5 | @Entity() 6 | export class DriverPosition extends BaseEntity { 7 | @ManyToOne(() => Driver) 8 | public driver: Driver; 9 | 10 | @Column({ type: 'float' }) 11 | public latitude!: number; 12 | 13 | @Column({ type: 'float' }) 14 | public longitude: number; 15 | 16 | @Column({ type: 'bigint' }) 17 | public seenAt: number; 18 | 19 | public getDriver() { 20 | return this.driver; 21 | } 22 | 23 | public setDriver(driver: Driver) { 24 | this.driver = driver; 25 | } 26 | 27 | public getLatitude() { 28 | return this.latitude; 29 | } 30 | 31 | public setLatitude(latitude: number) { 32 | this.latitude = latitude; 33 | } 34 | 35 | public getLongitude() { 36 | return this.longitude; 37 | } 38 | 39 | public setLongitude(longitude: number) { 40 | this.longitude = longitude; 41 | } 42 | 43 | public getSeenAt() { 44 | return this.seenAt; 45 | } 46 | 47 | public setSeenAt(seenAt: number) { 48 | this.seenAt = seenAt; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/entity/driver-session.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity, ManyToOne } from 'typeorm'; 3 | import { Driver } from './driver.entity'; 4 | import { CarClass } from './car-type.entity'; 5 | 6 | @Entity() 7 | export class DriverSession extends BaseEntity { 8 | @Column({ nullable: true, type: 'bigint' }) 9 | public loggedAt: number; 10 | 11 | @Column({ nullable: true, type: 'bigint' }) 12 | private loggedOutAt: number | null; 13 | 14 | @ManyToOne(() => Driver) 15 | private driver: Driver; 16 | 17 | @Column() 18 | private platesNumber: string; 19 | 20 | @Column() 21 | private carClass: CarClass; 22 | 23 | @Column() 24 | private carBrand: string; 25 | 26 | public getLoggedAt() { 27 | return this.loggedAt; 28 | } 29 | 30 | public getCarBrand() { 31 | return this.carBrand; 32 | } 33 | 34 | public setCarBrand(carBrand: string) { 35 | this.carBrand = carBrand; 36 | } 37 | 38 | public setLoggedAt(loggedAt: number) { 39 | this.loggedAt = loggedAt; 40 | } 41 | 42 | public getLoggedOutAt() { 43 | return this.loggedOutAt; 44 | } 45 | 46 | public setLoggedOutAt(loggedOutAt: number) { 47 | this.loggedOutAt = loggedOutAt; 48 | } 49 | 50 | public getDriver() { 51 | return this.driver; 52 | } 53 | 54 | public setDriver(driver: Driver) { 55 | this.driver = driver; 56 | } 57 | 58 | public getPlatesNumber() { 59 | return this.platesNumber; 60 | } 61 | 62 | public setPlatesNumber(platesNumber: string) { 63 | this.platesNumber = platesNumber; 64 | } 65 | 66 | public getCarClass() { 67 | return this.carClass; 68 | } 69 | 70 | public setCarClass(carClass: CarClass) { 71 | this.carClass = carClass; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/entity/driver.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, OneToMany, OneToOne, JoinColumn } from 'typeorm'; 2 | import { BaseEntity } from '../common/base.entity'; 3 | import { Transit } from './transit.entity'; 4 | import { DriverAttribute } from './driver-attribute.entity'; 5 | import { DriverFee } from './driver-fee.entity'; 6 | 7 | export enum DriverStatus { 8 | INACTIVE = 'inactive', 9 | ACTIVE = 'active', 10 | } 11 | 12 | export enum DriverType { 13 | CANDIDATE = 'candidate', 14 | REGULAR = 'regular', 15 | } 16 | 17 | @Entity() 18 | export class Driver extends BaseEntity { 19 | @Column() 20 | private status: DriverStatus; 21 | 22 | @Column() 23 | private firstName: string; 24 | 25 | @Column() 26 | private lastName: string; 27 | 28 | @Column() 29 | private driverLicense: string; 30 | 31 | @Column({ nullable: true, type: 'varchar' }) 32 | private photo: string | null; 33 | 34 | @Column() 35 | private type: DriverType; 36 | 37 | @Column({ default: false }) 38 | private isOccupied: boolean; 39 | 40 | @OneToOne(() => DriverFee, (fee) => fee.driver) 41 | @JoinColumn() 42 | public fee: DriverFee; 43 | 44 | public getAttributes() { 45 | return this.attributes || []; 46 | } 47 | 48 | public setAttributes(attributes: DriverAttribute[]) { 49 | this.attributes = attributes; 50 | } 51 | 52 | @OneToMany(() => DriverAttribute, (driverAttribute) => driverAttribute.driver) 53 | public attributes: DriverAttribute[]; 54 | 55 | @OneToMany(() => Transit, (transit) => transit.driver) 56 | public transits: Transit[]; 57 | 58 | public calculateEarningsForTransit(transit: Transit) { 59 | console.log(transit); 60 | return null; 61 | // zdublować kod wyliczenia kosztu przejazdu 62 | } 63 | 64 | public setLastName(lastName: string) { 65 | this.lastName = lastName; 66 | } 67 | 68 | public setFirstName(firstName: string) { 69 | this.firstName = firstName; 70 | } 71 | 72 | public setDriverLicense(license: string) { 73 | this.driverLicense = license; 74 | } 75 | 76 | public setStatus(status: DriverStatus) { 77 | this.status = status; 78 | } 79 | 80 | public setType(type: DriverType) { 81 | this.type = type; 82 | } 83 | 84 | public setPhoto(photo: string) { 85 | this.photo = photo; 86 | } 87 | 88 | public getLastName() { 89 | return this.lastName; 90 | } 91 | 92 | public getFirstName() { 93 | return this.firstName; 94 | } 95 | 96 | public getDriverLicense() { 97 | return this.driverLicense; 98 | } 99 | 100 | public getStatus() { 101 | return this.status; 102 | } 103 | 104 | public getType() { 105 | return this.type; 106 | } 107 | 108 | public getPhoto() { 109 | return this.photo; 110 | } 111 | 112 | public getFee() { 113 | return this.fee; 114 | } 115 | 116 | public setFee(fee: DriverFee) { 117 | this.fee = fee; 118 | } 119 | 120 | public getOccupied() { 121 | return this.isOccupied; 122 | } 123 | 124 | public setOccupied(isOccupied: boolean) { 125 | this.isOccupied = isOccupied; 126 | } 127 | 128 | public getTransits() { 129 | return this.transits || []; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/entity/invoice.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../common/base.entity'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | @Entity() 5 | export class Invoice extends BaseEntity { 6 | @Column() 7 | private amount: number; 8 | 9 | @Column() 10 | private subjectName: string; 11 | 12 | constructor(amount: number, subjectName: string) { 13 | super(); 14 | this.amount = amount; 15 | this.subjectName = subjectName; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/entity/transit.entity.ts: -------------------------------------------------------------------------------- 1 | import { ForbiddenException } from '@nestjs/common'; 2 | import { BaseEntity } from '../common/base.entity'; 3 | import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from 'typeorm'; 4 | import { Driver } from './driver.entity'; 5 | import { Client, PaymentType } from './client.entity'; 6 | import { Address } from './address.entity'; 7 | import { CarClass } from './car-type.entity'; 8 | 9 | export enum Status { 10 | DRAFT = 'draft', 11 | CANCELLED = 'cancelled', 12 | WAITING_FOR_DRIVER_ASSIGNMENT = 'waiting_for_driver_assignment', 13 | DRIVER_ASSIGNMENT_FAILED = 'driver_assignment_failed', 14 | TRANSIT_TO_PASSENGER = 'transit_to_passenger', 15 | IN_TRANSIT = 'in_transit', 16 | COMPLETED = 'completed', 17 | } 18 | 19 | export enum DriverPaymentStatus { 20 | NOT_PAID = 'not_paid', 21 | PAID = 'paid', 22 | CLAIMED = 'claimed', 23 | RETURNED = 'returned', 24 | } 25 | 26 | export enum ClientPaymentStatus { 27 | NOT_PAID = 'not_paid', 28 | PAID = 'paid', 29 | RETURNED = 'returned', 30 | } 31 | 32 | export enum Month { 33 | JANUARY, 34 | FEBRUARY, 35 | MARCH, 36 | APRIL, 37 | MAY, 38 | JUNE, 39 | JULY, 40 | AUGUST, 41 | SEPTEMBER, 42 | OCTOBER, 43 | NOVEMBER, 44 | DECEMBER, 45 | } 46 | 47 | export enum DayOfWeek { 48 | SUNDAY, 49 | MONDAY, 50 | TUESDAY, 51 | WEDNESDAY, 52 | THURSDAY, 53 | FRIDAY, 54 | SATURDAY, 55 | } 56 | 57 | @Entity() 58 | export class Transit extends BaseEntity { 59 | public static readonly BASE_FEE = 8; 60 | 61 | @ManyToOne(() => Driver, (driver) => driver.transits, { eager: true }) 62 | public driver: Driver | null; 63 | 64 | @Column({ nullable: true }) 65 | private driverPaymentStatus: DriverPaymentStatus; 66 | 67 | @Column({ nullable: true }) 68 | private clientPaymentStatus: ClientPaymentStatus; 69 | 70 | @Column({ nullable: true }) 71 | private paymentType: PaymentType; 72 | 73 | @Column() 74 | private status: Status; 75 | 76 | @Column({ type: 'bigint', nullable: true }) 77 | private date: number; 78 | 79 | @ManyToOne(() => Address, { eager: true }) 80 | @JoinColumn() 81 | private from: Address; 82 | 83 | @ManyToOne(() => Address, { eager: true }) 84 | @JoinColumn() 85 | private to: Address; 86 | 87 | @Column({ nullable: true, type: 'bigint' }) 88 | public acceptedAt: number | null; 89 | 90 | @Column({ nullable: true, type: 'bigint' }) 91 | public started: number | null; 92 | 93 | @Column({ default: 0 }) 94 | public pickupAddressChangeCounter: number; 95 | 96 | @ManyToMany(() => Driver) 97 | public driversRejections: Driver[]; 98 | 99 | @ManyToMany(() => Driver) 100 | public proposedDrivers: Driver[]; 101 | 102 | @Column({ default: 0, type: 'integer' }) 103 | public awaitingDriversResponses: number; 104 | 105 | @Column({ nullable: true, type: 'varchar' }) 106 | public factor: number | null; 107 | 108 | @Column({ nullable: false, default: 0 }) 109 | private km: number; 110 | 111 | // https://stackoverflow.com/questions/37107123/sould-i-store-price-as-decimal-or-integer-in-mysql 112 | @Column({ nullable: true, type: 'integer' }) 113 | private price: number | null; 114 | 115 | @Column({ nullable: true, type: 'integer' }) 116 | private estimatedPrice: number | null; 117 | 118 | @Column({ nullable: true }) 119 | private driversFee: number; 120 | 121 | @Column({ type: 'bigint', nullable: true }) 122 | public dateTime: number; 123 | 124 | @Column({ type: 'bigint', nullable: true }) 125 | private published: number; 126 | 127 | @ManyToOne(() => Client, { eager: true }) 128 | @JoinColumn() 129 | private client: Client; 130 | 131 | @Column() 132 | private carType: CarClass; 133 | 134 | @Column({ type: 'bigint', nullable: true }) 135 | private completeAt: number; 136 | 137 | public getCarType() { 138 | return this.carType as CarClass; 139 | } 140 | 141 | public setCarType(carType: CarClass) { 142 | this.carType = carType; 143 | } 144 | 145 | public getDriver() { 146 | return this.driver; 147 | } 148 | 149 | public getPrice() { 150 | return this.price; 151 | } 152 | 153 | //just for testing 154 | public setPrice(price: number) { 155 | this.price = price; 156 | } 157 | 158 | public getStatus() { 159 | return this.status; 160 | } 161 | 162 | public setStatus(status: Status) { 163 | this.status = status; 164 | } 165 | 166 | public getCompleteAt() { 167 | return this.completeAt; 168 | } 169 | 170 | public getClient() { 171 | return this.client; 172 | } 173 | 174 | public setClient(client: Client) { 175 | this.client = client; 176 | } 177 | 178 | public setDateTime(dateTime: number) { 179 | this.dateTime = dateTime; 180 | } 181 | 182 | public getDateTime() { 183 | return this.dateTime; 184 | } 185 | 186 | public getPublished() { 187 | return this.published; 188 | } 189 | 190 | public setPublished(published: number) { 191 | this.published = published; 192 | } 193 | 194 | public setDriver(driver: Driver | null) { 195 | this.driver = driver; 196 | } 197 | 198 | public getKm() { 199 | return this.km; 200 | } 201 | 202 | public setKm(km: number) { 203 | this.km = km; 204 | this.estimateCost(); 205 | } 206 | 207 | public getAwaitingDriversResponses() { 208 | return this.awaitingDriversResponses; 209 | } 210 | 211 | public setAwaitingDriversResponses(proposedDriversCounter: number) { 212 | this.awaitingDriversResponses = proposedDriversCounter; 213 | } 214 | 215 | public getDriversRejections() { 216 | return this.driversRejections || []; 217 | } 218 | 219 | public setDriversRejections(driversRejections: Driver[]) { 220 | this.driversRejections = driversRejections; 221 | } 222 | 223 | public getProposedDrivers() { 224 | return this.proposedDrivers || []; 225 | } 226 | 227 | public setProposedDrivers(proposedDrivers: Driver[]) { 228 | this.proposedDrivers = proposedDrivers; 229 | } 230 | 231 | public getAcceptedAt() { 232 | return this.acceptedAt; 233 | } 234 | 235 | public setAcceptedAt(acceptedAt: number) { 236 | this.acceptedAt = acceptedAt; 237 | } 238 | 239 | public getStarted() { 240 | return this.started; 241 | } 242 | 243 | public setStarted(started: number) { 244 | this.started = started; 245 | } 246 | 247 | public getFrom() { 248 | return this.from; 249 | } 250 | 251 | public setFrom(from: Address) { 252 | this.from = from; 253 | } 254 | 255 | public getTo() { 256 | return this.to; 257 | } 258 | 259 | public setTo(to: Address) { 260 | this.to = to; 261 | } 262 | 263 | public getPickupAddressChangeCounter() { 264 | return this.pickupAddressChangeCounter; 265 | } 266 | 267 | public setPickupAddressChangeCounter(pickupChanges: number) { 268 | this.pickupAddressChangeCounter = pickupChanges; 269 | } 270 | 271 | public setCompleteAt(when: number) { 272 | this.completeAt = when; 273 | } 274 | 275 | public getDriversFee() { 276 | return this.driversFee; 277 | } 278 | 279 | public setDriversFee(driversFee: number) { 280 | this.driversFee = driversFee; 281 | } 282 | 283 | public getEstimatedPrice() { 284 | return this.estimatedPrice; 285 | } 286 | 287 | public setEstimatedPrice(estimatedPrice: number) { 288 | this.estimatedPrice = estimatedPrice; 289 | } 290 | 291 | public estimateCost() { 292 | if (this.status === Status.COMPLETED) { 293 | throw new ForbiddenException( 294 | 'Estimating cost for completed transit is forbidden, id = ' + 295 | this.getId(), 296 | ); 297 | } 298 | 299 | this.estimatedPrice = this.calculateCost(); 300 | 301 | this.price = null; 302 | 303 | return this.estimatedPrice; 304 | } 305 | 306 | public calculateFinalCosts(): number { 307 | if (this.status === Status.COMPLETED) { 308 | return this.calculateCost(); 309 | } else { 310 | throw new ForbiddenException( 311 | 'Cannot calculate final cost if the transit is not completed', 312 | ); 313 | } 314 | } 315 | 316 | private calculateCost(): number { 317 | let baseFee = Transit.BASE_FEE; 318 | let factorToCalculate = this.factor; 319 | if (factorToCalculate == null) { 320 | factorToCalculate = 1; 321 | } 322 | let kmRate: number; 323 | const day = new Date(); 324 | // wprowadzenie nowych cennikow od 1.01.2019 325 | if (day.getFullYear() <= 2018) { 326 | kmRate = 1.0; 327 | baseFee++; 328 | } else { 329 | if ( 330 | (day.getMonth() == Month.DECEMBER && day.getDate() == 31) || 331 | (day.getMonth() == Month.JANUARY && 332 | day.getDate() == 1 && 333 | day.getHours() <= 6) 334 | ) { 335 | kmRate = 3.5; 336 | baseFee += 3; 337 | } else { 338 | // piątek i sobota po 17 do 6 następnego dnia 339 | if ( 340 | (day.getDay() == DayOfWeek.FRIDAY && day.getHours() >= 17) || 341 | (day.getDay() == DayOfWeek.SATURDAY && day.getHours() <= 6) || 342 | (day.getDay() == DayOfWeek.SATURDAY && day.getHours() >= 17) || 343 | (day.getDay() == DayOfWeek.SUNDAY && day.getHours() <= 6) 344 | ) { 345 | kmRate = 2.5; 346 | baseFee += 2; 347 | } else { 348 | // pozostałe godziny weekendu 349 | if ( 350 | (day.getDay() == DayOfWeek.SATURDAY && 351 | day.getHours() > 6 && 352 | day.getHours() < 17) || 353 | (day.getDay() == DayOfWeek.SUNDAY && day.getHours() > 6) 354 | ) { 355 | kmRate = 1.5; 356 | } else { 357 | // tydzień roboczy 358 | kmRate = 1.0; 359 | baseFee++; 360 | } 361 | } 362 | } 363 | } 364 | const priceBigDecimal = Number( 365 | (this.km * kmRate * factorToCalculate + baseFee).toFixed(2), 366 | ); 367 | this.price = priceBigDecimal; 368 | return this.price; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(process.env.PORT || 3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /src/repository/address.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Address } from '../entity/address.entity'; 3 | 4 | @EntityRepository(Address) 5 | export class AddressRepository extends Repository
{ 6 | // FIX ME: To replace with getOrCreate method instead of that? 7 | // Actual workaround for address uniqueness problem: assign result from repo.save to variable for later usage 8 | //@ts-expect-error to avoid params error 9 | public async save(address: Address) { 10 | if (!address.getId()) { 11 | const existingAddress = await this.findOne({ 12 | where: { hash: address.getHash() }, 13 | }); 14 | if (existingAddress) { 15 | return existingAddress; 16 | } 17 | } 18 | 19 | return super.save(address); 20 | } 21 | 22 | public async findByHash(hash: string) { 23 | return this.findOne({ 24 | where: { hash }, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/repository/awarded-miles.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { AwardedMiles } from '../entity/awarded-miles.entity'; 3 | import { Client } from '../entity/client.entity'; 4 | 5 | @EntityRepository(AwardedMiles) 6 | export class AwardedMilesRepository extends Repository { 7 | public async findAllByClient(client: Client) { 8 | return this.find({ where: { client } }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/repository/awards-account.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { AwardsAccount } from '../entity/awards-account.entity'; 3 | import { Client } from '../entity/client.entity'; 4 | 5 | @EntityRepository(AwardsAccount) 6 | export class AwardsAccountRepository extends Repository { 7 | public async findByClient(client: Client) { 8 | return this.findOne({ where: { client } }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/repository/car-type.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; 3 | import { NotFoundException } from '@nestjs/common'; 4 | 5 | @EntityRepository(CarType) 6 | export class CarTypeRepository extends Repository { 7 | public async findByCarClass(carClass: CarClass): Promise { 8 | const carType = await this.findOne({ where: { carClass } }); 9 | 10 | if (!carType) { 11 | throw new NotFoundException('Cannot find car type'); 12 | } 13 | return carType; 14 | } 15 | 16 | public async findByStatus(status: CarStatus): Promise { 17 | return this.find({ where: { status } }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/repository/claim-attachment.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { ClaimAttachment } from '../entity/claim-attachment.entity'; 3 | 4 | @EntityRepository(ClaimAttachment) 5 | export class ClaimAttachmentRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/repository/claim.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Claim } from '../entity/claim.entity'; 3 | import { Client } from '../entity/client.entity'; 4 | import { Transit } from '../entity/transit.entity'; 5 | 6 | @EntityRepository(Claim) 7 | export class ClaimRepository extends Repository { 8 | public async findByOwner(owner: Client) { 9 | return this.find({ where: { owner } }); 10 | } 11 | 12 | public async findByOwnerAndTransit(owner: Client, transit: Transit) { 13 | return this.find({ where: { owner, transit } }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/repository/client.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Client } from '../entity/client.entity'; 3 | 4 | @EntityRepository(Client) 5 | export class ClientRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/repository/contract-attachment.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { ContractAttachment } from '../entity/contract-attachment.entity'; 3 | import { Contract } from '../entity/contract.entity'; 4 | 5 | @EntityRepository(ContractAttachment) 6 | export class ContractAttachmentRepository extends Repository { 7 | public async findByContract(contract: Contract) { 8 | return this.find({ where: { contract } }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/repository/contract.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Contract } from '../entity/contract.entity'; 3 | 4 | @EntityRepository(Contract) 5 | export class ContractRepository extends Repository { 6 | public async findByPartnerName(partnerName: string) { 7 | return this.find({ where: { partnerName } }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/repository/driver-attribute.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { DriverAttribute } from '../entity/driver-attribute.entity'; 3 | 4 | @EntityRepository(DriverAttribute) 5 | export class DriverAttributeRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/repository/driver-fee.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { DriverFee } from '../entity/driver-fee.entity'; 3 | import { Driver } from '../entity/driver.entity'; 4 | 5 | @EntityRepository(DriverFee) 6 | export class DriverFeeRepository extends Repository { 7 | public async findByDriver(driver: Driver) { 8 | return this.findOne({ driver }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/repository/driver-position.repository.ts: -------------------------------------------------------------------------------- 1 | import { Between, EntityRepository, Repository } from 'typeorm'; 2 | import { DriverPosition } from '../entity/driver-position.entity'; 3 | import { Driver } from '../entity/driver.entity'; 4 | import { DriverPositionV2Dto } from '../dto/driver-position-v2.dto'; 5 | 6 | @EntityRepository(DriverPosition) 7 | export class DriverPositionRepository extends Repository { 8 | public async findAverageDriverPositionSince( 9 | latitudeMin: number, 10 | latitudeMax: number, 11 | longitudeMin: number, 12 | longitudeMax: number, 13 | date: number, 14 | ): Promise { 15 | const driverPosition = await this.createQueryBuilder('driverPosition') 16 | .leftJoinAndSelect('driverPosition.driver', 'p') 17 | .select(`AVG(p.latitude), AVG(p.longitude), MAX(p.seenAt)`) 18 | .where('p.longitude between :longitudeMin and :longitudeMax') 19 | .andWhere('p.longitude between :longitudeMin and :longitudeMax') 20 | .andWhere('p.seenAt >= :seenAt') 21 | .groupBy('p.driver.id') 22 | .setParameters({ 23 | longitudeMin, 24 | longitudeMax, 25 | seenAt: date, 26 | }) 27 | .getMany(); 28 | 29 | return driverPosition.map( 30 | (dp) => 31 | new DriverPositionV2Dto( 32 | dp.driver, 33 | dp.latitude, 34 | dp.longitude, 35 | dp.seenAt, 36 | ), 37 | ); 38 | } 39 | 40 | public async findByDriverAndSeenAtBetweenOrderBySeenAtAsc( 41 | driver: Driver, 42 | from: number, 43 | to: number, 44 | ): Promise { 45 | return this.find({ 46 | where: { 47 | driver, 48 | seenAt: Between(from, to), 49 | }, 50 | order: { 51 | seenAt: 'ASC', 52 | }, 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/repository/driver-session.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, MoreThan, Repository, IsNull } from 'typeorm'; 2 | import { DriverSession } from '../entity/driver-session.entity'; 3 | import { CarClass } from '../entity/car-type.entity'; 4 | import { Driver } from '../entity/driver.entity'; 5 | import { NotFoundException } from '@nestjs/common'; 6 | 7 | @EntityRepository(DriverSession) 8 | export class DriverSessionRepository extends Repository { 9 | public async findAllByLoggedOutAtNullAndDriverInAndCarClassIn( 10 | drivers: Driver[], 11 | carClasses: CarClass[], 12 | ): Promise { 13 | console.log('To implement...', drivers, carClasses); 14 | return []; 15 | } 16 | 17 | public async findTopByDriverAndLoggedOutAtIsNullOrderByLoggedAtDesc( 18 | driver: Driver, 19 | ): Promise { 20 | const session = await this.findOne({ 21 | where: { driver, loggedOutAt: IsNull() }, 22 | order: { 23 | loggedAt: 'DESC', 24 | }, 25 | }); 26 | 27 | if (!session) { 28 | throw new NotFoundException(`Session for ${driver.getId()} not exists`); 29 | } 30 | return session; 31 | } 32 | 33 | public async findAllByDriverAndLoggedAtAfter( 34 | driver: Driver, 35 | since: number, 36 | ): Promise { 37 | return this.find({ 38 | where: { 39 | driver, 40 | loggedAt: MoreThan(since), 41 | }, 42 | }); 43 | } 44 | 45 | public async findByDriver(driver: Driver): Promise { 46 | return this.find({ where: { driver } }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/repository/driver.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Driver } from '../entity/driver.entity'; 3 | 4 | @EntityRepository(Driver) 5 | export class DriverRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/repository/invoice.repository.ts: -------------------------------------------------------------------------------- 1 | import { EntityRepository, Repository } from 'typeorm'; 2 | import { Invoice } from '../entity/invoice.entity'; 3 | 4 | @EntityRepository(Invoice) 5 | export class InvoiceRepository extends Repository {} 6 | -------------------------------------------------------------------------------- /src/repository/transit.repository.ts: -------------------------------------------------------------------------------- 1 | import { Between, EntityRepository, Repository } from 'typeorm'; 2 | import { Status, Transit } from '../entity/transit.entity'; 3 | import { Driver } from '../entity/driver.entity'; 4 | import { Client } from '../entity/client.entity'; 5 | import { Address } from '../entity/address.entity'; 6 | 7 | @EntityRepository(Transit) 8 | export class TransitRepository extends Repository { 9 | public async findAllByDriverAndDateTimeBetween( 10 | driver: Driver, 11 | from: number, 12 | to: number, 13 | ): Promise { 14 | return await this.find({ 15 | where: { 16 | driver, 17 | dateTime: Between(from, to), 18 | }, 19 | }); 20 | } 21 | 22 | public async findAllByClientAndFromAndStatusOrderByDateTimeDesc( 23 | client: Client, 24 | from: Address, 25 | status: Status, 26 | ): Promise { 27 | return await this.find({ 28 | where: { 29 | client, 30 | from, 31 | status, 32 | }, 33 | order: { 34 | dateTime: 'DESC', 35 | }, 36 | }); 37 | } 38 | 39 | public async findAllByClientAndFromAndPublishedAfterAndStatusOrderByDateTimeDesc( 40 | client: Client, 41 | from: Address, 42 | when: number, 43 | status: Status, 44 | ): Promise { 45 | return this.find({ 46 | where: { 47 | client, 48 | from, 49 | status, 50 | published: when, 51 | }, 52 | order: { 53 | dateTime: 'DESC', 54 | }, 55 | }); 56 | } 57 | 58 | public async findByClient(client: Client): Promise { 59 | return this.find({ 60 | where: { 61 | client, 62 | }, 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/service/awards.service.ts: -------------------------------------------------------------------------------- 1 | import { AwardsAccountDto } from '../dto/awards-account.dto'; 2 | import { AwardedMiles } from '../entity/awarded-miles.entity'; 3 | import { 4 | Injectable, 5 | NotFoundException, 6 | NotAcceptableException, 7 | } from '@nestjs/common'; 8 | import { InjectRepository } from '@nestjs/typeorm'; 9 | import { ClientRepository } from '../repository/client.repository'; 10 | import { TransitRepository } from '../repository/transit.repository'; 11 | import { AppProperties } from '../config/app-properties.config'; 12 | import { AwardsAccountRepository } from '../repository/awards-account.repository'; 13 | import { AwardedMilesRepository } from '../repository/awarded-miles.repository'; 14 | import { AwardsAccount } from '../entity/awards-account.entity'; 15 | import dayjs from 'dayjs'; 16 | import { Client, Type } from '../entity/client.entity'; 17 | import orderBy from 'lodash.orderby'; 18 | 19 | export interface IAwardsService { 20 | findBy: (clientId: string) => Promise; 21 | 22 | registerToProgram: (clientId: string) => Promise; 23 | 24 | activateAccount: (clientId: string) => Promise; 25 | 26 | deactivateAccount: (clientId: string) => Promise; 27 | 28 | registerMiles: ( 29 | clientId: string, 30 | transitId: string, 31 | ) => Promise; 32 | 33 | registerSpecialMiles: ( 34 | clientId: string, 35 | miles: number, 36 | ) => Promise; 37 | 38 | removeMiles: (clientId: string, miles: number) => Promise; 39 | 40 | calculateBalance: (clientId: string) => Promise; 41 | 42 | transferMiles: ( 43 | fromClientId: string, 44 | toClientId: string, 45 | miles: number, 46 | ) => Promise; 47 | } 48 | 49 | @Injectable() 50 | export class AwardsService implements IAwardsService { 51 | constructor( 52 | @InjectRepository(ClientRepository) 53 | private clientRepository: ClientRepository, 54 | @InjectRepository(TransitRepository) 55 | private transitRepository: TransitRepository, 56 | @InjectRepository(AwardsAccountRepository) 57 | private accountRepository: AwardsAccountRepository, 58 | @InjectRepository(AwardedMilesRepository) 59 | private milesRepository: AwardedMilesRepository, 60 | private appProperties: AppProperties, 61 | ) {} 62 | 63 | public async findBy(clientId: string): Promise { 64 | const account = await this.getAccountForClient(clientId); 65 | 66 | return new AwardsAccountDto(account); 67 | } 68 | 69 | public async registerToProgram(clientId: string) { 70 | const client = await this.clientRepository.findOne(clientId); 71 | 72 | if (!client) { 73 | throw new NotFoundException('Client does not exists, id = ' + clientId); 74 | } 75 | 76 | const account = new AwardsAccount(); 77 | 78 | account.setClient(client); 79 | account.setActive(false); 80 | account.setDate(Date.now()); 81 | 82 | await this.accountRepository.save(account); 83 | } 84 | 85 | public async activateAccount(clientId: string) { 86 | const account = await this.getAccountForClient(clientId); 87 | 88 | account.setActive(true); 89 | 90 | await this.accountRepository.save(account); 91 | } 92 | 93 | public async deactivateAccount(clientId: string) { 94 | const account = await this.getAccountForClient(clientId); 95 | 96 | account.setActive(false); 97 | 98 | await this.accountRepository.save(account); 99 | } 100 | 101 | public async registerMiles(clientId: string, transitId: string) { 102 | const account = await this.getAccountForClient(clientId); 103 | const transit = await this.transitRepository.findOne(transitId); 104 | 105 | if (!transit) { 106 | throw new NotFoundException('transit does not exists, id = ' + transitId); 107 | } 108 | 109 | const now = Date.now(); 110 | if (!account.isAwardActive()) { 111 | return null; 112 | } else { 113 | const miles = new AwardedMiles(); 114 | miles.setTransit(transit); 115 | miles.setDate(Date.now()); 116 | miles.setClient(account.getClient()); 117 | miles.setMiles(this.appProperties.getDefaultMilesBonus()); 118 | miles.setExpirationDate( 119 | dayjs(now) 120 | .add(this.appProperties.getMilesExpirationInDays(), 'days') 121 | .valueOf(), 122 | ); 123 | miles.setSpecial(false); 124 | account.increaseTransactions(); 125 | 126 | await this.milesRepository.save(miles); 127 | await this.accountRepository.save(account); 128 | return miles; 129 | } 130 | } 131 | 132 | public async registerSpecialMiles(clientId: string, miles: number) { 133 | const account = await this.getAccountForClient(clientId); 134 | 135 | const _miles = new AwardedMiles(); 136 | _miles.setTransit(null); 137 | _miles.setClient(account.getClient()); 138 | _miles.setMiles(miles); 139 | _miles.setDate(Date.now()); 140 | _miles.setSpecial(true); 141 | account.increaseTransactions(); 142 | await this.milesRepository.save(_miles); 143 | await this.accountRepository.save(account); 144 | return _miles; 145 | } 146 | 147 | public async removeMiles(clientId: string, miles: number) { 148 | const client = await this.clientRepository.findOne(clientId); 149 | if (!client) { 150 | throw new NotFoundException( 151 | `Client with id ${clientId} doest not exists`, 152 | ); 153 | } 154 | 155 | const account = await this.accountRepository.findByClient(client); 156 | 157 | if (!account) { 158 | throw new NotFoundException( 159 | `Awards account for client id ${clientId} doest not exists`, 160 | ); 161 | } else { 162 | const balance = await this.calculateBalance(clientId); 163 | if (balance >= miles && account.isAwardActive()) { 164 | let milesList = await this.milesRepository.findAllByClient(client); 165 | const transitsCounter = ( 166 | await this.transitRepository.findByClient(client) 167 | ).length; 168 | 169 | // TODO: verify below sorter 170 | if (client.getClaims().length >= 3) { 171 | // milesList.sort(Comparator.comparing(AwardedMiles::getExpirationDate, Comparators.nullsHigh()).reversed().thenComparing(Comparators.nullsHigh())); 172 | milesList = orderBy(milesList, [(item) => item.getExpirationDate()]); 173 | } else if (client.getType() === Type.VIP) { 174 | // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getExpirationDate, Comparators.nullsLow())); 175 | milesList = orderBy(milesList, [ 176 | (item) => item.getSpecial(), 177 | (item) => item.getExpirationDate(), 178 | ]); 179 | } else if (transitsCounter >= 15 && this.isSunday()) { 180 | // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getExpirationDate, Comparators.nullsLow())); 181 | milesList = orderBy(milesList, [ 182 | (item) => item.getSpecial(), 183 | (item) => item.getExpirationDate(), 184 | ]); 185 | } else if (transitsCounter >= 15) { 186 | // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getDate)); 187 | milesList = orderBy(milesList, [ 188 | (item) => item.getSpecial(), 189 | (item) => item.getDate(), 190 | ]); 191 | } else { 192 | // milesList.sort(Comparator.comparing(AwardedMiles::getDate)); 193 | milesList = orderBy(milesList, (item) => item.getDate()); 194 | } 195 | for (const iter of milesList) { 196 | if (miles <= 0) { 197 | break; 198 | } 199 | if ( 200 | iter.getSpecial() || 201 | (iter.getExpirationDate() && 202 | dayjs(iter.getExpirationDate()).isAfter(dayjs())) 203 | ) { 204 | if (iter.getMiles() <= miles) { 205 | miles -= iter.getMiles(); 206 | iter.setMiles(0); 207 | } else { 208 | iter.setMiles(iter.getMiles() - miles); 209 | miles = 0; 210 | } 211 | await this.milesRepository.save(iter); 212 | } 213 | } 214 | } else { 215 | throw new NotAcceptableException( 216 | 'Insufficient miles, id = ' + 217 | clientId + 218 | ', miles requested = ' + 219 | miles, 220 | ); 221 | } 222 | } 223 | } 224 | 225 | public async calculateBalance(clientId: string) { 226 | const client = await this.clientRepository.findOne(clientId); 227 | if (!client) { 228 | throw new NotFoundException( 229 | `Client with id ${clientId} doest not exists`, 230 | ); 231 | } 232 | const milesList = await this.milesRepository.findAllByClient(client); 233 | 234 | const sum = milesList 235 | .filter( 236 | (t) => 237 | (t.getExpirationDate() != null && 238 | t.getExpirationDate() && 239 | dayjs(t.getExpirationDate()).isAfter(dayjs())) || 240 | t.getSpecial(), 241 | ) 242 | .map((t) => t.getMiles()) 243 | .reduce((prev, curr) => prev + curr, 0); 244 | 245 | return sum; 246 | } 247 | 248 | public async transferMiles( 249 | fromClientId: string, 250 | toClientId: string, 251 | miles: number, 252 | ) { 253 | const fromClient = await this.clientRepository.findOne(fromClientId); 254 | if (!fromClient) { 255 | throw new NotFoundException( 256 | `Client with id ${fromClientId} doest not exists`, 257 | ); 258 | } 259 | const accountFrom = await this.getAccountForClient(fromClient); 260 | const accountTo = await this.getAccountForClient(toClientId); 261 | 262 | const balanceFromClient = await this.calculateBalance(fromClientId); 263 | if (balanceFromClient >= miles && accountFrom.isAwardActive()) { 264 | const milesList = await this.milesRepository.findAllByClient(fromClient); 265 | 266 | for (const iter of milesList) { 267 | if ( 268 | iter.getSpecial() || 269 | dayjs(iter.getExpirationDate()).isAfter(dayjs()) 270 | ) { 271 | if (iter.getMiles() <= miles) { 272 | iter.setClient(accountTo.getClient()); 273 | miles -= iter.getMiles(); 274 | } else { 275 | iter.setMiles(iter.getMiles() - miles); 276 | const _miles = new AwardedMiles(); 277 | 278 | _miles.setClient(accountTo.getClient()); 279 | _miles.setSpecial(iter.getSpecial() ?? false); 280 | _miles.setExpirationDate(iter.getExpirationDate() || Date.now()); 281 | _miles.setMiles(miles); 282 | 283 | miles -= iter.getMiles(); 284 | 285 | await this.milesRepository.save(_miles); 286 | } 287 | await this.milesRepository.save(iter); 288 | } 289 | } 290 | 291 | accountFrom.increaseTransactions(); 292 | accountTo.increaseTransactions(); 293 | 294 | await this.accountRepository.save(accountFrom); 295 | await this.accountRepository.save(accountTo); 296 | } 297 | } 298 | 299 | private isSunday() { 300 | return dayjs().get('day') === 0; 301 | } 302 | 303 | private async getAccountForClient(clientId: string | Client) { 304 | const client = 305 | typeof clientId === 'string' 306 | ? await this.clientRepository.findOne(clientId) 307 | : clientId; 308 | if (!client) { 309 | throw new NotFoundException( 310 | `Client with id ${clientId} doest not exists`, 311 | ); 312 | } 313 | 314 | const account = await this.accountRepository.findByClient(client); 315 | 316 | if (!account) { 317 | throw new NotFoundException( 318 | `Awards account for client id ${clientId} doest not exists`, 319 | ); 320 | } 321 | 322 | return account; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/service/car-type.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { CarTypeRepository } from '../repository/car-type.repository'; 4 | import { AppProperties } from '../config/app-properties.config'; 5 | import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; 6 | import { CreateCarTypeDto } from '../dto/create-car-type.dto'; 7 | import { CarTypeDto } from '../dto/car-type.dto'; 8 | 9 | @Injectable() 10 | export class CarTypeService { 11 | constructor( 12 | @InjectRepository(CarTypeRepository) 13 | private carTypeRepository: CarTypeRepository, 14 | private readonly appProperties: AppProperties, 15 | ) {} 16 | 17 | public async load(id: string) { 18 | const carType = await this.carTypeRepository.findOne(id); 19 | if (!carType) { 20 | throw new NotFoundException('Cannot find car type'); 21 | } 22 | return carType; 23 | } 24 | 25 | public async loadDto(id: string): Promise { 26 | const carType = await this.load(id); 27 | return new CarTypeDto(carType); 28 | } 29 | 30 | public async create(carTypeDTO: CreateCarTypeDto): Promise { 31 | try { 32 | const byCarClass = await this.carTypeRepository.findByCarClass( 33 | carTypeDTO.carClass, 34 | ); 35 | byCarClass.setDescription(carTypeDTO.description); 36 | return this.carTypeRepository.save(byCarClass); 37 | } catch { 38 | const carType = new CarType( 39 | carTypeDTO.carClass, 40 | carTypeDTO.description, 41 | this.getMinNumberOfCars(carTypeDTO.carClass), 42 | ); 43 | 44 | return this.carTypeRepository.save(carType); 45 | } 46 | } 47 | 48 | public async activate(id: string) { 49 | const carType = await this.load(id); 50 | 51 | carType.activate(); 52 | 53 | await this.carTypeRepository.save(carType); 54 | } 55 | 56 | public async deactivate(id: string) { 57 | const carType = await this.load(id); 58 | 59 | carType.deactivate(); 60 | 61 | await this.carTypeRepository.save(carType); 62 | } 63 | 64 | public async registerCar(carClass: CarClass) { 65 | const carType = await this.carTypeRepository.findByCarClass(carClass); 66 | 67 | carType.registerCar(); 68 | 69 | await this.carTypeRepository.save(carType); 70 | } 71 | 72 | public async unregisterCar(carClass: CarClass) { 73 | const carType = await this.carTypeRepository.findByCarClass(carClass); 74 | 75 | carType.unregisterCar(); 76 | 77 | await this.carTypeRepository.save(carType); 78 | } 79 | 80 | public async registerActiveCar(carClass: CarClass) { 81 | const carType = await this.carTypeRepository.findByCarClass(carClass); 82 | 83 | carType.registerActiveCar(); 84 | 85 | await this.carTypeRepository.save(carType); 86 | } 87 | 88 | public async unregisterActiveCar(carClass: CarClass) { 89 | const carType = await this.carTypeRepository.findByCarClass(carClass); 90 | 91 | carType.unregisterActiveCar(); 92 | 93 | await this.carTypeRepository.save(carType); 94 | } 95 | 96 | public async findActiveCarClasses() { 97 | const cars = await this.carTypeRepository.findByStatus(CarStatus.ACTIVE); 98 | return cars.map((car) => car.getCarClass()); 99 | } 100 | 101 | public async removeCarType(carClass: CarClass) { 102 | const carType = await this.carTypeRepository.findByCarClass(carClass); 103 | if (carType) { 104 | await this.carTypeRepository.delete(carType); 105 | } 106 | } 107 | 108 | private async findByCarClass(carClass: CarClass) { 109 | const byCarClass = this.carTypeRepository.findByCarClass(carClass); 110 | if (!byCarClass) { 111 | throw new NotFoundException(`Car class does not exist: ${carClass}`); 112 | } 113 | return byCarClass; 114 | } 115 | 116 | private getMinNumberOfCars(carClass: CarClass) { 117 | if (carClass === CarClass.ECO) { 118 | return this.appProperties.getMinNoOfCarsForEcoClass(); 119 | } else { 120 | return 10; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/service/claim-number-generator.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from '@nestjs/typeorm'; 2 | import { Injectable } from '@nestjs/common'; 3 | import * as dayjs from 'dayjs'; 4 | import { ClaimRepository } from '../repository/claim.repository'; 5 | import { Claim } from '../entity/claim.entity'; 6 | 7 | @Injectable() 8 | export class ClaimNumberGenerator { 9 | constructor( 10 | @InjectRepository(ClaimRepository) 11 | private claimRepository: ClaimRepository, 12 | ) {} 13 | 14 | public async generate(claim: Claim) { 15 | const count = await this.claimRepository.count(); 16 | let prefix = count; 17 | if (count === 0) { 18 | prefix = 1; 19 | } 20 | const DATE_TIME_FORMAT = 'dd/MM/yyyy'; 21 | return ( 22 | prefix + 23 | count + 24 | '---' + 25 | dayjs(claim.getCreationDate()).format(DATE_TIME_FORMAT) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/service/claim.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { ClientRepository } from '../repository/client.repository'; 4 | import { TransitRepository } from '../repository/transit.repository'; 5 | import { AppProperties } from '../config/app-properties.config'; 6 | import { ClaimRepository } from '../repository/claim.repository'; 7 | import { ClaimNumberGenerator } from './claim-number-generator.service'; 8 | import { AwardsService } from './awards.service'; 9 | import { ClientNotificationService } from './client-notification.service'; 10 | import { DriverNotificationService } from './driver-notification.service'; 11 | import { ClaimDto } from '../dto/claim.dto'; 12 | import { Claim, ClaimStatus, CompletionMode } from '../entity/claim.entity'; 13 | import { Type } from '../entity/client.entity'; 14 | 15 | @Injectable() 16 | export class ClaimService { 17 | constructor( 18 | @InjectRepository(ClientRepository) 19 | private clientRepository: ClientRepository, 20 | @InjectRepository(TransitRepository) 21 | private transitRepository: TransitRepository, 22 | @InjectRepository(ClaimRepository) 23 | private claimRepository: ClaimRepository, 24 | private claimNumberGenerator: ClaimNumberGenerator, 25 | private awardsService: AwardsService, 26 | private clientNotificationService: ClientNotificationService, 27 | private driverNotificationService: DriverNotificationService, 28 | private appProperties: AppProperties, 29 | ) {} 30 | 31 | public async create(claimDTO: ClaimDto): Promise { 32 | let claim = new Claim(); 33 | claim.setCreationDate(Date.now()); 34 | claim.setClaimNo(await this.claimNumberGenerator.generate(claim)); 35 | claim = await this.update(claimDTO, claim); 36 | return claim; 37 | } 38 | 39 | public async find(id: string): Promise { 40 | const claim = await this.claimRepository.findOne(id); 41 | if (!claim) { 42 | throw new NotFoundException('Claim does not exists'); 43 | } 44 | return claim; 45 | } 46 | 47 | public async update(claimDTO: ClaimDto, claim: Claim) { 48 | const client = await this.clientRepository.findOne(claimDTO.getClientId()); 49 | const transit = await this.transitRepository.findOne( 50 | claimDTO.getTransitId(), 51 | ); 52 | if (client == null) { 53 | throw new NotFoundException('Client does not exists'); 54 | } 55 | if (transit == null) { 56 | throw new NotFoundException('Transit does not exists'); 57 | } 58 | if (claimDTO.isDraft()) { 59 | claim.setStatus(ClaimStatus.DRAFT); 60 | } else { 61 | claim.setStatus(ClaimStatus.NEW); 62 | } 63 | claim.setOwner(client); 64 | claim.setTransit(transit); 65 | claim.setCreationDate(Date.now()); 66 | claim.setReason(claimDTO.getReason()); 67 | claim.setIncidentDescription(claimDTO.getIncidentDescription()); 68 | return this.claimRepository.save(claim); 69 | } 70 | 71 | public async setStatus(newStatus: ClaimStatus, id: string) { 72 | const claim = await this.find(id); 73 | claim.setStatus(newStatus); 74 | await this.claimRepository.save(claim); 75 | return claim; 76 | } 77 | 78 | public async tryToResolveAutomatically(id: string): Promise { 79 | const claim = await this.find(id); 80 | if ( 81 | ( 82 | await this.claimRepository.findByOwnerAndTransit( 83 | claim.getOwner(), 84 | claim.getTransit(), 85 | ) 86 | ).length > 1 87 | ) { 88 | claim.setStatus(ClaimStatus.ESCALATED); 89 | claim.setCompletionDate(Date.now()); 90 | claim.setChangeDate(Date.now()); 91 | claim.setCompletionMode(CompletionMode.MANUAL); 92 | return claim; 93 | } 94 | if ( 95 | (await this.claimRepository.findByOwner(claim.getOwner())).length <= 3 96 | ) { 97 | claim.setStatus(ClaimStatus.REFUNDED); 98 | claim.setCompletionDate(Date.now()); 99 | claim.setChangeDate(Date.now()); 100 | claim.setCompletionMode(CompletionMode.AUTOMATIC); 101 | await this.clientNotificationService.notifyClientAboutRefund( 102 | claim.getClaimNo(), 103 | claim.getOwner().getId(), 104 | ); 105 | return claim; 106 | } 107 | if (claim.getOwner().getType() === Type.VIP) { 108 | if ( 109 | (claim.getTransit().getPrice() ?? 0) < 110 | this.appProperties.getAutomaticRefundForVipThreshold() 111 | ) { 112 | claim.setStatus(ClaimStatus.REFUNDED); 113 | claim.setCompletionDate(Date.now()); 114 | claim.setChangeDate(Date.now()); 115 | claim.setCompletionMode(CompletionMode.AUTOMATIC); 116 | await this.clientNotificationService.notifyClientAboutRefund( 117 | claim.getClaimNo(), 118 | claim.getOwner().getId(), 119 | ); 120 | await this.awardsService.registerSpecialMiles( 121 | claim.getOwner().getId(), 122 | 10, 123 | ); 124 | } else { 125 | claim.setStatus(ClaimStatus.ESCALATED); 126 | claim.setCompletionDate(Date.now()); 127 | claim.setChangeDate(Date.now()); 128 | claim.setCompletionMode(CompletionMode.MANUAL); 129 | const driver = claim.getTransit().getDriver(); 130 | if (driver) { 131 | await this.driverNotificationService.askDriverForDetailsAboutClaim( 132 | claim.getClaimNo(), 133 | driver.getId(), 134 | ); 135 | } 136 | } 137 | } else { 138 | if ( 139 | (await this.transitRepository.findByClient(claim.getOwner())).length >= 140 | this.appProperties.getNoOfTransitsForClaimAutomaticRefund() 141 | ) { 142 | if ( 143 | (claim.getTransit().getPrice() ?? 0) < 144 | this.appProperties.getAutomaticRefundForVipThreshold() 145 | ) { 146 | claim.setStatus(ClaimStatus.REFUNDED); 147 | claim.setCompletionDate(Date.now()); 148 | claim.setChangeDate(Date.now()); 149 | claim.setCompletionMode(CompletionMode.AUTOMATIC); 150 | await this.clientNotificationService.notifyClientAboutRefund( 151 | claim.getClaimNo(), 152 | claim.getOwner().getId(), 153 | ); 154 | } else { 155 | claim.setStatus(ClaimStatus.ESCALATED); 156 | claim.setCompletionDate(Date.now()); 157 | claim.setChangeDate(Date.now()); 158 | claim.setCompletionMode(CompletionMode.MANUAL); 159 | await this.clientNotificationService.askForMoreInformation( 160 | claim.getClaimNo(), 161 | claim.getOwner().getId(), 162 | ); 163 | } 164 | } else { 165 | claim.setStatus(ClaimStatus.ESCALATED); 166 | claim.setCompletionDate(Date.now()); 167 | claim.setChangeDate(Date.now()); 168 | claim.setCompletionMode(CompletionMode.MANUAL); 169 | await this.driverNotificationService.askDriverForDetailsAboutClaim( 170 | claim.getClaimNo(), 171 | claim.getOwner().getId(), 172 | ); 173 | } 174 | } 175 | 176 | return claim; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/service/client-notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class ClientNotificationService { 5 | public notifyClientAboutRefund(claimNo: string, clientId: string) { 6 | // ... 7 | console.log(`To implement...`); 8 | console.log({ claimNo, clientId }); 9 | } 10 | 11 | public askForMoreInformation(claimNo: string, clientId: string) { 12 | // ... 13 | console.log(`To implement...`); 14 | console.log({ claimNo, clientId }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/service/client.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from '@nestjs/typeorm'; 2 | import { Injectable, NotFoundException } from '@nestjs/common'; 3 | import { ClientRepository } from '../repository/client.repository'; 4 | import { Client, PaymentType, Type } from '../entity/client.entity'; 5 | import { ClientDto } from '../dto/client.dto'; 6 | 7 | @Injectable() 8 | export class ClientService { 9 | constructor( 10 | @InjectRepository(ClientRepository) 11 | private clientRepository: ClientRepository, 12 | ) {} 13 | 14 | public async registerClient( 15 | name: string, 16 | lastName: string, 17 | type: Type, 18 | paymentType: PaymentType, 19 | ) { 20 | const client = new Client(); 21 | client.setName(name); 22 | client.setLastName(lastName); 23 | client.setType(type); 24 | client.setDefaultPaymentType(paymentType); 25 | return this.clientRepository.save(client); 26 | } 27 | 28 | public async changeDefaultPaymentType( 29 | clientId: string, 30 | paymentType: PaymentType, 31 | ) { 32 | const client = await this.clientRepository.findOne(clientId); 33 | if (!client) { 34 | throw new NotFoundException('Client does not exists, id = ' + clientId); 35 | } 36 | client.setDefaultPaymentType(paymentType); 37 | await this.clientRepository.save(client); 38 | } 39 | 40 | public async upgradeToVIP(clientId: string) { 41 | const client = await this.clientRepository.findOne(clientId); 42 | if (!client) { 43 | throw new NotFoundException('Client does not exists, id = ' + clientId); 44 | } 45 | client.setType(Type.VIP); 46 | await this.clientRepository.save(client); 47 | } 48 | 49 | public async downgradeToRegular(clientId: string) { 50 | const client = await this.clientRepository.findOne(clientId); 51 | if (!client) { 52 | throw new NotFoundException('Client does not exists, id = ' + clientId); 53 | } 54 | client.setType(Type.NORMAL); 55 | await this.clientRepository.save(client); 56 | } 57 | 58 | public async load(id: string) { 59 | const client = await this.clientRepository.findOne(id); 60 | if (!client) { 61 | throw new NotFoundException('Client does not exists, id = ' + id); 62 | } 63 | return new ClientDto(client); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/service/contract.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotFoundException, 4 | NotAcceptableException, 5 | } from '@nestjs/common'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | import { ContractRepository } from '../repository/contract.repository'; 8 | import { ContractAttachmentRepository } from '../repository/contract-attachment.repository'; 9 | import { ContractDto } from '../dto/contract.dto'; 10 | import { Contract, ContractStatus } from '../entity/contract.entity'; 11 | import { 12 | ContractAttachment, 13 | ContractAttachmentStatus, 14 | } from '../entity/contract-attachment.entity'; 15 | import { ContractAttachmentDto } from '../dto/contract-attachment.dto'; 16 | import { CreateContractDto } from '../dto/create-contract.dto'; 17 | import { CreateContractAttachmentDto } from '../dto/create-contract-attachment.dto'; 18 | 19 | @Injectable() 20 | export class ContractService { 21 | constructor( 22 | @InjectRepository(ContractRepository) 23 | private contractRepository: ContractRepository, 24 | @InjectRepository(ContractAttachmentRepository) 25 | private contractAttachmentRepository: ContractAttachmentRepository, 26 | ) {} 27 | 28 | public async createContract(createContractDto: CreateContractDto) { 29 | const contract = new Contract(); 30 | contract.setPartnerName(createContractDto.partnerName); 31 | contract.setCreationDate(Date.now()); 32 | const partnerContractsCount = 33 | ( 34 | await this.contractRepository.findByPartnerName( 35 | createContractDto.partnerName, 36 | ) 37 | ).length + 1; 38 | contract.setSubject(createContractDto.subject); 39 | contract.setContractNo( 40 | 'C/' + partnerContractsCount + '/' + createContractDto.partnerName, 41 | ); 42 | return this.contractRepository.save(contract); 43 | } 44 | 45 | public async acceptContract(id: string) { 46 | const contract = await this.find(id); 47 | const attachments = await this.contractAttachmentRepository.findByContract( 48 | contract, 49 | ); 50 | if ( 51 | attachments.every( 52 | (a) => 53 | a.getStatus() === ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES, 54 | ) 55 | ) { 56 | contract.setStatus(ContractStatus.ACCEPTED); 57 | contract.setAcceptedAt(Date.now()); 58 | await this.contractAttachmentRepository.save(attachments); 59 | await this.contractRepository.save(contract); 60 | } else { 61 | throw new NotAcceptableException( 62 | 'Not all attachments accepted by both sides', 63 | ); 64 | } 65 | } 66 | 67 | public async rejectContract(id: string) { 68 | const contract = await this.find(id); 69 | contract.setStatus(ContractStatus.REJECTED); 70 | await this.contractRepository.save(contract); 71 | } 72 | 73 | public async rejectAttachment(attachmentId: string) { 74 | const contractAttachment = await this.contractAttachmentRepository.findOne( 75 | attachmentId, 76 | ); 77 | if (!contractAttachment) { 78 | throw new NotFoundException('Contract attachment does not exist'); 79 | } 80 | contractAttachment.setStatus(ContractAttachmentStatus.REJECTED); 81 | contractAttachment.setRejectedAt(Date.now()); 82 | await this.contractAttachmentRepository.save(contractAttachment); 83 | } 84 | 85 | public async acceptAttachment(attachmentId: string) { 86 | const contractAttachment = await this.contractAttachmentRepository.findOne( 87 | attachmentId, 88 | ); 89 | if (!contractAttachment) { 90 | throw new NotFoundException('Contract attachment does not exist'); 91 | } 92 | 93 | if ( 94 | contractAttachment.getStatus() === 95 | ContractAttachmentStatus.ACCEPTED_BY_ONE_SIDE || 96 | contractAttachment.getStatus() === 97 | ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES 98 | ) { 99 | contractAttachment.setStatus( 100 | ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES, 101 | ); 102 | } else { 103 | contractAttachment.setStatus( 104 | ContractAttachmentStatus.ACCEPTED_BY_ONE_SIDE, 105 | ); 106 | } 107 | 108 | contractAttachment.setAcceptedAt(Date.now()); 109 | 110 | await this.contractAttachmentRepository.save(contractAttachment); 111 | } 112 | 113 | public async find(id: string) { 114 | const contract = await this.contractRepository.findOne(id); 115 | if (!contract) { 116 | throw new NotFoundException('Contract does not exist'); 117 | } 118 | return contract; 119 | } 120 | 121 | public async findDto(id: string) { 122 | return new ContractDto(await this.find(id)); 123 | } 124 | 125 | public async proposeAttachment( 126 | contractId: string, 127 | contractAttachmentDto: CreateContractAttachmentDto, 128 | ) { 129 | const contract = await this.find(contractId); 130 | const contractAttachment = new ContractAttachment(); 131 | contractAttachment.setContract(contract); 132 | contractAttachment.setData(contractAttachmentDto.data); 133 | await this.contractAttachmentRepository.save(contractAttachment); 134 | contract.getAttachments().push(contractAttachment); 135 | await this.contractRepository.save(contract); 136 | return new ContractAttachmentDto(contractAttachment); 137 | } 138 | 139 | public async removeAttachment(contractId: string, attachmentId: string) { 140 | //TODO sprawdzenie czy nalezy do kontraktu (JIRA: II-14455) 141 | await this.contractAttachmentRepository.delete(attachmentId); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/service/distance-calculator.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class DistanceCalculator { 5 | public static degreesToRadians(degrees: number) { 6 | return degrees * (Math.PI / 180); 7 | } 8 | 9 | public calculateByMap( 10 | latitudeFrom: number, 11 | longitudeFrom: number, 12 | latitudeTo: number, 13 | longitudeTo: number, 14 | ) { 15 | // ... 16 | console.log({ latitudeFrom, longitudeFrom, latitudeTo, longitudeTo }); 17 | return 42; 18 | } 19 | 20 | public calculateByGeo( 21 | latitudeFrom: number, 22 | longitudeFrom: number, 23 | latitudeTo: number, 24 | longitudeTo: number, 25 | ) { 26 | // https://www.geeksforgeeks.org/program-distance-two-points-earth/ 27 | // The math module contains a function 28 | // named toRadians which converts from 29 | // degrees to radians. 30 | const lon1 = DistanceCalculator.degreesToRadians(longitudeFrom); 31 | const lon2 = DistanceCalculator.degreesToRadians(longitudeTo); 32 | const lat1 = DistanceCalculator.degreesToRadians(latitudeFrom); 33 | const lat2 = DistanceCalculator.degreesToRadians(latitudeTo); 34 | 35 | // Haversine formula 36 | const dlon = lon2 - lon1; 37 | const dlat = lat2 - lat1; 38 | const a = 39 | Math.pow(Math.sin(dlat / 2), 2) + 40 | Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2); 41 | 42 | const c = 2 * Math.asin(Math.sqrt(a)); 43 | 44 | // Radius of earth in kilometers. Use 3956 for miles 45 | const r = 6371; 46 | 47 | // calculate the result 48 | const distanceInKMeters = c * r; 49 | 50 | return distanceInKMeters; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/service/driver-fee.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { DriverFeeRepository } from '../repository/driver-fee.repository'; 4 | import { TransitRepository } from '../repository/transit.repository'; 5 | import { FeeType } from '../entity/driver-fee.entity'; 6 | 7 | @Injectable() 8 | export class DriverFeeService { 9 | constructor( 10 | @InjectRepository(DriverFeeRepository) 11 | private driverFeeRepository: DriverFeeRepository, 12 | @InjectRepository(TransitRepository) 13 | private transitRepository: TransitRepository, 14 | ) {} 15 | 16 | public async calculateDriverFee(transitId: string) { 17 | const transit = await this.transitRepository.findOne(transitId); 18 | if (!transit) { 19 | throw new NotFoundException('transit does not exist, id = ' + transitId); 20 | } 21 | if (transit.getDriversFee() != null) { 22 | return transit.getDriversFee(); 23 | } 24 | const transitPrice = transit.getPrice() ?? 0; 25 | 26 | const driver = transit.getDriver(); 27 | 28 | if (!driver) { 29 | throw new NotFoundException( 30 | 'driver not exist for transit = ' + transitId, 31 | ); 32 | } 33 | const driverFee = await this.driverFeeRepository.findByDriver(driver); 34 | if (!driverFee) { 35 | throw new NotFoundException( 36 | 'driver Fees not defined for driver, driver id = ' + driver.getId(), 37 | ); 38 | } 39 | let finalFee; 40 | if (driverFee.getFeeType() === FeeType.FLAT) { 41 | finalFee = transitPrice - driverFee.getAmount(); 42 | } else { 43 | finalFee = (transitPrice * driverFee.getAmount()) / 100; 44 | } 45 | 46 | return Math.max( 47 | finalFee, 48 | driverFee.getMin() == null ? 0 : driverFee.getMin(), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/service/driver-notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class DriverNotificationService { 5 | public notifyAboutPossibleTransit(driverId: string, transitId: string) { 6 | // ... 7 | console.log(`To implement...`); 8 | console.log({ driverId, transitId }); 9 | } 10 | 11 | public notifyAboutChangedTransitAddress(driverId: string, transitId: string) { 12 | // ... 13 | console.log(`To implement...`); 14 | console.log({ driverId, transitId }); 15 | } 16 | 17 | public notifyAboutCancelledTransit(driverId: string, transitId: string) { 18 | // ... 19 | console.log(`To implement...`); 20 | console.log({ driverId, transitId }); 21 | } 22 | 23 | public askDriverForDetailsAboutClaim(claimNo: string, driverId: string) { 24 | // ... 25 | console.log(`To implement...`); 26 | console.log({ driverId, claimNo }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/service/driver-session.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { DriverRepository } from '../repository/driver.repository'; 4 | import { DriverSessionRepository } from '../repository/driver-session.repository'; 5 | import { CarTypeService } from './car-type.service'; 6 | import { CarClass } from '../entity/car-type.entity'; 7 | import { DriverSession } from '../entity/driver-session.entity'; 8 | 9 | @Injectable() 10 | export class DriverSessionService { 11 | constructor( 12 | @InjectRepository(DriverSessionRepository) 13 | private driverSessionRepository: DriverSessionRepository, 14 | @InjectRepository(DriverRepository) 15 | private driverRepository: DriverRepository, 16 | private carTypeService: CarTypeService, 17 | ) {} 18 | 19 | public async logIn( 20 | driverId: string, 21 | plateNumber: string, 22 | carClass: CarClass, 23 | carBrand: string, 24 | ) { 25 | const session = new DriverSession(); 26 | const driver = await this.driverRepository.findOne(driverId); 27 | 28 | if (!driver) { 29 | throw new NotFoundException(`Driver with id ${driverId} not exists`); 30 | } 31 | session.setDriver(driver); 32 | session.setLoggedAt(Date.now()); 33 | session.setCarClass(carClass); 34 | session.setPlatesNumber(plateNumber); 35 | session.setCarBrand(carBrand); 36 | await this.carTypeService.registerActiveCar(session.getCarClass()); 37 | return this.driverSessionRepository.save(session); 38 | } 39 | 40 | public async logOut(sessionId: string) { 41 | const session = await this.driverSessionRepository.findOne(sessionId); 42 | if (!session) { 43 | throw new NotFoundException('Session does not exist'); 44 | } 45 | await this.carTypeService.unregisterCar(session.getCarClass()); 46 | session.setLoggedOutAt(Date.now()); 47 | 48 | await this.driverSessionRepository.save(session); 49 | } 50 | 51 | public async logOutCurrentSession(driverId: string) { 52 | const driver = await this.driverRepository.findOne(driverId); 53 | 54 | if (!driver) { 55 | throw new NotFoundException(`Driver with id ${driverId} not exists`); 56 | } 57 | 58 | const session = 59 | await this.driverSessionRepository.findTopByDriverAndLoggedOutAtIsNullOrderByLoggedAtDesc( 60 | driver, 61 | ); 62 | if (session) { 63 | session.setLoggedOutAt(Date.now()); 64 | await this.carTypeService.unregisterCar(session.getCarClass()); 65 | await this.driverSessionRepository.save(session); 66 | } 67 | } 68 | 69 | public async findByDriver(driverId: string): Promise { 70 | const driver = await this.driverRepository.findOne(driverId); 71 | 72 | if (!driver) { 73 | throw new NotFoundException(`Driver with id ${driverId} not exists`); 74 | } 75 | 76 | return this.driverSessionRepository.findByDriver(driver); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/service/driver-tracking.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotFoundException, 4 | NotAcceptableException, 5 | } from '@nestjs/common'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | import { DriverRepository } from '../repository/driver.repository'; 8 | import { DriverPositionRepository } from '../repository/driver-position.repository'; 9 | import { DistanceCalculator } from './distance-calculator.service'; 10 | import { DriverPosition } from '../entity/driver-position.entity'; 11 | import { DriverStatus } from '../entity/driver.entity'; 12 | 13 | @Injectable() 14 | export class DriverTrackingService { 15 | constructor( 16 | @InjectRepository(DriverRepository) 17 | private driverRepository: DriverRepository, 18 | @InjectRepository(DriverPositionRepository) 19 | private positionRepository: DriverPositionRepository, 20 | private distanceCalculator: DistanceCalculator, 21 | ) {} 22 | 23 | public async registerPosition( 24 | driverId: string, 25 | latitude: number, 26 | longitude: number, 27 | ): Promise { 28 | const driver = await this.driverRepository.findOne(driverId); 29 | if (!driver) { 30 | throw new NotFoundException('Driver does not exists, id = ' + driverId); 31 | } 32 | if (driver.getStatus() !== DriverStatus.ACTIVE) { 33 | throw new NotAcceptableException( 34 | 'Driver is not active, cannot register position, id = ' + driverId, 35 | ); 36 | } 37 | const position = new DriverPosition(); 38 | position.setDriver(driver); 39 | position.setSeenAt(Date.now()); 40 | position.setLatitude(latitude); 41 | position.setLongitude(longitude); 42 | return await this.positionRepository.save(position); 43 | } 44 | 45 | public async calculateTravelledDistance( 46 | driverId: string, 47 | from: number, 48 | to: number, 49 | ) { 50 | const driver = await this.driverRepository.findOne(driverId); 51 | if (!driver) { 52 | throw new NotFoundException('Driver does not exists, id = ' + driverId); 53 | } 54 | const positions = 55 | await this.positionRepository.findByDriverAndSeenAtBetweenOrderBySeenAtAsc( 56 | driver, 57 | from, 58 | to, 59 | ); 60 | let distanceTravelled = 0; 61 | 62 | if (positions.length > 1) { 63 | let previousPosition = positions[0]; 64 | 65 | for (const position of positions.slice(1)) { 66 | distanceTravelled += this.distanceCalculator.calculateByGeo( 67 | previousPosition.getLatitude(), 68 | previousPosition.getLongitude(), 69 | position.getLatitude(), 70 | position.getLongitude(), 71 | ); 72 | 73 | previousPosition = position; 74 | } 75 | } 76 | 77 | return distanceTravelled; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/service/driver.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotAcceptableException, 4 | NotFoundException, 5 | ForbiddenException, 6 | } from '@nestjs/common'; 7 | import { Driver, DriverStatus, DriverType } from '../entity/driver.entity'; 8 | import { DriverDto } from '../dto/driver.dto'; 9 | import { CreateDriverDto } from '../dto/create-driver.dto'; 10 | import { DriverRepository } from '../repository/driver.repository'; 11 | import { InjectRepository } from '@nestjs/typeorm'; 12 | import { DriverAttributeRepository } from '../repository/driver-attribute.repository'; 13 | import { TransitRepository } from '../repository/transit.repository'; 14 | import { DriverFeeService } from './driver-fee.service'; 15 | import dayjs from 'dayjs'; 16 | 17 | @Injectable() 18 | export class DriverService { 19 | public static DRIVER_LICENSE_REGEX = '^[A-Z9]{5}\\d{6}[A-Z9]{2}\\d[A-Z]{2}$'; 20 | 21 | constructor( 22 | @InjectRepository(DriverRepository) 23 | private driverRepository: DriverRepository, 24 | @InjectRepository(DriverAttributeRepository) 25 | private driverAttributeRepository: DriverAttributeRepository, 26 | @InjectRepository(DriverRepository) 27 | private transitRepository: TransitRepository, 28 | private driverFeeService: DriverFeeService, 29 | ) {} 30 | 31 | public async createDriver({ 32 | photo, 33 | driverLicense, 34 | lastName, 35 | firstName, 36 | }: CreateDriverDto): Promise { 37 | const driver = new Driver(); 38 | if (driver.getStatus() === DriverStatus.ACTIVE) { 39 | if ( 40 | !driverLicense || 41 | !driverLicense.match(DriverService.DRIVER_LICENSE_REGEX) 42 | ) { 43 | throw new NotAcceptableException( 44 | 'Illegal license no = ' + driverLicense, 45 | ); 46 | } 47 | } 48 | driver.setDriverLicense(driverLicense); 49 | driver.setLastName(lastName); 50 | driver.setFirstName(firstName); 51 | driver.setStatus(DriverStatus.INACTIVE); 52 | driver.setType(DriverType.CANDIDATE); 53 | if (photo !== null) { 54 | if (Buffer.from(photo, 'base64').toString('base64') === photo) { 55 | driver.setPhoto(photo); 56 | } else { 57 | throw new NotAcceptableException('Illegal photo in base64'); 58 | } 59 | } 60 | 61 | return this.driverRepository.save(driver); 62 | } 63 | 64 | public async loadDriver(driverId: string): Promise { 65 | const driver = await this.driverRepository.findOne(driverId); 66 | 67 | if (!driver) { 68 | throw new NotFoundException( 69 | `Driver with id ${driverId} does not exists.`, 70 | ); 71 | } 72 | 73 | return new DriverDto(driver); 74 | } 75 | 76 | public async changeDriverStatus(driverId: string, status: DriverStatus) { 77 | const driver = await this.driverRepository.findOne(driverId); 78 | 79 | if (!driver) { 80 | throw new NotFoundException( 81 | `Driver with id ${driverId} does not exists.`, 82 | ); 83 | } 84 | if (status === DriverStatus.ACTIVE) { 85 | const license = driver.getDriverLicense(); 86 | 87 | if (!license) { 88 | throw new ForbiddenException( 89 | `Status cannot be ACTIVE. Illegal license no ${license}`, 90 | ); 91 | } 92 | } 93 | 94 | driver.setStatus(status); 95 | await this.driverRepository.update(driver.getId(), driver); 96 | } 97 | 98 | public async changeLicenseNumber(newLicense: string, driverId: string) { 99 | const driver = await this.driverRepository.findOne(driverId); 100 | 101 | if (!driver) { 102 | throw new NotFoundException( 103 | `Driver with id ${driverId} does not exists.`, 104 | ); 105 | } 106 | if (!newLicense || !newLicense.match(DriverService.DRIVER_LICENSE_REGEX)) { 107 | throw new NotAcceptableException( 108 | 'Illegal new license no = ' + newLicense, 109 | ); 110 | } 111 | 112 | if (!(driver.getStatus() === DriverStatus.ACTIVE)) { 113 | throw new NotAcceptableException( 114 | 'Driver is not active, cannot change license', 115 | ); 116 | } 117 | 118 | driver.setDriverLicense(newLicense); 119 | await this.driverRepository.save(driver); 120 | } 121 | 122 | public async changePhoto(driverId: string, photo: string) { 123 | const driver = await this.driverRepository.findOne(driverId); 124 | 125 | if (!driver) { 126 | throw new NotFoundException( 127 | `Driver with id ${driverId} does not exists.`, 128 | ); 129 | } 130 | 131 | if (!photo || Buffer.from(photo, 'base64').toString('base64') === photo) { 132 | throw new NotAcceptableException('Illegal photo in base64'); 133 | } 134 | driver.setPhoto(photo); 135 | await this.driverRepository.save(driver); 136 | } 137 | 138 | public async calculateDriverMonthlyPayment( 139 | driverId: string, 140 | year: number, 141 | month: number, 142 | ) { 143 | const driver = await this.driverRepository.findOne(driverId); 144 | 145 | if (!driver) { 146 | throw new NotFoundException( 147 | `Driver with id ${driverId} does not exists.`, 148 | ); 149 | } 150 | 151 | const yearMonth = dayjs(`${year}-${month}`, 'YYYY-M'); 152 | const from = yearMonth.startOf('month'); 153 | const to = yearMonth.endOf('month'); 154 | 155 | const transitsList = 156 | await this.transitRepository.findAllByDriverAndDateTimeBetween( 157 | driver, 158 | from.valueOf(), 159 | to.valueOf(), 160 | ); 161 | 162 | const sum = ( 163 | await Promise.all( 164 | transitsList.map((t) => 165 | this.driverFeeService.calculateDriverFee(t.getId()), 166 | ), 167 | ) 168 | ).reduce((prev, curr) => prev + curr, 0); 169 | 170 | return sum; 171 | } 172 | 173 | public async calculateDriverYearlyPayment( 174 | driverId: string, 175 | year: number, 176 | ): Promise> { 177 | const payments = new Map(); 178 | const months = Array.from({ length: 5 }).map((_, i) => i); 179 | for (const m of months) { 180 | payments.set(m, this.calculateDriverMonthlyPayment(driverId, year, m)); 181 | } 182 | return payments; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/service/geocoding.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Address } from '../entity/address.entity'; 3 | 4 | @Injectable() 5 | export class GeocodingService { 6 | public geocodeAddress(address: Address) { 7 | //TODO ... call do zewnętrznego serwisu 8 | console.log('To implement', address); 9 | const geocoded = [0, 1]; 10 | 11 | geocoded[0] = 1; //latitude 12 | geocoded[1] = 1; //longitude 13 | 14 | return geocoded; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/service/invoice-generator.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { InvoiceRepository } from '../repository/invoice.repository'; 4 | import { Invoice } from '../entity/invoice.entity'; 5 | 6 | @Injectable() 7 | export class InvoiceGenerator { 8 | constructor( 9 | @InjectRepository(InvoiceRepository) 10 | private invoiceRepository: InvoiceRepository, 11 | ) {} 12 | 13 | public async generate(amount: number, subjectName: string) { 14 | return this.invoiceRepository.save(new Invoice(amount, subjectName)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/service/transit-analyzer.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from '@nestjs/typeorm'; 2 | import { ClientRepository } from '../repository/client.repository'; 3 | import { TransitRepository } from '../repository/transit.repository'; 4 | import { AddressRepository } from '../repository/address.repository'; 5 | import { Address } from '../entity/address.entity'; 6 | import { Injectable, NotFoundException } from '@nestjs/common'; 7 | import { Status, Transit } from '../entity/transit.entity'; 8 | import { Client } from '../entity/client.entity'; 9 | import dayjs from 'dayjs'; 10 | 11 | @Injectable() 12 | export class TransitAnalyzerService { 13 | constructor( 14 | @InjectRepository(ClientRepository) 15 | private clientRepository: ClientRepository, 16 | @InjectRepository(TransitRepository) 17 | private transitRepository: TransitRepository, 18 | @InjectRepository(AddressRepository) 19 | private addressRepository: AddressRepository, 20 | ) {} 21 | 22 | public async analyze( 23 | clientId: string, 24 | addressId: string, 25 | ): Promise { 26 | const client = await this.clientRepository.findOne(clientId); 27 | if (!client) { 28 | throw new NotFoundException('Client does not exists, id = ' + clientId); 29 | } 30 | const address = await this.addressRepository.findOne(addressId); 31 | if (!address) { 32 | throw new NotFoundException('Address does not exists, id = ' + addressId); 33 | } 34 | return this._analyze(client, address, null); 35 | } 36 | 37 | // Brace yourself, deadline is coming... They made me to do it this way. 38 | // Tested! 39 | private async _analyze( 40 | client: Client, 41 | from: Address, 42 | t: Transit | null, 43 | ): Promise { 44 | let ts: Transit[] = []; 45 | 46 | if (!t) { 47 | ts = 48 | await this.transitRepository.findAllByClientAndFromAndStatusOrderByDateTimeDesc( 49 | client, 50 | from, 51 | Status.COMPLETED, 52 | ); 53 | } else { 54 | ts = 55 | await this.transitRepository.findAllByClientAndFromAndPublishedAfterAndStatusOrderByDateTimeDesc( 56 | client, 57 | from, 58 | t.getPublished(), 59 | Status.COMPLETED, 60 | ); 61 | } 62 | 63 | // Workaround for performance reasons. 64 | if (ts.length > 1000 && client.getId() == '666') { 65 | // No one will see a difference for this customer ;) 66 | ts = ts.slice(0, 1000); 67 | } 68 | 69 | // if (ts.isEmpty()) { 70 | // return List.of(t.getTo()); 71 | // } 72 | 73 | if (t) { 74 | ts = ts.filter((_t) => 75 | dayjs(t.getCompleteAt()).add(15, 'minutes').isAfter(_t.getStarted()), 76 | ); 77 | // Before 2018-01-01: 78 | //.filter(t -> t.getCompleteAt().plus(15, ChronoUnit.MINUTES).isAfter(t.getPublished())) 79 | } 80 | 81 | if (!ts.length && t) { 82 | return [t.getTo()]; 83 | } 84 | 85 | const mappedTs: Address[][] = []; 86 | 87 | for (const _t of ts) { 88 | const result = []; 89 | result.push(_t.getFrom()); 90 | result.push(...(await this._analyze(client, _t.getTo(), _t))); 91 | mappedTs.push(result); 92 | } 93 | 94 | function compare(a: Address[], b: Address[]) { 95 | if (a.length > b.length) return -1; 96 | if (a.length < b.length) return 1; 97 | return 0; 98 | } 99 | 100 | mappedTs.sort(compare); 101 | 102 | return mappedTs[0]?.length ? mappedTs[0] : []; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/service/transit.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotAcceptableException, 4 | NotFoundException, 5 | } from '@nestjs/common'; 6 | import { InjectRepository } from '@nestjs/typeorm'; 7 | import { ClientRepository } from '../repository/client.repository'; 8 | import { TransitRepository } from '../repository/transit.repository'; 9 | import { DriverRepository } from '../repository/driver.repository'; 10 | import { DriverPositionRepository } from '../repository/driver-position.repository'; 11 | import { DriverSessionRepository } from '../repository/driver-session.repository'; 12 | import { AddressRepository } from '../repository/address.repository'; 13 | import { AwardsService } from './awards.service'; 14 | import { DriverFeeService } from './driver-fee.service'; 15 | import { CarTypeService } from './car-type.service'; 16 | import { GeocodingService } from './geocoding.service'; 17 | import { InvoiceGenerator } from './invoice-generator.service'; 18 | import { DistanceCalculator } from './distance-calculator.service'; 19 | import { TransitDto } from '../dto/transit.dto'; 20 | import { AddressDto } from '../dto/address.dto'; 21 | import { CarClass } from '../entity/car-type.entity'; 22 | import { Address } from '../entity/address.entity'; 23 | import { Status, Transit } from '../entity/transit.entity'; 24 | import { DriverNotificationService } from './driver-notification.service'; 25 | import * as dayjs from 'dayjs'; 26 | import { DriverStatus } from '../entity/driver.entity'; 27 | import { DriverPositionV2Dto } from '../dto/driver-position-v2.dto'; 28 | import { CreateTransitDto } from '../dto/create-transit.dto'; 29 | 30 | @Injectable() 31 | export class TransitService { 32 | constructor( 33 | @InjectRepository(ClientRepository) 34 | private clientRepository: ClientRepository, 35 | @InjectRepository(TransitRepository) 36 | private transitRepository: TransitRepository, 37 | @InjectRepository(DriverRepository) 38 | private driverRepository: DriverRepository, 39 | @InjectRepository(DriverPositionRepository) 40 | private driverPositionRepository: DriverPositionRepository, 41 | @InjectRepository(DriverSessionRepository) 42 | private driverSessionRepository: DriverSessionRepository, 43 | @InjectRepository(AddressRepository) 44 | private addressRepository: AddressRepository, 45 | private awardsService: AwardsService, 46 | private driverFeeService: DriverFeeService, 47 | private carTypeService: CarTypeService, 48 | private geocodingService: GeocodingService, 49 | private invoiceGenerator: InvoiceGenerator, 50 | private distanceCalculator: DistanceCalculator, 51 | private notificationService: DriverNotificationService, 52 | ) {} 53 | 54 | public async createTransit(transitDto: CreateTransitDto) { 55 | const from = await this.addressFromDto(new AddressDto(transitDto.from)); 56 | const to = await this.addressFromDto(new AddressDto(transitDto.to)); 57 | 58 | if (!from || !to) { 59 | throw new NotAcceptableException( 60 | 'Cannot create transit for empty address', 61 | ); 62 | } 63 | return this._createTransit( 64 | transitDto.clientId, 65 | from, 66 | to, 67 | transitDto.carClass, 68 | ); 69 | } 70 | 71 | public async _createTransit( 72 | clientId: string, 73 | from: Address, 74 | to: Address, 75 | carClass: CarClass, 76 | ) { 77 | const client = await this.clientRepository.findOne(clientId); 78 | 79 | if (!client) { 80 | throw new NotFoundException('Client does not exist, id = ' + clientId); 81 | } 82 | 83 | const transit = new Transit(); 84 | 85 | // FIXME later: add some exceptions handling 86 | const geoFrom = this.geocodingService.geocodeAddress(from); 87 | const geoTo = this.geocodingService.geocodeAddress(to); 88 | 89 | transit.setClient(client); 90 | transit.setFrom(from); 91 | transit.setTo(to); 92 | transit.setCarType(carClass); 93 | transit.setStatus(Status.DRAFT); 94 | transit.setDateTime(Date.now()); 95 | transit.setKm( 96 | this.distanceCalculator.calculateByMap( 97 | geoFrom[0], 98 | geoFrom[1], 99 | geoTo[0], 100 | geoTo[1], 101 | ), 102 | ); 103 | 104 | return this.transitRepository.save(transit); 105 | } 106 | 107 | public async _changeTransitAddressFrom(transitId: string, address: Address) { 108 | const newAddress = await this.addressRepository.save(address); 109 | const transit = await this.transitRepository.findOne(transitId); 110 | 111 | if (!transit) { 112 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 113 | } 114 | 115 | if (!newAddress) { 116 | throw new NotAcceptableException('Cannot process without address'); 117 | } 118 | 119 | // FIXME later: add some exceptions handling 120 | const geoFromNew = this.geocodingService.geocodeAddress(newAddress); 121 | const geoFromOld = this.geocodingService.geocodeAddress(transit.getFrom()); 122 | 123 | // https://www.geeksforgeeks.org/program-distance-two-points-earth/ 124 | // The math module contains a function 125 | // named toRadians which converts from 126 | // degrees to radians. 127 | const lon1 = DistanceCalculator.degreesToRadians(geoFromNew[1]); 128 | const lon2 = DistanceCalculator.degreesToRadians(geoFromOld[1]); 129 | const lat1 = DistanceCalculator.degreesToRadians(geoFromNew[0]); 130 | const lat2 = DistanceCalculator.degreesToRadians(geoFromOld[0]); 131 | 132 | // Haversine formula 133 | const dlon = lon2 - lon1; 134 | const dlat = lat2 - lat1; 135 | const a = 136 | Math.pow(Math.sin(dlat / 2), 2) + 137 | Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2); 138 | 139 | const c = 2 * Math.asin(Math.sqrt(a)); 140 | 141 | // Radius of earth in kilometers. Use 3956 for miles 142 | const r = 6371; 143 | 144 | // calculate the result 145 | const distanceInKMeters = c * r; 146 | 147 | if ( 148 | transit.getStatus() !== Status.DRAFT || 149 | transit.getStatus() === Status.WAITING_FOR_DRIVER_ASSIGNMENT || 150 | transit.getPickupAddressChangeCounter() > 2 || 151 | distanceInKMeters > 0.25 152 | ) { 153 | throw new NotAcceptableException( 154 | "Address 'from' cannot be changed, id = " + transitId, 155 | ); 156 | } 157 | 158 | transit.setFrom(newAddress); 159 | transit.setKm( 160 | this.distanceCalculator.calculateByMap( 161 | geoFromNew[0], 162 | geoFromNew[1], 163 | geoFromOld[0], 164 | geoFromOld[1], 165 | ), 166 | ); 167 | transit.setPickupAddressChangeCounter( 168 | transit.getPickupAddressChangeCounter() + 1, 169 | ); 170 | await this.transitRepository.save(transit); 171 | 172 | for (const driver of transit.getProposedDrivers()) { 173 | await this.notificationService.notifyAboutChangedTransitAddress( 174 | driver.getId(), 175 | transitId, 176 | ); 177 | } 178 | } 179 | 180 | public async changeTransitAddressTo( 181 | transitId: string, 182 | newAddress: AddressDto, 183 | ) { 184 | return this._changeTransitAddressTo( 185 | transitId, 186 | newAddress.toAddressEntity(), 187 | ); 188 | } 189 | 190 | private async _changeTransitAddressTo( 191 | transitId: string, 192 | newAddress: Address, 193 | ) { 194 | const savedAddress = await this.addressRepository.save(newAddress); 195 | const transit = await this.transitRepository.findOne(transitId); 196 | 197 | if (!transit) { 198 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 199 | } 200 | 201 | if (transit.getStatus() === Status.COMPLETED) { 202 | throw new NotAcceptableException( 203 | "Address 'to' cannot be changed, id = " + transitId, 204 | ); 205 | } 206 | 207 | // FIXME later: add some exceptions handling 208 | const geoFrom = this.geocodingService.geocodeAddress(transit.getFrom()); 209 | const geoTo = this.geocodingService.geocodeAddress(savedAddress); 210 | transit.setTo(savedAddress); 211 | transit.setKm( 212 | this.distanceCalculator.calculateByMap( 213 | geoFrom[0], 214 | geoFrom[1], 215 | geoTo[0], 216 | geoTo[1], 217 | ), 218 | ); 219 | 220 | const driver = transit.getDriver(); 221 | await this.transitRepository.save(transit); 222 | if (driver) { 223 | this.notificationService.notifyAboutChangedTransitAddress( 224 | driver.getId(), 225 | transitId, 226 | ); 227 | } 228 | } 229 | 230 | public changeTransitAddressFrom(transitId: string, newAddress: AddressDto) { 231 | return this._changeTransitAddressFrom( 232 | transitId, 233 | newAddress.toAddressEntity(), 234 | ); 235 | } 236 | 237 | public async cancelTransit(transitId: string) { 238 | const transit = await this.transitRepository.findOne(transitId); 239 | 240 | if (!transit) { 241 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 242 | } 243 | 244 | if ( 245 | ![ 246 | Status.DRAFT, 247 | Status.WAITING_FOR_DRIVER_ASSIGNMENT, 248 | Status.TRANSIT_TO_PASSENGER, 249 | ].includes(transit.getStatus()) 250 | ) { 251 | throw new NotAcceptableException( 252 | 'Transit cannot be cancelled, id = ' + transitId, 253 | ); 254 | } 255 | 256 | const driver = transit.getDriver(); 257 | if (driver) { 258 | this.notificationService.notifyAboutCancelledTransit( 259 | driver.getId(), 260 | transitId, 261 | ); 262 | } 263 | 264 | transit.setStatus(Status.CANCELLED); 265 | transit.setDriver(null); 266 | transit.setKm(0); 267 | transit.setAwaitingDriversResponses(0); 268 | await this.transitRepository.save(transit); 269 | } 270 | 271 | public async publishTransit(transitId: string) { 272 | const transit = await this.transitRepository.findOne(transitId); 273 | 274 | if (!transit) { 275 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 276 | } 277 | 278 | transit.setStatus(Status.WAITING_FOR_DRIVER_ASSIGNMENT); 279 | transit.setPublished(Date.now()); 280 | await this.transitRepository.save(transit); 281 | 282 | return this.findDriversForTransit(transitId); 283 | } 284 | 285 | // Abandon hope all ye who enter here... 286 | public async findDriversForTransit(transitId: string) { 287 | const transit = await this.transitRepository.findOne(transitId); 288 | 289 | if (transit) { 290 | if (transit.getStatus() === Status.WAITING_FOR_DRIVER_ASSIGNMENT) { 291 | let distanceToCheck = 0; 292 | 293 | // Tested on production, works as expected. 294 | // If you change this code and the system will collapse AGAIN, I'll find you... 295 | while (true) { 296 | if (transit.getAwaitingDriversResponses() > 4) { 297 | return transit; 298 | } 299 | 300 | distanceToCheck++; 301 | 302 | // FIXME: to refactor when the final business logic will be determined 303 | if ( 304 | dayjs(transit.getPublished()) 305 | .add(300, 'seconds') 306 | .isBefore(dayjs()) || 307 | distanceToCheck >= 20 || 308 | // Should it be here? How is it even possible due to previous status check above loop? 309 | transit.getStatus() === Status.CANCELLED 310 | ) { 311 | transit.setStatus(Status.DRIVER_ASSIGNMENT_FAILED); 312 | transit.setDriver(null); 313 | transit.setKm(0); 314 | transit.setAwaitingDriversResponses(0); 315 | await this.transitRepository.save(transit); 316 | return transit; 317 | } 318 | let geocoded: number[] = [0, 0]; 319 | 320 | try { 321 | geocoded = this.geocodingService.geocodeAddress(transit.getFrom()); 322 | } catch (e) { 323 | // Geocoding failed! Ask Jessica or Bryan for some help if needed. 324 | } 325 | 326 | const longitude = geocoded[1]; 327 | const latitude = geocoded[0]; 328 | 329 | //https://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters 330 | //Earth’s radius, sphere 331 | //double R = 6378; 332 | const R = 6371; // Changed to 6371 due to Copy&Paste pattern from different source 333 | 334 | //offsets in meters 335 | const dn = distanceToCheck; 336 | const de = distanceToCheck; 337 | 338 | //Coordinate offsets in radians 339 | const dLat = dn / R; 340 | const dLon = de / (R * Math.cos((Math.PI * latitude) / 180)); 341 | 342 | //Offset positions, decimal degrees 343 | const latitudeMin = latitude - (dLat * 180) / Math.PI; 344 | const latitudeMax = latitude + (dLat * 180) / Math.PI; 345 | const longitudeMin = longitude - (dLon * 180) / Math.PI; 346 | const longitudeMax = longitude + (dLon * 180) / Math.PI; 347 | 348 | let driversAvgPositions = 349 | await this.driverPositionRepository.findAverageDriverPositionSince( 350 | latitudeMin, 351 | latitudeMax, 352 | longitudeMin, 353 | longitudeMax, 354 | dayjs().subtract(5, 'minutes').valueOf(), 355 | ); 356 | 357 | if (driversAvgPositions.length) { 358 | const comparator = ( 359 | d1: DriverPositionV2Dto, 360 | d2: DriverPositionV2Dto, 361 | ) => { 362 | const a = Math.sqrt( 363 | Math.pow(latitude - d1.getLatitude(), 2) + 364 | Math.pow(longitude - d1.getLongitude(), 2), 365 | ); 366 | const b = Math.sqrt( 367 | Math.pow(latitude - d2.getLatitude(), 2) + 368 | Math.pow(longitude - d2.getLongitude(), 2), 369 | ); 370 | if (a < b) { 371 | return -1; 372 | } 373 | if (a > b) { 374 | return 1; 375 | } 376 | return 0; 377 | }; 378 | driversAvgPositions.sort(comparator); 379 | driversAvgPositions = driversAvgPositions.slice(0, 20); 380 | 381 | const carClasses: CarClass[] = []; 382 | const activeCarClasses = 383 | await this.carTypeService.findActiveCarClasses(); 384 | if (activeCarClasses.length === 0) { 385 | return transit; 386 | } 387 | if (transit.getCarType()) { 388 | if (activeCarClasses.includes(transit.getCarType())) { 389 | carClasses.push(transit.getCarType()); 390 | } else { 391 | return transit; 392 | } 393 | } else { 394 | carClasses.push(...activeCarClasses); 395 | } 396 | 397 | const drivers = driversAvgPositions.map((item) => item.getDriver()); 398 | 399 | const fetchedCars = 400 | await this.driverSessionRepository.findAllByLoggedOutAtNullAndDriverInAndCarClassIn( 401 | drivers, 402 | carClasses, 403 | ); 404 | const activeDriverIdsInSpecificCar = fetchedCars.map((ds) => 405 | ds.getDriver().getId(), 406 | ); 407 | 408 | driversAvgPositions = driversAvgPositions.filter((dp) => 409 | activeDriverIdsInSpecificCar.includes(dp.getDriver().getId()), 410 | ); 411 | 412 | // Iterate across average driver positions 413 | for (const driverAvgPosition of driversAvgPositions) { 414 | const driver = driverAvgPosition.getDriver(); 415 | if ( 416 | driver.getStatus() === DriverStatus.ACTIVE && 417 | !driver.getOccupied() 418 | ) { 419 | if ( 420 | !Array.from(transit.getDriversRejections()).includes(driver) 421 | ) { 422 | transit.getProposedDrivers().push(driver); 423 | transit.setAwaitingDriversResponses( 424 | transit.getAwaitingDriversResponses() + 1, 425 | ); 426 | await this.notificationService.notifyAboutPossibleTransit( 427 | driver.getId(), 428 | transitId, 429 | ); 430 | } 431 | } else { 432 | // Not implemented yet! 433 | } 434 | } 435 | 436 | await this.transitRepository.save(transit); 437 | } else { 438 | // Next iteration, no drivers at specified area 439 | continue; 440 | } 441 | } 442 | } else { 443 | throw new NotAcceptableException( 444 | 'Wrong status for transit id = ' + transitId, 445 | ); 446 | } 447 | } else { 448 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 449 | } 450 | } 451 | 452 | public async acceptTransit(driverId: string, transitId: string) { 453 | const driver = await this.driverRepository.findOne(driverId); 454 | 455 | if (!driver) { 456 | throw new NotFoundException('Driver does not exist, id = ' + driverId); 457 | } else { 458 | const transit = await this.transitRepository.findOne(transitId); 459 | 460 | if (!transit) { 461 | throw new NotFoundException( 462 | 'Transit does not exist, id = ' + transitId, 463 | ); 464 | } else { 465 | if (transit.getDriver()) { 466 | throw new NotAcceptableException( 467 | 'Transit already accepted, id = ' + transitId, 468 | ); 469 | } else { 470 | if (!Array.from(transit.getProposedDrivers()).includes(driver)) { 471 | throw new NotAcceptableException( 472 | 'Driver out of possible drivers, id = ' + transitId, 473 | ); 474 | } else { 475 | if (Array.from(transit.getDriversRejections()).includes(driver)) { 476 | throw new NotAcceptableException( 477 | 'Driver out of possible drivers, id = ' + transitId, 478 | ); 479 | } else { 480 | transit.setDriver(driver); 481 | transit.setAwaitingDriversResponses(0); 482 | transit.setAcceptedAt(Date.now()); 483 | transit.setStatus(Status.TRANSIT_TO_PASSENGER); 484 | await this.transitRepository.save(transit); 485 | driver.setOccupied(true); 486 | await this.driverRepository.save(driver); 487 | } 488 | } 489 | } 490 | } 491 | } 492 | } 493 | 494 | public async startTransit(driverId: string, transitId: string) { 495 | const driver = await this.driverRepository.findOne(driverId); 496 | 497 | if (!driver) { 498 | throw new NotFoundException('Driver does not exist, id = ' + driverId); 499 | } 500 | 501 | const transit = await this.transitRepository.findOne(transitId); 502 | 503 | if (!transit) { 504 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 505 | } 506 | 507 | if (transit.getStatus() !== Status.TRANSIT_TO_PASSENGER) { 508 | throw new NotAcceptableException( 509 | 'Transit cannot be started, id = ' + transitId, 510 | ); 511 | } 512 | 513 | transit.setStatus(Status.IN_TRANSIT); 514 | transit.setStarted(Date.now()); 515 | await this.transitRepository.save(transit); 516 | } 517 | 518 | public async rejectTransit(driverId: string, transitId: string) { 519 | const driver = await this.driverRepository.findOne(driverId); 520 | 521 | if (!driver) { 522 | throw new NotFoundException('Driver does not exist, id = ' + driverId); 523 | } 524 | 525 | const transit = await this.transitRepository.findOne(transitId); 526 | 527 | if (!transit) { 528 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 529 | } 530 | 531 | transit.getDriversRejections().push(driver); 532 | transit.setAwaitingDriversResponses( 533 | transit.getAwaitingDriversResponses() - 1, 534 | ); 535 | await this.transitRepository.save(transit); 536 | } 537 | 538 | public completeTransitFromDto( 539 | driverId: string, 540 | transitId: string, 541 | destinationAddress: AddressDto, 542 | ) { 543 | return this.completeTransit( 544 | driverId, 545 | transitId, 546 | destinationAddress.toAddressEntity(), 547 | ); 548 | } 549 | 550 | public async completeTransit( 551 | driverId: string, 552 | transitId: string, 553 | destinationAddress: Address, 554 | ) { 555 | await this.addressRepository.save(destinationAddress); 556 | const driver = await this.driverRepository.findOne(driverId); 557 | 558 | if (!driver) { 559 | throw new NotFoundException('Driver does not exist, id = ' + driverId); 560 | } 561 | 562 | const transit = await this.transitRepository.findOne(transitId); 563 | 564 | if (!transit) { 565 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 566 | } 567 | 568 | if (transit.getStatus() === Status.IN_TRANSIT) { 569 | // FIXME later: add some exceptions handling 570 | const geoFrom = this.geocodingService.geocodeAddress(transit.getFrom()); 571 | const geoTo = this.geocodingService.geocodeAddress(transit.getTo()); 572 | 573 | transit.setTo(destinationAddress); 574 | transit.setKm( 575 | this.distanceCalculator.calculateByMap( 576 | geoFrom[0], 577 | geoFrom[1], 578 | geoTo[0], 579 | geoTo[1], 580 | ), 581 | ); 582 | transit.setStatus(Status.COMPLETED); 583 | transit.calculateFinalCosts(); 584 | driver.setOccupied(false); 585 | transit.setCompleteAt(Date.now()); 586 | const driverFee = await this.driverFeeService.calculateDriverFee( 587 | transitId, 588 | ); 589 | transit.setDriversFee(driverFee); 590 | await this.driverRepository.save(driver); 591 | await this.awardsService.registerMiles( 592 | transit.getClient().getId(), 593 | transitId, 594 | ); 595 | await this.transitRepository.save(transit); 596 | await this.invoiceGenerator.generate( 597 | transit.getPrice() ?? 0, 598 | transit.getClient().getName() + ' ' + transit.getClient().getLastName(), 599 | ); 600 | } else { 601 | throw new NotAcceptableException( 602 | 'Cannot complete Transit, id = ' + transitId, 603 | ); 604 | } 605 | } 606 | 607 | public async loadTransit(transitId: string) { 608 | const transit = await this.transitRepository.findOne(transitId); 609 | 610 | if (!transit) { 611 | throw new NotFoundException('Transit does not exist, id = ' + transitId); 612 | } 613 | 614 | return new TransitDto(transit); 615 | } 616 | 617 | private async addressFromDto(addressDTO: AddressDto) { 618 | const address = addressDTO.toAddressEntity(); 619 | return this.addressRepository.save(address); 620 | } 621 | } 622 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "noImplicitAny": true, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------