From a82e8dd28fa3a726fec2d85dad5dd3b2ab4b3888 Mon Sep 17 00:00:00 2001 From: Andrzej Hanusek Date: Thu, 3 Feb 2022 20:53:51 +0100 Subject: [PATCH] =?UTF-8?q?tak=20si=C4=99=20wszystko=20zacz=C4=99=C5=82o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dist | 4 + .eslintrc.js | 7 +- .gitignore | 3 +- .husky/pre-commit | 6 + README.md | 71 +- package.json | 18 +- src/app.controller.spec.ts | 22 - src/app.controller.ts | 12 - src/app.module.ts | 118 +++- src/app.service.ts | 8 - src/common/base.entity.ts | 10 + src/config/app-properties.config.ts | 53 ++ src/controllers/awards-account.controller.ts | 60 ++ src/controllers/car-type.controller.ts | 72 ++ src/controllers/claim.controller.ts | 52 ++ src/controllers/client.controller.ts | 61 ++ src/controllers/contract.controller.ts | 70 ++ src/controllers/driver-report.controller.ts | 94 +++ src/controllers/driver-session.controller.ts | 44 ++ src/controllers/driver-tracking.controller.ts | 54 ++ src/controllers/driver.controller.ts | 50 ++ .../transit-analyzer.controller.ts | 20 + src/controllers/transit.controller.ts | 115 ++++ src/dto/address.dto.ts | 126 ++++ src/dto/analyzed-addresses.dto.ts | 17 + src/dto/awards-account.dto.ts | 51 ++ src/dto/car-type.dto.ts | 26 + src/dto/claim.dto.ts | 150 +++++ src/dto/client.dto.ts | 72 ++ src/dto/contract-attachment.dto.ts | 37 ++ src/dto/contract.dto.ts | 121 ++++ src/dto/create-address.dto.ts | 25 + src/dto/create-car-type.dto.ts | 9 + src/dto/create-claim.dto.ts | 15 + src/dto/create-client.dto.ts | 16 + src/dto/create-contract-attachment.dto.ts | 6 + src/dto/create-contract.dto.ts | 9 + src/dto/create-driver-position.dto.ts | 10 + src/dto/create-driver-session.dto.ts | 13 + src/dto/create-driver.dto.ts | 14 + src/dto/create-transit.dto.ts | 25 + src/dto/driver-attribute.dto.ts | 41 ++ src/dto/driver-position-v2.dto.ts | 55 ++ src/dto/driver-position.dto.ts | 21 + src/dto/driver-report.dto.ts | 36 + src/dto/driver-session.dto.ts | 62 ++ src/dto/driver.dto.ts | 27 + src/dto/transit.dto.ts | 317 +++++++++ src/entity/address.entity.ts | 160 +++++ src/entity/awarded-miles.entity.ts | 75 +++ src/entity/awards-account.entity.ts | 51 ++ src/entity/car-type.entity.ts | 111 ++++ src/entity/claim-attachment.entity.ts | 56 ++ src/entity/claim.entity.ts | 132 ++++ src/entity/client.entity.ts | 88 +++ src/entity/contract-attachment.entity.ts | 93 +++ src/entity/contract.entity.ts | 115 ++++ src/entity/driver-attribute.entity.ts | 58 ++ src/entity/driver-fee.entity.ts | 63 ++ src/entity/driver-position.entity.ts | 50 ++ src/entity/driver-session.entity.ts | 73 ++ src/entity/driver.entity.ts | 131 ++++ src/entity/invoice.entity.ts | 17 + src/entity/transit.entity.ts | 370 +++++++++++ src/main.ts | 2 +- src/repository/address.repository.ts | 27 + src/repository/awarded-miles.repository.ts | 10 + src/repository/awards-account.repository.ts | 10 + src/repository/car-type.repository.ts | 19 + src/repository/claim-attachment.repository.ts | 5 + src/repository/claim.repository.ts | 15 + src/repository/client.repository.ts | 5 + .../contract-attachment.repository.ts | 10 + src/repository/contract.repository.ts | 9 + src/repository/driver-attribute.repository.ts | 5 + src/repository/driver-fee.repository.ts | 10 + src/repository/driver-position.repository.ts | 55 ++ src/repository/driver-session.repository.ts | 48 ++ src/repository/driver.repository.ts | 5 + src/repository/invoice.repository.ts | 5 + src/repository/transit.repository.ts | 65 ++ src/service/awards.service.ts | 324 +++++++++ src/service/car-type.service.ts | 123 ++++ src/service/claim-number-generator.service.ts | 28 + src/service/claim.service.ts | 178 +++++ src/service/client-notification.service.ts | 16 + src/service/client.service.ts | 65 ++ src/service/contract.service.ts | 143 ++++ src/service/distance-calculator.service.ts | 52 ++ src/service/driver-fee.service.ts | 51 ++ src/service/driver-notification.service.ts | 28 + src/service/driver-session.service.ts | 78 +++ src/service/driver-tracking.service.ts | 79 +++ src/service/driver.service.ts | 184 ++++++ src/service/geocoding.service.ts | 16 + src/service/invoice-generator.service.ts | 16 + src/service/transit-analyzer.service.ts | 104 +++ src/service/transit.service.ts | 621 ++++++++++++++++++ tsconfig.json | 4 +- yarn.lock | 361 +++++++++- 100 files changed, 6630 insertions(+), 114 deletions(-) create mode 100644 .env.dist create mode 100755 .husky/pre-commit delete mode 100644 src/app.controller.spec.ts delete mode 100644 src/app.controller.ts delete mode 100644 src/app.service.ts create mode 100644 src/common/base.entity.ts create mode 100644 src/config/app-properties.config.ts create mode 100644 src/controllers/awards-account.controller.ts create mode 100644 src/controllers/car-type.controller.ts create mode 100644 src/controllers/claim.controller.ts create mode 100644 src/controllers/client.controller.ts create mode 100644 src/controllers/contract.controller.ts create mode 100644 src/controllers/driver-report.controller.ts create mode 100644 src/controllers/driver-session.controller.ts create mode 100644 src/controllers/driver-tracking.controller.ts create mode 100644 src/controllers/driver.controller.ts create mode 100644 src/controllers/transit-analyzer.controller.ts create mode 100644 src/controllers/transit.controller.ts create mode 100644 src/dto/address.dto.ts create mode 100644 src/dto/analyzed-addresses.dto.ts create mode 100644 src/dto/awards-account.dto.ts create mode 100644 src/dto/car-type.dto.ts create mode 100644 src/dto/claim.dto.ts create mode 100644 src/dto/client.dto.ts create mode 100644 src/dto/contract-attachment.dto.ts create mode 100644 src/dto/contract.dto.ts create mode 100644 src/dto/create-address.dto.ts create mode 100644 src/dto/create-car-type.dto.ts create mode 100644 src/dto/create-claim.dto.ts create mode 100644 src/dto/create-client.dto.ts create mode 100644 src/dto/create-contract-attachment.dto.ts create mode 100644 src/dto/create-contract.dto.ts create mode 100644 src/dto/create-driver-position.dto.ts create mode 100644 src/dto/create-driver-session.dto.ts create mode 100644 src/dto/create-driver.dto.ts create mode 100644 src/dto/create-transit.dto.ts create mode 100644 src/dto/driver-attribute.dto.ts create mode 100644 src/dto/driver-position-v2.dto.ts create mode 100644 src/dto/driver-position.dto.ts create mode 100644 src/dto/driver-report.dto.ts create mode 100644 src/dto/driver-session.dto.ts create mode 100644 src/dto/driver.dto.ts create mode 100644 src/dto/transit.dto.ts create mode 100644 src/entity/address.entity.ts create mode 100644 src/entity/awarded-miles.entity.ts create mode 100644 src/entity/awards-account.entity.ts create mode 100644 src/entity/car-type.entity.ts create mode 100644 src/entity/claim-attachment.entity.ts create mode 100644 src/entity/claim.entity.ts create mode 100644 src/entity/client.entity.ts create mode 100644 src/entity/contract-attachment.entity.ts create mode 100644 src/entity/contract.entity.ts create mode 100644 src/entity/driver-attribute.entity.ts create mode 100644 src/entity/driver-fee.entity.ts create mode 100644 src/entity/driver-position.entity.ts create mode 100644 src/entity/driver-session.entity.ts create mode 100644 src/entity/driver.entity.ts create mode 100644 src/entity/invoice.entity.ts create mode 100644 src/entity/transit.entity.ts create mode 100644 src/repository/address.repository.ts create mode 100644 src/repository/awarded-miles.repository.ts create mode 100644 src/repository/awards-account.repository.ts create mode 100644 src/repository/car-type.repository.ts create mode 100644 src/repository/claim-attachment.repository.ts create mode 100644 src/repository/claim.repository.ts create mode 100644 src/repository/client.repository.ts create mode 100644 src/repository/contract-attachment.repository.ts create mode 100644 src/repository/contract.repository.ts create mode 100644 src/repository/driver-attribute.repository.ts create mode 100644 src/repository/driver-fee.repository.ts create mode 100644 src/repository/driver-position.repository.ts create mode 100644 src/repository/driver-session.repository.ts create mode 100644 src/repository/driver.repository.ts create mode 100644 src/repository/invoice.repository.ts create mode 100644 src/repository/transit.repository.ts create mode 100644 src/service/awards.service.ts create mode 100644 src/service/car-type.service.ts create mode 100644 src/service/claim-number-generator.service.ts create mode 100644 src/service/claim.service.ts create mode 100644 src/service/client-notification.service.ts create mode 100644 src/service/client.service.ts create mode 100644 src/service/contract.service.ts create mode 100644 src/service/distance-calculator.service.ts create mode 100644 src/service/driver-fee.service.ts create mode 100644 src/service/driver-notification.service.ts create mode 100644 src/service/driver-session.service.ts create mode 100644 src/service/driver-tracking.service.ts create mode 100644 src/service/driver.service.ts create mode 100644 src/service/geocoding.service.ts create mode 100644 src/service/invoice-generator.service.ts create mode 100644 src/service/transit-analyzer.service.ts create mode 100644 src/service/transit.service.ts diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..9684ea4 --- /dev/null +++ b/.env.dist @@ -0,0 +1,4 @@ +DATABASE_NAME= +DATABASE_PORT= +DATABASE_USERNAME= +DATABASE_PASSWORD= diff --git a/.eslintrc.js b/.eslintrc.js index f6c62be..e8c9614 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,8 +17,11 @@ module.exports = { ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 2, + '@typescript-eslint/explicit-member-accessibility': [ + 2, + { overrides: { constructors: 'off' } }, + ], }, }; diff --git a/.gitignore b/.gitignore index 22f55ad..a60d5fd 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json +.env diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2178a33 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn tsc +yarn format +yarn lint diff --git a/README.md b/README.md index 9fe8812..6810ae9 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,48 @@ -

- Nest Logo -

- -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest - -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Coverage -Discord -Backers on Open Collective -Sponsors on Open Collective - - Support us - -

- - -## Description - -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. +### Fork of legacyfighter/cabs-java to Nest.js (TypeScript / Node) +____ + +# Rozwój kodu + +Kod będzie rozwijać się wraz z cotygodniową narracją szkoleniową. +Zarówno pojawiać się w nim będą kolejne poprawki jak i odziedziczone po firmach partnerskich nowe moduły ;-) Jak to w prawdziwym legacy. + +# Przeglądanie kodu + +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. + + +_____________ + + ## Installation ```bash -$ npm install +$ yarn install ``` ## Running the app ```bash # development -$ npm run start +$ yarn start # watch mode -$ npm run start:dev +$ yarn start:dev # production mode -$ npm run start:prod +$ yarn start:prod ``` ## Test ```bash # unit tests -$ npm run test +$ yarn test # e2e tests -$ npm run test:e2e +$ yarn test:e2e # test coverage -$ npm run test:cov +$ yarn test:cov ``` - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](LICENSE). diff --git a/package.json b/package.json index fcf873d..552178f 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,26 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "prepare": "husky install", + "check-types": "tsc" }, "dependencies": { "@nestjs/common": "^8.0.0", + "@nestjs/config": "^1.1.6", "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", + "@nestjs/typeorm": "^8.0.3", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", + "dayjs": "^1.10.7", + "lodash.orderby": "^4.6.0", + "object-hash": "^2.2.0", + "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", - "rxjs": "^7.2.0" + "rxjs": "^7.2.0", + "typeorm": "^0.2.41" }, "devDependencies": { "@nestjs/cli": "^8.0.0", @@ -34,13 +45,16 @@ "@nestjs/testing": "^8.0.0", "@types/express": "^4.17.13", "@types/jest": "27.0.2", + "@types/lodash.orderby": "^4.6.6", "@types/node": "^16.0.0", + "@types/object-hash": "^2.2.1", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", + "husky": "^7.0.4", "jest": "^27.2.5", "prettier": "^2.3.2", "source-map-support": "^0.5.20", diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts deleted file mode 100644 index d22f389..0000000 --- a/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/src/app.controller.ts b/src/app.controller.ts deleted file mode 100644 index cce879e..0000000 --- a/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } -} diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..8497d7f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,118 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule } from '@nestjs/config'; +import { DriverController } from './controllers/driver.controller'; +import { DriverService } from './service/driver.service'; +import { DriverRepository } from './repository/driver.repository'; +import { AppProperties } from './config/app-properties.config'; +import { CarTypeRepository } from './repository/car-type.repository'; +import { CarTypeController } from './controllers/car-type.controller'; +import { CarTypeService } from './service/car-type.service'; +import { DistanceCalculator } from './service/distance-calculator.service'; +import { InvoiceRepository } from './repository/invoice.repository'; +import { InvoiceGenerator } from './service/invoice-generator.service'; +import { DriverNotificationService } from './service/driver-notification.service'; +import { GeocodingService } from './service/geocoding.service'; +import { ClaimNumberGenerator } from './service/claim-number-generator.service'; +import { ClaimRepository } from './repository/claim.repository'; +import { ClientNotificationService } from './service/client-notification.service'; +import { ClientService } from './service/client.service'; +import { ClientRepository } from './repository/client.repository'; +import { ClientController } from './controllers/client.controller'; +import { DriverSessionService } from './service/driver-session.service'; +import { DriverSessionRepository } from './repository/driver-session.repository'; +import { DriverSessionController } from './controllers/driver-session.controller'; +import { DriverFeeRepository } from './repository/driver-fee.repository'; +import { TransitRepository } from './repository/transit.repository'; +import { DriverFeeService } from './service/driver-fee.service'; +import { DriverPositionRepository } from './repository/driver-position.repository'; +import { DriverTrackingService } from './service/driver-tracking.service'; +import { DriverTrackingController } from './controllers/driver-tracking.controller'; +import { ClaimAttachmentRepository } from './repository/claim-attachment.repository'; +import { AddressRepository } from './repository/address.repository'; +import { DriverAttributeRepository } from './repository/driver-attribute.repository'; +import { AwardedMilesRepository } from './repository/awarded-miles.repository'; +import { AwardsAccountRepository } from './repository/awards-account.repository'; +import { ContractAttachmentRepository } from './repository/contract-attachment.repository'; +import { ContractRepository } from './repository/contract.repository'; +import { TransitAnalyzerService } from './service/transit-analyzer.service'; +import { AwardsService } from './service/awards.service'; +import { ClaimService } from './service/claim.service'; +import { ContractService } from './service/contract.service'; +import { TransitService } from './service/transit.service'; +import { TransitAnalyzerController } from './controllers/transit-analyzer.controller'; +import { TransitController } from './controllers/transit.controller'; +import { AwardsAccountController } from './controllers/awards-account.controller'; +import { ClaimController } from './controllers/claim.controller'; +import { ContractController } from './controllers/contract.controller'; +import { DriverReportController } from './controllers/driver-report.controller'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forRoot({ + type: 'postgres', + host: 'localhost', + port: process.env.DATABASE_PORT + ? parseInt(process.env.DATABASE_PORT, 10) + : 3456, + username: process.env.DATABASE_USERNAME, + password: process.env.DATABASE_PASSWORD, + database: process.env.DATABASE_NAME, + autoLoadEntities: true, + synchronize: true, + }), + TypeOrmModule.forFeature([ + DriverRepository, + CarTypeRepository, + InvoiceRepository, + ClaimRepository, + ClientRepository, + DriverSessionRepository, + DriverFeeRepository, + TransitRepository, + DriverPositionRepository, + ClaimAttachmentRepository, + AddressRepository, + DriverAttributeRepository, + AwardedMilesRepository, + AwardsAccountRepository, + ContractAttachmentRepository, + ContractRepository, + ]), + ], + controllers: [ + DriverController, + CarTypeController, + ClientController, + DriverSessionController, + DriverTrackingController, + TransitAnalyzerController, + TransitController, + AwardsAccountController, + ClaimController, + ContractController, + DriverReportController, + ], + providers: [ + AppProperties, + DriverService, + CarTypeService, + DistanceCalculator, + InvoiceGenerator, + DriverNotificationService, + GeocodingService, + ClaimNumberGenerator, + ClientNotificationService, + ClientService, + DriverSessionService, + DriverFeeService, + DriverTrackingService, + TransitAnalyzerService, + AwardsService, + ClaimService, + ContractService, + TransitService, + ], }) export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts deleted file mode 100644 index 927d7cc..0000000 --- a/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/src/common/base.entity.ts b/src/common/base.entity.ts new file mode 100644 index 0000000..b49178c --- /dev/null +++ b/src/common/base.entity.ts @@ -0,0 +1,10 @@ +import { PrimaryGeneratedColumn } from 'typeorm'; + +export class BaseEntity { + @PrimaryGeneratedColumn('uuid') + protected id: string; + + public getId(): string { + return this.id; + } +} diff --git a/src/config/app-properties.config.ts b/src/config/app-properties.config.ts new file mode 100644 index 0000000..775587d --- /dev/null +++ b/src/config/app-properties.config.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppProperties { + private noOfTransitsForClaimAutomaticRefund: number; + + private automaticRefundForVipThreshold: number; + + private minNoOfCarsForEcoClass: number; + + private milesExpirationInDays = 365; + + private defaultMilesBonus = 10; + + public getAutomaticRefundForVipThreshold() { + return this.automaticRefundForVipThreshold; + } + + public getNoOfTransitsForClaimAutomaticRefund() { + return this.noOfTransitsForClaimAutomaticRefund; + } + + public setNoOfTransitsForClaimAutomaticRefund( + noOfTransitsForClaimAutomaticRefund: number, + ) { + this.noOfTransitsForClaimAutomaticRefund = + noOfTransitsForClaimAutomaticRefund; + } + + public getMinNoOfCarsForEcoClass() { + return this.minNoOfCarsForEcoClass; + } + + public setMinNoOfCarsForEcoClass(minNoOfCarsForEcoClass: number) { + this.minNoOfCarsForEcoClass = minNoOfCarsForEcoClass; + } + + public getMilesExpirationInDays() { + return this.milesExpirationInDays; + } + + public getDefaultMilesBonus() { + return this.defaultMilesBonus; + } + + public setMilesExpirationInDays(milesExpirationInDays: number) { + this.milesExpirationInDays = milesExpirationInDays; + } + + public setDefaultMilesBonus(defaultMilesBonus: number) { + this.defaultMilesBonus = defaultMilesBonus; + } +} diff --git a/src/controllers/awards-account.controller.ts b/src/controllers/awards-account.controller.ts new file mode 100644 index 0000000..df81455 --- /dev/null +++ b/src/controllers/awards-account.controller.ts @@ -0,0 +1,60 @@ +import { AwardsService } from '../service/awards.service'; +import { Controller, Get, Param, Post } from '@nestjs/common'; +import { AwardsAccountDto } from '../dto/awards-account.dto'; + +@Controller('clients') +export class AwardsAccountController { + constructor(private awardsService: AwardsService) {} + + @Post(':clientId/awards') + public async register( + @Param('clientId') clientId: string, + ): Promise { + await this.awardsService.registerToProgram(clientId); + return this.awardsService.findBy(clientId); + } + + @Post(':clientId/awards/activate') + public async activate( + @Param('clientId') clientId: string, + ): Promise { + await this.awardsService.activateAccount(clientId); + return this.awardsService.findBy(clientId); + } + + @Post(':clientId/awards/deactivate') + public async deactivate( + @Param('clientId') clientId: string, + ): Promise { + await this.awardsService.deactivateAccount(clientId); + return this.awardsService.findBy(clientId); + } + + @Post(':clientId/awards/balance') + public async calculateBalance( + @Param('clientId') clientId: string, + ): Promise { + return this.awardsService.calculateBalance(clientId); + } + + @Post(':clientId/awards/transfer/:toClientId/:howMuch') + public async transferMiles( + @Param('clientId') clientId: string, + @Param('toClientId') toClientId: string, + @Param('howMuch') howMuch: string, + ): Promise { + await this.awardsService.transferMiles( + clientId, + toClientId, + Number(howMuch), + ); + return this.awardsService.findBy(clientId); + } + + @Get(':clientId/awards') + public async findBy( + @Param('clientId') clientId: string, + ): Promise { + return this.awardsService.findBy(clientId); + } +} diff --git a/src/controllers/car-type.controller.ts b/src/controllers/car-type.controller.ts new file mode 100644 index 0000000..62939a8 --- /dev/null +++ b/src/controllers/car-type.controller.ts @@ -0,0 +1,72 @@ +import { + Body, + Controller, + Param, + Post, + UsePipes, + ValidationPipe, + Res, + HttpStatus, + Get, +} from '@nestjs/common'; +import { Response } from 'express'; +import { CarTypeService } from '../service/car-type.service'; +import { CreateCarTypeDto } from '../dto/create-car-type.dto'; +import { CarTypeDto } from '../dto/car-type.dto'; +import { CarClass } from '../entity/car-type.entity'; + +@Controller('cartypes') +export class CarTypeController { + constructor(private readonly carTypeService: CarTypeService) {} + + @Post() + @UsePipes(ValidationPipe) + public async create( + @Body() createCarTypeDto: CreateCarTypeDto, + ): Promise { + const carType = await this.carTypeService.create(createCarTypeDto); + + return new CarTypeDto(carType); + } + + @Post(':carClass/registerCar') + public async registerCar( + @Param('carClass') carClass: CarClass, + @Res() res: Response, + ): Promise { + await this.carTypeService.registerCar(carClass); + res.status(HttpStatus.OK).send(); + } + + @Post(':carClass/unregisterCar') + public async unregisterCar( + @Param('carClass') carClass: CarClass, + @Res() res: Response, + ): Promise { + await this.carTypeService.unregisterCar(carClass); + res.status(HttpStatus.OK).send(); + } + + @Post(':id/activate') + public async activate( + @Param('id') id: string, + @Res() res: Response, + ): Promise { + await this.carTypeService.activate(id); + res.status(HttpStatus.OK).send(); + } + + @Post(':id/deactivate') + public async deactivate( + @Param('id') id: string, + @Res() res: Response, + ): Promise { + await this.carTypeService.deactivate(id); + res.status(HttpStatus.OK).send(); + } + + @Get(':id') + public async find(@Param('id') id: string): Promise { + return this.carTypeService.loadDto(id); + } +} diff --git a/src/controllers/claim.controller.ts b/src/controllers/claim.controller.ts new file mode 100644 index 0000000..7353bde --- /dev/null +++ b/src/controllers/claim.controller.ts @@ -0,0 +1,52 @@ +import { ClaimService } from '../service/claim.service'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { ClaimDto } from '../dto/claim.dto'; +import { Claim, ClaimStatus } from '../entity/claim.entity'; +import { CreateClaimDto } from '../dto/create-claim.dto'; + +@Controller('claims') +export class ClaimController { + constructor(private claimService: ClaimService) {} + + @Post('createDraft') + public async create(@Body() createClaimDto: CreateClaimDto) { + const created = await this.claimService.create( + new ClaimDto(createClaimDto), + ); + return this.toDto(created); + } + + @Post('send') + public async sendNew(@Body() createClaimDto: CreateClaimDto) { + const claimDto = new ClaimDto(createClaimDto); + claimDto.setDraft(false); + const claim = await this.claimService.create(claimDto); + return this.toDto(claim); + } + + @Post(':claimId/markInProcess') + public async markAsInProcess(@Param('claimId') claimId: string) { + const claim = await this.claimService.setStatus( + ClaimStatus.IN_PROCESS, + claimId, + ); + return this.toDto(claim); + } + + @Get(':claimId') + public async find(@Param('claimId') claimId: string) { + const claim = await this.claimService.find(claimId); + const dto = this.toDto(claim); + return dto; + } + + @Post('/claims/:claimId') + public async tryToAutomaticallyResolve(@Param('claimId') claimId: string) { + const claim = await this.claimService.tryToResolveAutomatically(claimId); + return this.toDto(claim); + } + + private toDto(claim: Claim) { + return new ClaimDto(claim); + } +} diff --git a/src/controllers/client.controller.ts b/src/controllers/client.controller.ts new file mode 100644 index 0000000..760bdea --- /dev/null +++ b/src/controllers/client.controller.ts @@ -0,0 +1,61 @@ +import { + Body, + Controller, + Get, + Param, + Post, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { ClientService } from '../service/client.service'; +import { CreateClientDto } from '../dto/create-client.dto'; +import { ClientDto } from '../dto/client.dto'; + +@Controller('clients') +export class ClientController { + constructor(private readonly clientService: ClientService) {} + + @Post() + @UsePipes(ValidationPipe) + public async register( + @Body() createClientDto: CreateClientDto, + ): Promise { + const client = await this.clientService.registerClient( + createClientDto.name, + createClientDto.lastName, + createClientDto.type, + createClientDto.defaultPaymentType, + ); + + return this.clientService.load(client.getId()); + } + + @Get(':id') + public async find(@Param('id') id: string): Promise { + return this.clientService.load(id); + } + + @Post(':id/upgrade') + public async upgradeToVIP(@Param('id') id: string): Promise { + await this.clientService.upgradeToVIP(id); + return this.clientService.load(id); + } + + @Post(':id/downgrade') + public async downgrade(@Param('id') id: string): Promise { + await this.clientService.downgradeToRegular(id); + return this.clientService.load(id); + } + + @Post(':id/changeDefaultPaymentType') + public async changeDefaultPaymentType( + @Param('id') id: string, + @Body() createClientDto: Pick, + ): Promise { + await this.clientService.changeDefaultPaymentType( + id, + createClientDto.defaultPaymentType, + ); + return this.clientService.load(id); + } +} diff --git a/src/controllers/contract.controller.ts b/src/controllers/contract.controller.ts new file mode 100644 index 0000000..6b0d7b5 --- /dev/null +++ b/src/controllers/contract.controller.ts @@ -0,0 +1,70 @@ +import { ContractService } from '../service/contract.service'; +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { ContractDto } from '../dto/contract.dto'; +import { CreateContractDto } from '../dto/create-contract.dto'; +import { CreateContractAttachmentDto } from '../dto/create-contract-attachment.dto'; + +@Controller('contracts') +export class ContractController { + constructor(private contractService: ContractService) {} + + @Post() + public async create(@Body() createContractDto: CreateContractDto) { + const created = await this.contractService.createContract( + createContractDto, + ); + return new ContractDto(created); + } + + @Get(':contractId') + public async find(@Param('contractId') contractId: string) { + const contract = await this.contractService.findDto(contractId); + return contract; + } + + @Post(':contractId/attachment') + public async proposeAttachment( + @Param('contractId') contractId: string, + @Body() createContractAttachmentDto: CreateContractAttachmentDto, + ) { + const dto = await this.contractService.proposeAttachment( + contractId, + createContractAttachmentDto, + ); + return dto; + } + + @Post(':contractId/attachment/:attachmentId/reject') + public async rejectAttachment( + @Param('contractId') contractId: string, + @Param('attachmentId') attachmentId: string, + ) { + await this.contractService.rejectAttachment(attachmentId); + } + + @Post(':contractId/attachment/:attachmentId/accept') + public async acceptAttachment( + @Param('contractId') contractId: string, + @Param('attachmentId') attachmentId: string, + ) { + await this.contractService.acceptAttachment(attachmentId); + } + + @Delete(':contractId/attachment/:attachmentId') + public async removeAttachment( + @Param('contractId') contractId: string, + @Param('attachmentId') attachmentId: string, + ) { + await this.contractService.removeAttachment(contractId, attachmentId); + } + + @Post(':contractId/accept') + public async acceptContract(@Param('contractId') contractId: string) { + await this.contractService.acceptContract(contractId); + } + + @Post(':contractId/reject') + public async rejectContract(@Param('contractId') contractId: string) { + await this.contractService.rejectContract(contractId); + } +} diff --git a/src/controllers/driver-report.controller.ts b/src/controllers/driver-report.controller.ts new file mode 100644 index 0000000..a294883 --- /dev/null +++ b/src/controllers/driver-report.controller.ts @@ -0,0 +1,94 @@ +import { DriverService } from '../service/driver.service'; +import { DriverRepository } from '../repository/driver.repository'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ClaimRepository } from '../repository/claim.repository'; +import { DriverSessionRepository } from '../repository/driver-session.repository'; +import { + Body, + Controller, + Get, + NotFoundException, + Param, +} from '@nestjs/common'; +import { DriverReport } from '../dto/driver-report.dto'; +import { DriverAttributeName } from '../entity/driver-attribute.entity'; +import { DriverAttributeDto } from '../dto/driver-attribute.dto'; +import * as dayjs from 'dayjs'; +import { DriverSessionDto } from '../dto/driver-session.dto'; +import { TransitDto } from '../dto/transit.dto'; +import { Status, Transit } from '../entity/transit.entity'; +import { ClaimDto } from '../dto/claim.dto'; + +@Controller('driverreport') +export class DriverReportController { + constructor( + private driverService: DriverService, + @InjectRepository(DriverRepository) + private driverRepository: DriverRepository, + @InjectRepository(ClaimRepository) + private claimRepository: ClaimRepository, + @InjectRepository(DriverSessionRepository) + private driverSessionRepository: DriverSessionRepository, + ) {} + + @Get(':driverId') + public async loadReportForDriver( + @Param('driverId') driverId: string, + @Body() body: { lastDays: number }, + ): Promise { + const driverReport = new DriverReport(); + const driverDto = await this.driverService.loadDriver(driverId); + + driverReport.setDriverDTO(driverDto); + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException(`Driver with id ${driverId} not exists`); + } + + driver + .getAttributes() + .filter( + (attr) => + attr.getName() !== DriverAttributeName.MEDICAL_EXAMINATION_REMARKS, + ) + .forEach((attr) => + driverReport.getAttributes().push(new DriverAttributeDto(attr)), + ); + + const beggingOfToday = dayjs().startOf('day'); + const since = beggingOfToday.subtract(body.lastDays, 'days'); + const allByDriverAndLoggedAtAfter = + await this.driverSessionRepository.findAllByDriverAndLoggedAtAfter( + driver, + since.valueOf(), + ); + const sessionsWithTransits: Map = new Map(); + for (const session of allByDriverAndLoggedAtAfter) { + const dto = new DriverSessionDto(session); + const transitsInSession: Transit[] = Array.from( + driver?.getTransits(), + ).filter( + (t) => + t.getStatus() === Status.COMPLETED && + !dayjs(t.getCompleteAt()).isBefore(session.getLoggedAt()) && + !dayjs(t.getCompleteAt()).isAfter(session.getLoggedOutAt()), + ); + + const transitsDtosInSession: TransitDto[] = []; + for (const t of transitsInSession) { + const transitDTO = new TransitDto(t); + const byOwnerAndTransit = + await this.claimRepository.findByOwnerAndTransit(t.getClient(), t); + if (byOwnerAndTransit.length) { + const claim = new ClaimDto(byOwnerAndTransit[0]); + transitDTO.setClaimDTO(claim); + } + transitsDtosInSession.push(transitDTO); + } + sessionsWithTransits.set(dto, transitsDtosInSession); + } + driverReport.setSessions(sessionsWithTransits); + return driverReport; + } +} diff --git a/src/controllers/driver-session.controller.ts b/src/controllers/driver-session.controller.ts new file mode 100644 index 0000000..d126926 --- /dev/null +++ b/src/controllers/driver-session.controller.ts @@ -0,0 +1,44 @@ +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { DriverSessionService } from '../service/driver-session.service'; +import { DriverSession } from '../entity/driver-session.entity'; +import { CreateDriverSessionDto } from '../dto/create-driver-session.dto'; + +@Controller('drivers') +export class DriverSessionController { + constructor(private readonly driverSessionService: DriverSessionService) {} + + @Post(':driverId/driverSessions/login') + public async logIn( + @Param('driverId') driverId: string, + @Body() createSessionDto: CreateDriverSessionDto, + ): Promise { + await this.driverSessionService.logIn( + driverId, + createSessionDto.platesNumber, + createSessionDto.carClass, + createSessionDto.carBrand, + ); + } + + @Delete(':driverId/driverSessions/:sessionId') + public async logOut( + @Param('driverId') driverId: string, + @Param('sessionId') sessionId: string, + ): Promise { + await this.driverSessionService.logOut(sessionId); + } + + @Delete(':driverId/driverSessions') + public async logOutCurrent( + @Param('driverId') driverId: string, + ): Promise { + await this.driverSessionService.logOutCurrentSession(driverId); + } + + @Get(':driverId/driverSessions') + public async list( + @Param('driverId') driverId: string, + ): Promise { + return this.driverSessionService.findByDriver(driverId); + } +} diff --git a/src/controllers/driver-tracking.controller.ts b/src/controllers/driver-tracking.controller.ts new file mode 100644 index 0000000..21cdea3 --- /dev/null +++ b/src/controllers/driver-tracking.controller.ts @@ -0,0 +1,54 @@ +import { + Body, + Controller, + Get, + Param, + Post, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { DriverTrackingService } from '../service/driver-tracking.service'; +import { DriverPositionDto } from '../dto/driver-position.dto'; +import { CreateDriverPositionDto } from '../dto/create-driver-position.dto'; +import { DriverPosition } from '../entity/driver-position.entity'; + +@Controller('driverPositions') +export class DriverTrackingController { + constructor(private readonly trackingService: DriverTrackingService) {} + + @Post() + @UsePipes(ValidationPipe) + public async create( + @Body() createDriverPositionDto: CreateDriverPositionDto, + ): Promise { + console.log('dto', createDriverPositionDto); + const driverPosition = await this.trackingService.registerPosition( + createDriverPositionDto.driverId, + createDriverPositionDto.latitude, + createDriverPositionDto.longitude, + ); + + return this.toDto(driverPosition); + } + + @Get(':driverId/total') + public async calculateTravelledDistance( + @Param('driverId') driverId: string, + @Body() params: { from: number; to: number }, + ): Promise { + return this.trackingService.calculateTravelledDistance( + driverId, + params.from, + params.to, + ); + } + + private toDto(driverPosition: DriverPosition) { + return new DriverPositionDto( + driverPosition.getDriver().getId(), + driverPosition.getLatitude(), + driverPosition.getLongitude(), + driverPosition.getSeenAt(), + ); + } +} diff --git a/src/controllers/driver.controller.ts b/src/controllers/driver.controller.ts new file mode 100644 index 0000000..b8a18df --- /dev/null +++ b/src/controllers/driver.controller.ts @@ -0,0 +1,50 @@ +import { + Body, + Controller, + Get, + Param, + Post, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { DriverService } from '../service/driver.service'; +import { CreateDriverDto } from '../dto/create-driver.dto'; +import { DriverDto } from '../dto/driver.dto'; +import { DriverStatus } from '../entity/driver.entity'; + +@Controller('drivers') +export class DriverController { + constructor(private readonly driverService: DriverService) {} + + @Post() + @UsePipes(ValidationPipe) + public async createDriver( + @Body() createDriverDto: CreateDriverDto, + ): Promise { + const driver = await this.driverService.createDriver(createDriverDto); + + return this.driverService.loadDriver(driver.getId()); + } + + @Get(':id') + public async getDriver(@Param('id') id: string): Promise { + return this.driverService.loadDriver(id); + } + + @Post(':id') + public async updateDriver(@Param('id') id: string): Promise { + return this.driverService.loadDriver(id); + } + + @Post(':id/deactivate') + public async deactivateDriver(@Param('id') id: string): Promise { + await this.driverService.changeDriverStatus(id, DriverStatus.INACTIVE); + return this.driverService.loadDriver(id); + } + + @Post(':id/activate') + public async activateDriver(@Param('id') id: string): Promise { + await this.driverService.changeDriverStatus(id, DriverStatus.ACTIVE); + return this.driverService.loadDriver(id); + } +} diff --git a/src/controllers/transit-analyzer.controller.ts b/src/controllers/transit-analyzer.controller.ts new file mode 100644 index 0000000..be0ca5d --- /dev/null +++ b/src/controllers/transit-analyzer.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { TransitAnalyzerService } from '../service/transit-analyzer.service'; +import { AnalyzedAddressesDto } from '../dto/analyzed-addresses.dto'; +import { AddressDto } from '../dto/address.dto'; + +@Controller('transitAnalyze') +export class TransitAnalyzerController { + constructor(private readonly transitAnalyzer: TransitAnalyzerService) {} + + @Get(':clientId/:addressId') + public async analyze( + @Param('clientId') clientId: string, + @Param('addressId') addressId: string, + ): Promise { + const addresses = await this.transitAnalyzer.analyze(clientId, addressId); + const addressDtos = addresses.map((a) => new AddressDto(a)); + + return new AnalyzedAddressesDto(addressDtos); + } +} diff --git a/src/controllers/transit.controller.ts b/src/controllers/transit.controller.ts new file mode 100644 index 0000000..7baf21d --- /dev/null +++ b/src/controllers/transit.controller.ts @@ -0,0 +1,115 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { TransitService } from '../service/transit.service'; +import { TransitDto } from '../dto/transit.dto'; +import { CreateAddressDto } from '../dto/create-address.dto'; +import { AddressDto } from '../dto/address.dto'; +import { CreateTransitDto } from '../dto/create-transit.dto'; + +@Controller('transits') +export class TransitController { + constructor(private readonly transitService: TransitService) {} + + @Get(':transitId') + public async getTransit( + @Param('transitId') transitId: string, + ): Promise { + return this.transitService.loadTransit(transitId); + } + + @Post() + public async createTransit( + @Body() createTransitDto: CreateTransitDto, + ): Promise { + const transit = await this.transitService.createTransit(createTransitDto); + return this.transitService.loadTransit(transit.getId()); + } + + @Post(':transitId/changeAddressTo') + public async changeAddressTo( + @Param('transitId') transitId: string, + @Body() createAddressDto: CreateAddressDto, + ): Promise { + await this.transitService.changeTransitAddressTo( + transitId, + new AddressDto(createAddressDto), + ); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/changeAddressFrom') + public async changeAddressFrom( + @Param('transitId') transitId: string, + @Body() createAddressDto: CreateAddressDto, + ): Promise { + await this.transitService.changeTransitAddressFrom( + transitId, + new AddressDto(createAddressDto), + ); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/cancel') + public async cancel( + @Param('transitId') transitId: string, + ): Promise { + await this.transitService.cancelTransit(transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/publish') + public async publishTransit( + @Param('transitId') transitId: string, + ): Promise { + await this.transitService.publishTransit(transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/findDrivers') + public async findDriversForTransit( + @Param('transitId') transitId: string, + ): Promise { + await this.transitService.findDriversForTransit(transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/accept/:driverId') + public async acceptTransit( + @Param('transitId') transitId: string, + @Param('driverId') driverId: string, + ): Promise { + await this.transitService.acceptTransit(driverId, transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/start/:driverId') + public async start( + @Param('transitId') transitId: string, + @Param('driverId') driverId: string, + ): Promise { + await this.transitService.startTransit(driverId, transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/reject/:driverId') + public async reject( + @Param('transitId') transitId: string, + @Param('driverId') driverId: string, + ): Promise { + await this.transitService.rejectTransit(driverId, transitId); + return this.transitService.loadTransit(transitId); + } + + @Post(':transitId/complete/:driverId') + public async complete( + @Param('transitId') transitId: string, + @Param('driverId') driverId: string, + @Body() createAddressDto: CreateAddressDto, + ): Promise { + await this.transitService.completeTransitFromDto( + driverId, + transitId, + new AddressDto(createAddressDto), + ); + return this.transitService.loadTransit(transitId); + } +} diff --git a/src/dto/address.dto.ts b/src/dto/address.dto.ts new file mode 100644 index 0000000..7a3cc9d --- /dev/null +++ b/src/dto/address.dto.ts @@ -0,0 +1,126 @@ +import { Address } from '../entity/address.entity'; + +export class AddressDto { + public country: string; + + public district: string | null; + + public city: string; + + public street: string; + + public buildingNumber: number; + + public additionalNumber: number | null; + + public postalCode: string; + + public name: string; + + public constructor( + a: + | Address + | { + country: string; + city: string; + street: string; + buildingNumber: number; + postalCode: string; + }, + ) { + if (a instanceof Address) { + this.country = a.getCountry(); + this.district = a.getDistrict(); + this.city = a.getCity(); + this.street = a.getStreet(); + this.buildingNumber = a.getBuildingNumber(); + this.additionalNumber = a.getAdditionalNumber(); + this.postalCode = a.getPostalCode(); + this.name = a.getName(); + } else { + this.country = a.country; + this.city = a.city; + this.street = a.street; + this.buildingNumber = a.buildingNumber; + this.postalCode = a.postalCode; + } + } + public getCountry() { + return this.country; + } + + public setCountry(country: string) { + this.country = country; + } + + public getDistrict() { + return this.district; + } + + public setDistrict(district: string | null) { + this.district = district; + } + + public getCity() { + return this.city; + } + + public setCity(city: string) { + this.city = city; + } + + public getStreet() { + return this.street; + } + + public setStreet(street: string) { + this.street = street; + } + + public getBuildingNumber() { + return this.buildingNumber; + } + + public setBuildingNumber(buildingNumber: number) { + this.buildingNumber = buildingNumber; + } + + public getAdditionalNumber() { + return this.additionalNumber; + } + + public setAdditionalNumber(additionalNumber: number) { + this.additionalNumber = additionalNumber; + } + + public getPostalCode() { + return this.postalCode; + } + + public setPostalCode(postalCode: string) { + this.postalCode = postalCode; + } + + public getName() { + return this.name; + } + + public setName(name: string) { + this.name = name; + } + + public toAddressEntity() { + const address = new Address( + this.getCountry(), + this.getCity(), + this.getStreet(), + this.getBuildingNumber(), + ); + address.setAdditionalNumber(this.getAdditionalNumber()); + address.setName(this.getName() ?? ''); + address.setPostalCode(this.getPostalCode()); + address.setDistrict(this.getDistrict()); + address.setHash(); + return address; + } +} diff --git a/src/dto/analyzed-addresses.dto.ts b/src/dto/analyzed-addresses.dto.ts new file mode 100644 index 0000000..6a22be3 --- /dev/null +++ b/src/dto/analyzed-addresses.dto.ts @@ -0,0 +1,17 @@ +import { AddressDto } from './address.dto'; + +export class AnalyzedAddressesDto { + public addresses: AddressDto[]; + + constructor(addresses: AddressDto[]) { + this.addresses = addresses; + } + + public getAddresses() { + return this.addresses; + } + + public setAddresses(addresses: AddressDto[]) { + this.addresses = addresses; + } +} diff --git a/src/dto/awards-account.dto.ts b/src/dto/awards-account.dto.ts new file mode 100644 index 0000000..ac6c541 --- /dev/null +++ b/src/dto/awards-account.dto.ts @@ -0,0 +1,51 @@ +import { ClientDto } from './client.dto'; +import { AwardsAccount } from '../entity/awards-account.entity'; + +export class AwardsAccountDto { + private client: ClientDto; + + private date: number; + + private isActive: boolean; + + private transactions: number; + + constructor(account: AwardsAccount) { + this.isActive = account.isAwardActive(); + this.client = new ClientDto(account.getClient()); + this.transactions = account.getTransactions(); + this.date = account.getDate(); + } + + public setClient(client: ClientDto) { + this.client = client; + } + + public getClient() { + return this.client; + } + + public setDate(date: number) { + this.date = date; + } + + public setActive(active: boolean) { + this.isActive = active; + } + + public getTransactions() { + return this.transactions; + } + + public setTransactions(transactions: number) { + this.transactions = transactions; + } + + public getDate() { + return this.date; + } + + public getActive() { + return this.isActive; + } +} diff --git a/src/dto/car-type.dto.ts b/src/dto/car-type.dto.ts new file mode 100644 index 0000000..40ec98e --- /dev/null +++ b/src/dto/car-type.dto.ts @@ -0,0 +1,26 @@ +import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; + +export class CarTypeDto { + private id: string; + private carClass: CarClass; + + private description: string | null; + + private status: CarStatus; + + private carsCounter: number; + + private minNoOfCarsToActivateClass: number; + + private activeCarsCounter: number; + + constructor(carType: CarType) { + this.id = carType.getId(); + this.carClass = carType.getCarClass(); + this.status = carType.getStatus(); + this.description = carType.getDescription(); + this.carsCounter = carType.getCarsCounter(); + this.minNoOfCarsToActivateClass = carType.getMinNoOfCarsToActivateClass(); + this.activeCarsCounter = carType.getActiveCarsCounter(); + } +} diff --git a/src/dto/claim.dto.ts b/src/dto/claim.dto.ts new file mode 100644 index 0000000..2158248 --- /dev/null +++ b/src/dto/claim.dto.ts @@ -0,0 +1,150 @@ +import { Claim, ClaimStatus, CompletionMode } from '../entity/claim.entity'; +import { CreateClaimDto } from './create-claim.dto'; + +export class ClaimDto { + private claimID: string; + + private clientId: string; + + private transitId: string; + + private reason: string; + + private incidentDescription: string | null; + + private _isDraft: boolean; + + private creationDate: number; + + private completionDate: number | null; + + private changeDate: number | null; + + private completionMode: CompletionMode | null; + + private status: ClaimStatus; + + private claimNo: string; + + public constructor(claim: Claim | CreateClaimDto) { + if (!(claim instanceof Claim)) { + this.setClaimID(claim.clientId); + this.setReason(claim.reason); + this.setDraft(true); + this.setIncidentDescription(claim.incidentDescription); + } else { + if (claim.getStatus() === ClaimStatus.DRAFT) { + this.setDraft(true); + } else { + this.setDraft(false); + } + this.setClaimID(claim.getId()); + this.setReason(claim.getReason()); + this.setIncidentDescription(claim.getIncidentDescription()); + this.setTransitId(claim.getTransit().getId()); + this.setClientId(claim.getOwner().getId()); + this.setCompletionDate(claim.getCompletionDate()); + this.setChangeDate(claim.getChangeDate()); + this.setClaimNo(claim.getClaimNo()); + this.setStatus(claim.getStatus()); + this.setCompletionMode(claim.getCompletionMode()); + this.setCreationDate(claim.getCreationDate()); + } + } + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getCompletionDate() { + return this.completionDate; + } + + public setCompletionDate(completionDate: number | null) { + this.completionDate = completionDate; + } + + public getChangeDate() { + return this.changeDate; + } + + public setChangeDate(changeDate: number | null) { + this.changeDate = changeDate; + } + + public getCompletionMode() { + return this.completionMode; + } + + public setCompletionMode(completionMode: CompletionMode | null) { + this.completionMode = completionMode; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: ClaimStatus) { + this.status = status; + } + + public getClaimNo() { + return this.claimNo; + } + + public setClaimNo(claimNo: string) { + this.claimNo = claimNo; + } + + public getClaimID() { + return this.claimID; + } + + public setClaimID(claimID: string) { + this.claimID = claimID; + } + + public getClientId() { + return this.clientId; + } + + public setClientId(clientId: string) { + this.clientId = clientId; + } + + public getTransitId() { + return this.transitId; + } + + public setTransitId(transitId: string) { + this.transitId = transitId; + } + + public getReason() { + return this.reason; + } + + public setReason(reason: string) { + this.reason = reason; + } + + public getIncidentDescription() { + return this.incidentDescription; + } + + public setIncidentDescription(incidentDescription: string | null) { + this.incidentDescription = incidentDescription; + } + + public isDraft() { + return this._isDraft; + } + + public setDraft(draft: boolean) { + this._isDraft = draft; + } +} diff --git a/src/dto/client.dto.ts b/src/dto/client.dto.ts new file mode 100644 index 0000000..ff9071a --- /dev/null +++ b/src/dto/client.dto.ts @@ -0,0 +1,72 @@ +import { Client, ClientType, PaymentType, Type } from '../entity/client.entity'; + +export class ClientDto { + private id: string; + + private type: Type; + + private name: string; + + private lastName: string; + + private defaultPaymentType: PaymentType; + + private clientType: ClientType; + + constructor(client: Client) { + this.id = client.getId(); + this.type = client.getType(); + this.name = client.getName(); + this.lastName = client.getLastName(); + this.defaultPaymentType = client.getDefaultPaymentType(); + this.clientType = client.getClientType(); + } + + public getName() { + return this.name; + } + + public setName(name: string) { + this.name = name; + } + + public getLastName() { + return this.lastName; + } + + public setLastName(lastName: string) { + this.lastName = lastName; + } + + public getClientType() { + return this.clientType; + } + + public setClientType(clientType: ClientType) { + this.clientType = clientType; + } + + public getType() { + return this.type; + } + + public setType(type: Type) { + this.type = type; + } + + public getDefaultPaymentType() { + return this.defaultPaymentType; + } + + public setDefaultPaymentType(defaultPaymentType: PaymentType) { + this.defaultPaymentType = defaultPaymentType; + } + + public getId() { + return this.id; + } + + public setId(id: string) { + this.id = id; + } +} diff --git a/src/dto/contract-attachment.dto.ts b/src/dto/contract-attachment.dto.ts new file mode 100644 index 0000000..cb73520 --- /dev/null +++ b/src/dto/contract-attachment.dto.ts @@ -0,0 +1,37 @@ +import { + ContractAttachment, + ContractAttachmentStatus, +} from '../entity/contract-attachment.entity'; + +export class ContractAttachmentDto { + private id: string; + + private contractId: string; + + private data: string; + + private creationDate: number; + + private acceptedAt: number | null; + + private rejectedAt: number | null; + + private changeDate: number | null; + + private status: ContractAttachmentStatus; + + constructor(attachment: ContractAttachment, contractId?: string) { + this.id = attachment.getId(); + this.data = attachment.getData().toString(); + this.contractId = contractId || attachment.getContract().getId(); + this.creationDate = attachment.getCreationDate(); + this.rejectedAt = attachment.getRejectedAt(); + this.acceptedAt = attachment.getAcceptedAt(); + this.changeDate = attachment.getChangeDate(); + this.status = attachment.getStatus(); + } + + public getData() { + return this.data; + } +} diff --git a/src/dto/contract.dto.ts b/src/dto/contract.dto.ts new file mode 100644 index 0000000..c9a8b54 --- /dev/null +++ b/src/dto/contract.dto.ts @@ -0,0 +1,121 @@ +import { Contract, ContractStatus } from '../entity/contract.entity'; +import { ContractAttachmentDto } from './contract-attachment.dto'; + +export class ContractDto { + private id: string; + + private subject: string; + + private partnerName: string; + + private creationDate: number; + + private acceptedAt: number | null; + + private rejectedAt: number | null; + + private changeDate: number | null; + + private status: ContractStatus; + + private contractNo: string; + + private attachments: ContractAttachmentDto[] = []; + + constructor(contract: Contract) { + this.setContractNo(contract.getContractNo()); + this.setAcceptedAt(contract.getAcceptedAt()); + this.setRejectedAt(contract.getRejectedAt()); + this.setCreationDate(contract.getCreationDate()); + this.setChangeDate(contract.getChangeDate()); + this.setStatus(contract.getStatus()); + this.setPartnerName(contract.getPartnerName()); + this.setSubject(contract.getSubject()); + for (const attachment of contract.getAttachments()) { + this.attachments.push( + new ContractAttachmentDto(attachment, contract.getId()), + ); + } + this.setId(contract.getId()); + } + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getAcceptedAt() { + return this.acceptedAt; + } + + public setAcceptedAt(acceptedAt: number | null) { + this.acceptedAt = acceptedAt; + } + + public getRejectedAt() { + return this.rejectedAt; + } + + public setRejectedAt(rejectedAt: number | null) { + this.rejectedAt = rejectedAt; + } + + public getChangeDate() { + return this.changeDate; + } + + public setChangeDate(changeDate: number | null) { + this.changeDate = changeDate; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: ContractStatus) { + this.status = status; + } + + public getContractNo() { + return this.contractNo; + } + + public setContractNo(contractNo: string) { + this.contractNo = contractNo; + } + + public getPartnerName() { + return this.partnerName; + } + + public setPartnerName(partnerName: string) { + this.partnerName = partnerName; + } + + public getSubject() { + return this.subject; + } + + public setSubject(subject: string) { + this.subject = subject; + } + + public getId() { + return this.id; + } + + public setId(id: string) { + this.id = id; + } + + public getAttachments() { + return this.attachments; + } + + public setAttachments(attachments: ContractAttachmentDto[]) { + this.attachments = attachments; + } +} diff --git a/src/dto/create-address.dto.ts b/src/dto/create-address.dto.ts new file mode 100644 index 0000000..ca2b77f --- /dev/null +++ b/src/dto/create-address.dto.ts @@ -0,0 +1,25 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateAddressDto { + @IsNotEmpty() + public country: string; + + public district: string | null; + + @IsNotEmpty() + public city: string; + + @IsNotEmpty() + public street: string; + + @IsNotEmpty() + public buildingNumber: number; + + @IsNotEmpty() + public additionalNumber: number | null; + + @IsNotEmpty() + public postalCode: string; + + public name: string; +} diff --git a/src/dto/create-car-type.dto.ts b/src/dto/create-car-type.dto.ts new file mode 100644 index 0000000..eb4e19e --- /dev/null +++ b/src/dto/create-car-type.dto.ts @@ -0,0 +1,9 @@ +import { IsEnum } from 'class-validator'; +import { CarClass } from '../entity/car-type.entity'; + +export class CreateCarTypeDto { + public description: string; + + @IsEnum(CarClass) + public carClass: CarClass; +} diff --git a/src/dto/create-claim.dto.ts b/src/dto/create-claim.dto.ts new file mode 100644 index 0000000..8f1688d --- /dev/null +++ b/src/dto/create-claim.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateClaimDto { + @IsNotEmpty() + public reason: string; + + @IsNotEmpty() + public transitId: string; + + @IsNotEmpty() + public clientId: string; + + @IsNotEmpty() + public incidentDescription: string; +} diff --git a/src/dto/create-client.dto.ts b/src/dto/create-client.dto.ts new file mode 100644 index 0000000..f874859 --- /dev/null +++ b/src/dto/create-client.dto.ts @@ -0,0 +1,16 @@ +import { PaymentType, Type } from '../entity/client.entity'; +import { IsEnum, IsNotEmpty } from 'class-validator'; + +export class CreateClientDto { + @IsNotEmpty() + public name: string; + + @IsNotEmpty() + public lastName: string; + + @IsEnum(Type) + public type: Type; + + @IsEnum(PaymentType) + public defaultPaymentType: PaymentType; +} diff --git a/src/dto/create-contract-attachment.dto.ts b/src/dto/create-contract-attachment.dto.ts new file mode 100644 index 0000000..48892a6 --- /dev/null +++ b/src/dto/create-contract-attachment.dto.ts @@ -0,0 +1,6 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateContractAttachmentDto { + @IsNotEmpty() + public data: string; +} diff --git a/src/dto/create-contract.dto.ts b/src/dto/create-contract.dto.ts new file mode 100644 index 0000000..52d45d3 --- /dev/null +++ b/src/dto/create-contract.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateContractDto { + @IsNotEmpty() + public subject: string; + + @IsNotEmpty() + public partnerName: string; +} diff --git a/src/dto/create-driver-position.dto.ts b/src/dto/create-driver-position.dto.ts new file mode 100644 index 0000000..93b5aee --- /dev/null +++ b/src/dto/create-driver-position.dto.ts @@ -0,0 +1,10 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateDriverPositionDto { + public latitude: number; + + public longitude: number; + + @IsNotEmpty() + public driverId: string; +} diff --git a/src/dto/create-driver-session.dto.ts b/src/dto/create-driver-session.dto.ts new file mode 100644 index 0000000..6ef516a --- /dev/null +++ b/src/dto/create-driver-session.dto.ts @@ -0,0 +1,13 @@ +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { CarClass } from '../entity/car-type.entity'; + +export class CreateDriverSessionDto { + @IsNotEmpty() + public carBrand: string; + + @IsNotEmpty() + public platesNumber: string; + + @IsEnum(CarClass) + public carClass: CarClass; +} diff --git a/src/dto/create-driver.dto.ts b/src/dto/create-driver.dto.ts new file mode 100644 index 0000000..22915c0 --- /dev/null +++ b/src/dto/create-driver.dto.ts @@ -0,0 +1,14 @@ +import { IsNotEmpty } from 'class-validator'; + +export class CreateDriverDto { + @IsNotEmpty() + public firstName: string; + + @IsNotEmpty() + public lastName: string; + + @IsNotEmpty() + public driverLicense: string; + + public photo: string; +} diff --git a/src/dto/create-transit.dto.ts b/src/dto/create-transit.dto.ts new file mode 100644 index 0000000..3e449cf --- /dev/null +++ b/src/dto/create-transit.dto.ts @@ -0,0 +1,25 @@ +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { CarClass } from '../entity/car-type.entity'; + +export interface Address { + country: string; + district?: string | null; + city: string; + street: string; + buildingNumber: number; + additionalNumber: number | null; + postalCode: string; + name?: string; +} + +export class CreateTransitDto { + @IsNotEmpty() + public clientId: string; + + public from: Address; + + public to: Address; + + @IsEnum(CarClass) + public carClass: CarClass; +} diff --git a/src/dto/driver-attribute.dto.ts b/src/dto/driver-attribute.dto.ts new file mode 100644 index 0000000..4542de5 --- /dev/null +++ b/src/dto/driver-attribute.dto.ts @@ -0,0 +1,41 @@ +import objectHash from 'object-hash'; +import { + DriverAttribute, + DriverAttributeName, +} from '../entity/driver-attribute.entity'; + +export class DriverAttributeDto { + public name: DriverAttributeName; + + public value: string; + + constructor(driverAttribute: DriverAttribute) { + this.name = driverAttribute.getName(); + this.value = driverAttribute.getValue(); + } + + public createDriverAttribute(name: DriverAttributeName, value: string) { + this.name = name; + this.value = value; + } + + public getName() { + return this.name; + } + + public setName(name: DriverAttributeName) { + this.name = name; + } + + public getValue() { + return this.value; + } + + public setValue(value: string) { + this.value = value; + } + + public hashCode() { + return objectHash({ name: this.name, value: this.value }); + } +} diff --git a/src/dto/driver-position-v2.dto.ts b/src/dto/driver-position-v2.dto.ts new file mode 100644 index 0000000..83febf9 --- /dev/null +++ b/src/dto/driver-position-v2.dto.ts @@ -0,0 +1,55 @@ +import { Driver } from '../entity/driver.entity'; + +export class DriverPositionV2Dto { + private driver: Driver; + + private latitude: number; + + private longitude: number; + + private seenAt: number; + + constructor( + driver: Driver, + latitude: number, + longitude: number, + seenAt: number, + ) { + this.driver = driver; + this.latitude = latitude; + this.longitude = longitude; + this.seenAt = seenAt; + } + + public getDriver() { + return this.driver; + } + + public setDriver(driver: Driver) { + this.driver = driver; + } + + public getLatitude() { + return this.latitude; + } + + public setLatitude(latitude: number) { + this.latitude = latitude; + } + + public getLongitude() { + return this.longitude; + } + + public setLongitude(longitude: number) { + this.longitude = longitude; + } + + public getSeenAt() { + return this.seenAt; + } + + public setSeenAt(seenAt: number) { + this.seenAt = seenAt; + } +} diff --git a/src/dto/driver-position.dto.ts b/src/dto/driver-position.dto.ts new file mode 100644 index 0000000..0092a28 --- /dev/null +++ b/src/dto/driver-position.dto.ts @@ -0,0 +1,21 @@ +export class DriverPositionDto { + private driverId: string; + + private latitude: number; + + private longitude: number; + + private seenAt: number; + + constructor( + driverId: string, + latitude: number, + longitude: number, + seenAt: number, + ) { + this.driverId = driverId; + this.latitude = latitude; + this.longitude = longitude; + this.seenAt = seenAt; + } +} diff --git a/src/dto/driver-report.dto.ts b/src/dto/driver-report.dto.ts new file mode 100644 index 0000000..fcc6e33 --- /dev/null +++ b/src/dto/driver-report.dto.ts @@ -0,0 +1,36 @@ +import { DriverDto } from './driver.dto'; +import { DriverAttributeDto } from './driver-attribute.dto'; +import { DriverSessionDto } from './driver-session.dto'; +import { TransitDto } from './transit.dto'; + +export class DriverReport { + public driverDto: DriverDto; + + public attributes: DriverAttributeDto[] = []; + + public sessions: Map = new Map(); + + public getDriverDto() { + return this.driverDto; + } + + public setDriverDTO(driverDto: DriverDto) { + this.driverDto = driverDto; + } + + public getAttributes() { + return this.attributes; + } + + public setAttributes(attributes: DriverAttributeDto[]) { + this.attributes = attributes; + } + + public getSessions() { + return this.sessions; + } + + public setSessions(sessions: Map) { + this.sessions = sessions; + } +} diff --git a/src/dto/driver-session.dto.ts b/src/dto/driver-session.dto.ts new file mode 100644 index 0000000..a8028db --- /dev/null +++ b/src/dto/driver-session.dto.ts @@ -0,0 +1,62 @@ +import { CarClass } from '../entity/car-type.entity'; +import { DriverSession } from '../entity/driver-session.entity'; + +export class DriverSessionDto { + private loggedAt: number | null; + + private loggedOutAt: number | null; + + private platesNumber: string; + + private carClass: CarClass; + + private carBrand: string; + + constructor(session: DriverSession) { + this.carBrand = session.getCarBrand(); + this.platesNumber = session.getPlatesNumber(); + this.loggedAt = session.getLoggedAt(); + this.loggedOutAt = session.getLoggedOutAt(); + this.carClass = session.getCarClass(); + } + + public getCarBrand() { + return this.carBrand; + } + + public setCarBrand(carBrand: string) { + this.carBrand = carBrand; + } + + public getLoggedAt() { + return this.loggedAt; + } + + public setLoggedAt(loggedAt: number) { + this.loggedAt = loggedAt; + } + + public getLoggedOutAt() { + return this.loggedOutAt; + } + + public setLoggedOutAt(loggedOutAt: number) { + this.loggedOutAt = loggedOutAt; + } + + public getPlatesNumber() { + return this.platesNumber; + } + + public setPlatesNumber(platesNumber: string) { + this.platesNumber = platesNumber; + } + + public getCarClass() { + return this.carClass; + } + + public setCarClass(carClass: CarClass) { + this.carClass = carClass; + } +} diff --git a/src/dto/driver.dto.ts b/src/dto/driver.dto.ts new file mode 100644 index 0000000..b70d7c0 --- /dev/null +++ b/src/dto/driver.dto.ts @@ -0,0 +1,27 @@ +import { Driver, DriverStatus, DriverType } from '../entity/driver.entity'; + +export class DriverDto { + private id: string; + + private status: DriverStatus; + + private firstName: string; + + private lastName: string; + + private driverLicense: string; + + private photo: string | null; + + private type: DriverType; + + constructor(driver: Driver) { + this.id = driver.getId(); + this.firstName = driver.getFirstName(); + this.lastName = driver.getLastName(); + this.driverLicense = driver.getDriverLicense(); + this.photo = driver.getPhoto(); + this.status = driver.getStatus(); + this.type = driver.getType(); + } +} diff --git a/src/dto/transit.dto.ts b/src/dto/transit.dto.ts new file mode 100644 index 0000000..a0c1606 --- /dev/null +++ b/src/dto/transit.dto.ts @@ -0,0 +1,317 @@ +import { DayOfWeek, Status, Transit } from '../entity/transit.entity'; +import { DriverDto } from './driver.dto'; +import { ClaimDto } from './claim.dto'; +import { AddressDto } from './address.dto'; +import { CarClass } from '../entity/car-type.entity'; +import { ClientDto } from './client.dto'; +import * as dayjs from 'dayjs'; +import * as dayOfYear from 'dayjs/plugin/dayOfYear'; +import { NotAcceptableException } from '@nestjs/common'; + +dayjs.extend(dayOfYear); + +export class TransitDto { + public id: string; + + public tariff: string; + + public status: Status; + + public driver: DriverDto; + + public factor: number | null; + + public distance: number; + + public distanceUnit: string; + + public kmRate: number; + + public price: number; + + public driverFee: number; + + public estimatedPrice: number; + + public baseFee: number; + + public date: number; + + public dateTime: number; + + public published: number; + + public acceptedAt: number | null; + + public started: number | null; + + public completeAt: number | null; + + public claimDto: ClaimDto; + + public proposedDrivers: DriverDto[] = []; + + public to: AddressDto; + + public from: AddressDto; + + public carClass: CarClass; + + public clientDto: ClientDto; + + constructor(transit: Transit) { + this.id = transit.getId(); + this.distance = transit.getKm(); + this.factor = transit.factor; + const price = transit.getPrice(); + if (price) { + this.price = price; + } + this.date = transit.getDateTime(); + this.status = transit.getStatus(); + this.setTariff(); + for (const d of transit.getProposedDrivers()) { + this.proposedDrivers.push(new DriverDto(d)); + } + this.to = new AddressDto(transit.getTo()); + this.from = new AddressDto(transit.getFrom()); + this.carClass = transit.getCarType(); + this.clientDto = new ClientDto(transit.getClient()); + if (transit.getDriversFee() != null) { + this.driverFee = transit.getDriversFee(); + } + const estimatedPrice = transit.getEstimatedPrice(); + if (estimatedPrice) { + this.estimatedPrice = estimatedPrice; + } + this.dateTime = transit.getDateTime(); + this.published = transit.getPublished(); + this.acceptedAt = transit.getAcceptedAt(); + this.started = transit.getStarted(); + this.completeAt = transit.getCompleteAt(); + } + + public getKmRate() { + return this.kmRate; + } + + public setTariff() { + const day = dayjs(); + + // wprowadzenie nowych cennikow od 1.01.2019 + if (day.get('year') <= 2018) { + this.kmRate = 1.0; + this.tariff = 'Standard'; + return; + } + + const year = day.get('year'); + const leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + + if ( + (leap && day.dayOfYear() == 366) || + (!leap && day.dayOfYear() == 365) || + (day.dayOfYear() == 1 && day.get('hour') <= 6) + ) { + this.tariff = 'Sylwester'; + this.kmRate = 3.5; + } else { + switch (day.get('day')) { + case DayOfWeek.MONDAY: + case DayOfWeek.TUESDAY: + case DayOfWeek.WEDNESDAY: + case DayOfWeek.THURSDAY: + this.kmRate = 1.0; + this.tariff = 'Standard'; + break; + case DayOfWeek.FRIDAY: + if (day.get('hour') < 17) { + this.tariff = 'Standard'; + this.kmRate = 1.0; + } else { + this.tariff = 'Weekend+'; + this.kmRate = 2.5; + } + break; + case DayOfWeek.SATURDAY: + if (day.get('hour') < 6 || day.get('hour') >= 17) { + this.kmRate = 2.5; + this.tariff = 'Weekend+'; + } else if (day.get('hour') < 17) { + this.kmRate = 1.5; + this.tariff = 'Weekend'; + } + break; + case DayOfWeek.SUNDAY: + if (day.get('hour') < 6) { + this.kmRate = 2.5; + this.tariff = 'Weekend+'; + } else { + this.kmRate = 1.5; + this.tariff = 'Weekend'; + } + break; + } + } + } + + public getTariff() { + return this.tariff; + } + + public getDistance(unit: string) { + this.distanceUnit = unit; + if (unit === 'km') { + if (this.distance == Math.ceil(this.distance)) { + return new Intl.NumberFormat('en-US', { + style: 'unit', + unit: 'length-kilometer', + }).format(Math.round(this.distance)); + } + return new Intl.NumberFormat('en-US', { + style: 'unit', + unit: 'length-kilometer', + }).format(this.distance); + } + if (unit === 'miles') { + const distance = this.distance / 1.609344; + if (distance == Math.ceil(distance)) { + return new Intl.NumberFormat('en-US', { + style: 'unit', + unit: 'length-mile', + }).format(Math.round(this.distance)); + } + return new Intl.NumberFormat('en-US', { + style: 'unit', + unit: 'length-mile', + }).format(this.distance); + } + if (unit === 'm') { + return new Intl.NumberFormat('en-US', { + style: 'unit', + unit: 'length-meter', + }).format(Math.round(this.distance * 1000)); + } + throw new NotAcceptableException('Invalid unit ' + unit); + } + + public getProposedDrivers() { + return this.proposedDrivers; + } + + public setProposedDrivers(proposedDrivers: DriverDto[]) { + this.proposedDrivers = proposedDrivers; + } + + public getClaimDTO() { + return this.claimDto; + } + + public setClaimDTO(claimDto: ClaimDto) { + this.claimDto = claimDto; + } + + public getTo() { + return this.to; + } + + public setTo(to: AddressDto) { + this.to = to; + } + + public getFrom() { + return this.from; + } + + public setFrom(from: AddressDto) { + this.from = from; + } + + public getCarClass() { + return this.carClass; + } + + public setCarClass(carClass: CarClass) { + this.carClass = carClass; + } + + public getClientDto() { + return this.clientDto; + } + + public setClientDTO(clientDto: ClientDto) { + this.clientDto = clientDto; + } + + public getId() { + return this.id; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: Status) { + this.status = status; + } + + public getPrice() { + return this.price; + } + + public getDriverFee() { + return this.driverFee; + } + + public setDriverFee(driverFee: number) { + this.driverFee = driverFee; + } + + public getDateTime() { + return this.dateTime; + } + + public setDateTime(dateTime: number) { + this.dateTime = dateTime; + } + + public getPublished() { + return this.published; + } + + public setPublished(published: number) { + this.published = published; + } + + public getAcceptedAt() { + return this.acceptedAt; + } + + public setAcceptedAt(acceptedAt: number) { + this.acceptedAt = acceptedAt; + } + + public getStarted() { + return this.started; + } + + public setStarted(started: number) { + this.started = started; + } + + public getCompleteAt() { + return this.completeAt; + } + + public setCompleteAt(completeAt: number) { + this.completeAt = completeAt; + } + + public getEstimatedPrice() { + return this.estimatedPrice; + } + + public setEstimatedPrice(estimatedPrice: number) { + this.estimatedPrice = estimatedPrice; + } +} diff --git a/src/entity/address.entity.ts b/src/entity/address.entity.ts new file mode 100644 index 0000000..fff907b --- /dev/null +++ b/src/entity/address.entity.ts @@ -0,0 +1,160 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity } from 'typeorm'; +import * as objectHash from 'object-hash'; + +@Entity() +export class Address extends BaseEntity { + @Column() + private country: string; + + @Column({ nullable: true, type: 'varchar' }) + private district: string | null; + + @Column() + private city: string; + + @Column() + private street: string; + + @Column() + private buildingNumber: number; + + @Column({ nullable: true, type: 'integer' }) + private additionalNumber: number | null; + + @Column() + private postalCode: string; + + @Column() + private name: string; + + @Column({ unique: true }) + private hash: string; + + constructor( + country: string, + city: string, + street: string, + buildingNumber: number, + ) { + super(); + this.country = country; + this.city = city; + this.street = street; + this.buildingNumber = buildingNumber; + } + + public getCountry() { + return this.country; + } + + public setCountry(country: string) { + this.country = country; + } + + public getDistrict() { + return this.district; + } + + public setDistrict(district: string | null) { + this.district = district; + } + + public getCity() { + return this.city; + } + + public setCity(city: string) { + this.city = city; + } + + public getStreet() { + return this.street; + } + + public setStreet(street: string) { + this.street = street; + } + + public getBuildingNumber() { + return this.buildingNumber; + } + + public setBuildingNumber(buildingNumber: number) { + this.buildingNumber = buildingNumber; + } + + public getAdditionalNumber() { + return this.additionalNumber; + } + + public setAdditionalNumber(additionalNumber: number | null) { + this.additionalNumber = additionalNumber; + } + + public getPostalCode() { + return this.postalCode; + } + + public setPostalCode(postalCode: string) { + this.postalCode = postalCode; + } + + public getName() { + return this.name; + } + + public setName(name: string) { + this.name = name; + } + + public setHash() { + this.hash = objectHash({ + country: this.country, + district: this.district, + city: this.city, + street: this.street, + buildingNumber: this.buildingNumber, + additionalNumber: this.additionalNumber, + postalCode: this.postalCode, + name: this.name, + }); + } + + public getHash() { + this.setHash(); + return this.hash; + } + + public toString() { + return ( + 'Address{' + + "id='" + + this.getId() + + "'" + + ", country='" + + this.country + + "'" + + ", district='" + + this.district + + "'" + + ", city='" + + this.city + + "'" + + ", street='" + + this.street + + "'" + + ', buildingNumber=' + + this.buildingNumber + + ', additionalNumber=' + + this.additionalNumber + + ", postalCode='" + + this.postalCode + + "'" + + ", name='" + + this.name + + "'" + + '}' + ); + } +} diff --git a/src/entity/awarded-miles.entity.ts b/src/entity/awarded-miles.entity.ts new file mode 100644 index 0000000..df67f1e --- /dev/null +++ b/src/entity/awarded-miles.entity.ts @@ -0,0 +1,75 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Client } from './client.entity'; +import { Transit } from './transit.entity'; + +@Entity() +export class AwardedMiles extends BaseEntity { + // Aggregate + // 1. mile celowo są osobno, aby się mogło rozjechać na ich wydawaniu -> docelowo: kolekcja VOs w agregacie + // VO + // 1. miles + expirationDate -> VO przykrywające logikę walidacji, czy nie przekroczono daty ważności punktów + // 2. wydzielenie interfejsu Miles -> różne VO z różną logiką, np. ExpirableMiles, NonExpirableMiles, LinearExpirableMiles + + @ManyToOne(() => Client) + public client: Client; + + @Column() + private miles: number; + + @Column({ default: Date.now(), type: 'bigint' }) + private date: number; + + @Column({ nullable: true, type: 'bigint' }) + private expirationDate: number | null; + + @Column({ nullable: true, type: 'boolean' }) + private isSpecial: boolean | null; + + @ManyToOne(() => Transit) + public transit: Transit | null; + + public getClient() { + return this.client; + } + + public setClient(client: Client) { + this.client = client; + } + + public getMiles() { + return this.miles; + } + + public setMiles(miles: number) { + this.miles = miles; + } + + public getDate() { + return this.date; + } + + public setDate(date: number) { + this.date = date; + } + + public getExpirationDate() { + return this.expirationDate; + } + + public setExpirationDate(expirationDate: number) { + this.expirationDate = expirationDate; + } + + public getSpecial() { + return this.isSpecial; + } + + public setSpecial(special: boolean) { + this.isSpecial = special; + } + + public setTransit(transit: Transit | null) { + this.transit = transit; + } +} diff --git a/src/entity/awards-account.entity.ts b/src/entity/awards-account.entity.ts new file mode 100644 index 0000000..d3d3c28 --- /dev/null +++ b/src/entity/awards-account.entity.ts @@ -0,0 +1,51 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, JoinColumn, OneToOne } from 'typeorm'; +import { Client } from './client.entity'; + +@Entity() +export class AwardsAccount extends BaseEntity { + @Column({ default: Date.now(), type: 'bigint' }) + private date: number; + + @Column({ default: false, type: 'boolean' }) + private isActive: boolean; + + @Column({ default: 0, type: 'integer' }) + private transactions: number; + + @OneToOne(() => Client, { eager: true }) + @JoinColumn() + private client: Client; + + public setClient(client: Client) { + this.client = client; + } + + public getClient() { + return this.client; + } + + public setDate(date: number) { + this.date = date; + } + + public setActive(active: boolean) { + this.isActive = active; + } + + public isAwardActive() { + return this.isActive; + } + + public getTransactions() { + return this.transactions; + } + + public increaseTransactions() { + this.transactions++; + } + + public getDate() { + return this.date; + } +} diff --git a/src/entity/car-type.entity.ts b/src/entity/car-type.entity.ts new file mode 100644 index 0000000..d35135d --- /dev/null +++ b/src/entity/car-type.entity.ts @@ -0,0 +1,111 @@ +import { NotAcceptableException } from '@nestjs/common'; +import { Column, Entity } from 'typeorm'; +import { BaseEntity } from '../common/base.entity'; + +export enum CarClass { + ECO = 'eco', + REGULAR = 'regular', + VAN = 'van', + PREMIUM = 'premium', +} + +export enum CarStatus { + INACTIVE = 'inactive', + ACTIVE = 'active', +} + +@Entity() +export class CarType extends BaseEntity { + @Column({ enum: CarClass, type: 'enum' }) + private carClass: CarClass; + + @Column({ nullable: true, type: 'varchar' }) + private description: string | null; + + @Column({ default: CarStatus.INACTIVE }) + private status: CarStatus; + + @Column({ type: 'int', default: 0 }) + private carsCounter: number; + + @Column({ type: 'int', default: 0 }) + private minNoOfCarsToActivateClass: number; + + @Column({ type: 'int', default: 0 }) + private activeCarsCounter: number; + + constructor( + carClass: CarClass, + description: string, + minNoOfCarsToActivateClass: number, + ) { + super(); + this.carClass = carClass; + this.description = description; + this.minNoOfCarsToActivateClass = minNoOfCarsToActivateClass; + } + + public registerActiveCar() { + this.activeCarsCounter++; + } + + public unregisterActiveCar() { + this.activeCarsCounter--; + } + + public registerCar() { + this.carsCounter++; + } + + public unregisterCar() { + this.carsCounter--; + if (this.carsCounter < 0) { + throw new NotAcceptableException('Cars counter can not be below 0'); + } + } + + public activate() { + if (this.carsCounter < this.minNoOfCarsToActivateClass) { + throw new NotAcceptableException( + `Cannot activate car class when less than ${this.minNoOfCarsToActivateClass} cars in the fleet`, + ); + } + this.status = CarStatus.ACTIVE; + } + + public deactivate() { + this.status = CarStatus.INACTIVE; + } + + public getCarClass() { + return this.carClass; + } + + public setCarClass(carClass: CarClass) { + this.carClass = carClass; + } + + public getDescription() { + return this.description; + } + + public setDescription(description: string) { + this.description = description; + } + + public getStatus() { + return this.status; + } + + public getCarsCounter() { + return this.carsCounter; + } + + public getActiveCarsCounter() { + return this.activeCarsCounter; + } + + public getMinNoOfCarsToActivateClass() { + return this.minNoOfCarsToActivateClass; + } +} diff --git a/src/entity/claim-attachment.entity.ts b/src/entity/claim-attachment.entity.ts new file mode 100644 index 0000000..9dd7fbc --- /dev/null +++ b/src/entity/claim-attachment.entity.ts @@ -0,0 +1,56 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Claim } from './claim.entity'; + +@Entity() +export class ClaimAttachment extends BaseEntity { + @ManyToOne(() => Claim) + private claim: Claim; + + @Column({ type: 'bigint' }) + private creationDate: number; + + @Column({ nullable: true, type: 'varchar' }) + private description: string | null; + + @Column({ type: 'bytea' }) + private data: Buffer; + + public getClient() { + return this.claim.getOwner(); + } + + public getClaim() { + return this.claim; + } + + public setClaim(claim: Claim) { + this.claim = claim; + } + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getDescription() { + return this.description; + } + + public setDescription(description: string) { + this.description = description; + } + + public getData() { + return this.data; + } + + public setData(data: string) { + this.data = Buffer.from( + '\\x' + Buffer.from(data, 'base64').toString('hex'), + ); + } +} diff --git a/src/entity/claim.entity.ts b/src/entity/claim.entity.ts new file mode 100644 index 0000000..bd638f4 --- /dev/null +++ b/src/entity/claim.entity.ts @@ -0,0 +1,132 @@ +import { BaseEntity } from '../common/base.entity'; +import { Client } from './client.entity'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { Transit } from './transit.entity'; + +export enum ClaimStatus { + DRAFT = 'draft', + NEW = 'new', + IN_PROCESS = 'in_process', + REFUNDED = 'refunded', + ESCALATED = 'escalated', + REJECTED = 'rejected', +} + +export enum CompletionMode { + MANUAL = 'manual', + AUTOMATIC = 'automatic', +} + +@Entity() +export class Claim extends BaseEntity { + @ManyToOne(() => Client, (client) => client.claims) + public owner: Client; + + @OneToOne(() => Transit) + @JoinColumn() + private transit: Transit; + + @Column({ type: 'bigint' }) + private creationDate: number; + + @Column({ nullable: true, type: 'bigint' }) + private completionDate: number | null; + + @Column({ nullable: true, type: 'bigint' }) + private changeDate: number | null; + + @Column() + private reason: string; + + @Column({ nullable: true, type: 'varchar' }) + private incidentDescription: string | null; + + @Column({ nullable: true, enum: CompletionMode, type: 'enum', default: null }) + private completionMode: CompletionMode | null; + + @Column() + private status: ClaimStatus; + + @Column() + private claimNo: string; + + public getClaimNo() { + return this.claimNo; + } + + public setClaimNo(claimNo: string) { + this.claimNo = claimNo; + } + + public getOwner() { + return this.owner; + } + + public setOwner(owner: Client) { + this.owner = owner; + } + + public getTransit() { + return this.transit; + } + + public setTransit(transit: Transit) { + this.transit = transit; + } + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getCompletionDate() { + return this.completionDate; + } + + public setCompletionDate(completionDate: number) { + this.completionDate = completionDate; + } + + public getIncidentDescription() { + return this.incidentDescription; + } + + public setIncidentDescription(incidentDescription: string | null) { + this.incidentDescription = incidentDescription; + } + + public getCompletionMode() { + return this.completionMode; + } + + public setCompletionMode(completionMode: CompletionMode) { + this.completionMode = completionMode; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: ClaimStatus) { + this.status = status; + } + + public getChangeDate() { + return this.changeDate; + } + + public setChangeDate(changeDate: number) { + this.changeDate = changeDate; + } + + public getReason() { + return this.reason; + } + + public setReason(reason: string) { + this.reason = reason; + } +} diff --git a/src/entity/client.entity.ts b/src/entity/client.entity.ts new file mode 100644 index 0000000..9e93af1 --- /dev/null +++ b/src/entity/client.entity.ts @@ -0,0 +1,88 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, OneToMany } from 'typeorm'; +import { Claim } from './claim.entity'; + +export enum ClientType { + INDIVIDUAL = 'individual', + COMPANY = 'company', +} + +export enum PaymentType { + PRE_PAID = 'pre_paid', + POST_PAID = 'post_paid', + MONTHLY_INVOICE = 'monthly_invoice', +} + +export enum Type { + NORMAL = 'normal', + VIP = 'vip', +} + +@Entity() +export class Client extends BaseEntity { + @Column() + private type: Type; + + @Column() + private name: string; + + @Column() + private lastName: string; + + @Column() + private defaultPaymentType: PaymentType; + + @Column({ type: 'enum', enum: ClientType, default: ClientType.INDIVIDUAL }) + private clientType: ClientType; + + @OneToMany(() => Claim, (claim) => claim.owner) + public claims: Claim[]; + + public getClaims() { + return this.claims; + } + + public setClaims(claims: Claim[]) { + this.claims = claims; + } + + public getName() { + return this.name; + } + + public setName(name: string) { + this.name = name; + } + + public getLastName() { + return this.lastName; + } + + public setLastName(lastName: string) { + this.lastName = lastName; + } + + public getClientType() { + return this.clientType; + } + + public setClientType(clientType: ClientType) { + this.clientType = clientType; + } + + public getType() { + return this.type; + } + + public setType(type: Type) { + this.type = type; + } + + public getDefaultPaymentType() { + return this.defaultPaymentType; + } + + public setDefaultPaymentType(defaultPaymentType: PaymentType) { + this.defaultPaymentType = defaultPaymentType; + } +} diff --git a/src/entity/contract-attachment.entity.ts b/src/entity/contract-attachment.entity.ts new file mode 100644 index 0000000..42b5cde --- /dev/null +++ b/src/entity/contract-attachment.entity.ts @@ -0,0 +1,93 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { Contract } from './contract.entity'; + +export enum ContractAttachmentStatus { + PROPOSED = 'proposed', + ACCEPTED_BY_ONE_SIDE = 'accepted_by_one_side', + ACCEPTED_BY_BOTH_SIDES = 'accepted_by_both_side', + REJECTED = 'rejected', +} + +@Entity() +export class ContractAttachment extends BaseEntity { + @ManyToOne(() => Contract, (contract) => contract.attachments) + @JoinColumn() + public contract: Contract; + + @Column({ type: 'bytea' }) + private data: Buffer; + + @Column({ default: Date.now(), type: 'bigint' }) + private creationDate: number; + + @Column({ nullable: true, type: 'bigint' }) + private acceptedAt: number | null; + + @Column({ nullable: true, type: 'bigint' }) + private rejectedAt: number | null; + + @Column({ nullable: true, type: 'bigint' }) + private changeDate: number; + + @Column({ default: ContractAttachmentStatus.PROPOSED }) + private status: ContractAttachmentStatus; + + public getData() { + return this.data; + } + + public setData(data: string) { + this.data = Buffer.from( + '\\x' + Buffer.from(data, 'base64').toString('hex'), + ); + } + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getAcceptedAt() { + return this.acceptedAt; + } + + public setAcceptedAt(acceptedAt: number) { + this.acceptedAt = acceptedAt; + } + + public getRejectedAt() { + return this.rejectedAt; + } + + public setRejectedAt(rejectedAt: number) { + this.rejectedAt = rejectedAt; + } + + public getChangeDate() { + return this.changeDate; + } + + public setChangeDate(changeDate: number) { + this.changeDate = changeDate; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: ContractAttachmentStatus) { + this.status = status; + } + + public getContract() { + return this.contract; + } + + public setContract(contract: Contract) { + this.contract = contract; + } +} diff --git a/src/entity/contract.entity.ts b/src/entity/contract.entity.ts new file mode 100644 index 0000000..e58ff86 --- /dev/null +++ b/src/entity/contract.entity.ts @@ -0,0 +1,115 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, OneToMany } from 'typeorm'; +import { ContractAttachment } from './contract-attachment.entity'; + +export enum ContractStatus { + NEGOTIATIONS_IN_PROGRESS = 'negotiations_in_progress', + REJECTED = 'rejected', + ACCEPTED = 'accepted', +} + +@Entity() +export class Contract extends BaseEntity { + @OneToMany( + () => ContractAttachment, + (contractAttachment) => contractAttachment.contract, + { eager: true }, + ) + public attachments: ContractAttachment[]; + + @Column() + private partnerName: string; + + @Column() + private subject: string; + + @Column({ default: Date.now(), type: 'bigint' }) + private creationDate: number; + + @Column({ nullable: true, type: 'bigint' }) + private acceptedAt: number | null; + + @Column({ nullable: true, type: 'bigint' }) + private rejectedAt: number | null; + + @Column({ nullable: true, type: 'bigint' }) + private changeDate: number | null; + + @Column({ default: ContractStatus.NEGOTIATIONS_IN_PROGRESS }) + private status: ContractStatus; + + @Column() + private contractNo: string; + + public getCreationDate() { + return this.creationDate; + } + + public setCreationDate(creationDate: number) { + this.creationDate = creationDate; + } + + public getAcceptedAt() { + return this.acceptedAt; + } + + public setAcceptedAt(acceptedAt: number) { + this.acceptedAt = acceptedAt; + } + + public getRejectedAt() { + return this.rejectedAt; + } + + public setRejectedAt(rejectedAt: number) { + this.rejectedAt = rejectedAt; + } + + public getChangeDate() { + return this.changeDate; + } + + public setChangeDate(changeDate: number) { + this.changeDate = changeDate; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: ContractStatus) { + this.status = status; + } + + public getContractNo() { + return this.contractNo; + } + + public setContractNo(contractNo: string) { + this.contractNo = contractNo; + } + + public getAttachments() { + return this.attachments || []; + } + + public setAttachments(attachments: ContractAttachment[]) { + this.attachments = attachments; + } + + public getPartnerName() { + return this.partnerName; + } + + public setPartnerName(partnerName: string) { + this.partnerName = partnerName; + } + + public getSubject() { + return this.subject; + } + + public setSubject(subject: string) { + this.subject = subject; + } +} diff --git a/src/entity/driver-attribute.entity.ts b/src/entity/driver-attribute.entity.ts new file mode 100644 index 0000000..15d70ab --- /dev/null +++ b/src/entity/driver-attribute.entity.ts @@ -0,0 +1,58 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { Driver } from './driver.entity'; + +export enum DriverAttributeName { + PENALTY_POINTS = 'penalty_points', + NATIONALITY = 'nationality', + YEARS_OF_EXPERIENCE = 'years_of_experience', + MEDICAL_EXAMINATION_EXPIRATION_DATE = 'medial_examination_expiration_date', + MEDICAL_EXAMINATION_REMARKS = 'medical_examination_remarks', + EMAIL = 'email', + BIRTHPLACE = 'birthplace', + COMPANY_NAME = 'companyName', +} + +@Entity() +export class DriverAttribute extends BaseEntity { + @Column() + private name: DriverAttributeName; + + @Column() + private value: string; + + @ManyToOne(() => Driver, (driver) => driver) + @JoinColumn({ name: 'DRIVER_ID' }) + public driver: Driver; + + constructor(driver: Driver, attr: DriverAttributeName, value: string) { + super(); + this.driver = driver; + this.name = attr; + this.value = value; + } + + public getName() { + return this.name; + } + + public setName(name: DriverAttributeName) { + this.name = name; + } + + public getValue() { + return this.value; + } + + public setValue(value: string) { + this.value = value; + } + + public getDriver() { + return this.driver; + } + + public setDriver(driver: Driver) { + this.driver = driver; + } +} diff --git a/src/entity/driver-fee.entity.ts b/src/entity/driver-fee.entity.ts new file mode 100644 index 0000000..aa1249d --- /dev/null +++ b/src/entity/driver-fee.entity.ts @@ -0,0 +1,63 @@ +import { BaseEntity } from '../common/base.entity'; +import { Driver } from './driver.entity'; +import { Column, Entity, OneToOne } from 'typeorm'; + +export enum FeeType { + FLAT = 'flat', + PERCENTAGE = 'percentage', +} + +@Entity() +export class DriverFee extends BaseEntity { + @Column() + private feeType: FeeType; + + @Column() + private amount: number; + + @Column({ default: 0 }) + private min: number; + + @OneToOne(() => Driver, (driver) => driver.fee) + public driver: Driver; + + constructor(feeType: FeeType, driver: Driver, amount: number, min: number) { + super(); + this.feeType = feeType; + this.driver = driver; + this.amount = amount; + this.min = min; + } + + public getFeeType() { + return this.feeType; + } + + public setFeeType(feeType: FeeType) { + this.feeType = feeType; + } + + public getDriver() { + return this.driver; + } + + public setDriver(driver: Driver) { + this.driver = driver; + } + + public getAmount() { + return this.amount; + } + + public setAmount(amount: number) { + this.amount = amount; + } + + public getMin() { + return this.min; + } + + public setMin(min: number) { + this.min = min; + } +} diff --git a/src/entity/driver-position.entity.ts b/src/entity/driver-position.entity.ts new file mode 100644 index 0000000..711689e --- /dev/null +++ b/src/entity/driver-position.entity.ts @@ -0,0 +1,50 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Driver } from './driver.entity'; + +@Entity() +export class DriverPosition extends BaseEntity { + @ManyToOne(() => Driver) + public driver: Driver; + + @Column({ type: 'float' }) + public latitude!: number; + + @Column({ type: 'float' }) + public longitude: number; + + @Column({ type: 'bigint' }) + public seenAt: number; + + public getDriver() { + return this.driver; + } + + public setDriver(driver: Driver) { + this.driver = driver; + } + + public getLatitude() { + return this.latitude; + } + + public setLatitude(latitude: number) { + this.latitude = latitude; + } + + public getLongitude() { + return this.longitude; + } + + public setLongitude(longitude: number) { + this.longitude = longitude; + } + + public getSeenAt() { + return this.seenAt; + } + + public setSeenAt(seenAt: number) { + this.seenAt = seenAt; + } +} diff --git a/src/entity/driver-session.entity.ts b/src/entity/driver-session.entity.ts new file mode 100644 index 0000000..04e4466 --- /dev/null +++ b/src/entity/driver-session.entity.ts @@ -0,0 +1,73 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Driver } from './driver.entity'; +import { CarClass } from './car-type.entity'; + +@Entity() +export class DriverSession extends BaseEntity { + @Column({ nullable: true, type: 'bigint' }) + public loggedAt: number; + + @Column({ nullable: true, type: 'bigint' }) + private loggedOutAt: number | null; + + @ManyToOne(() => Driver) + private driver: Driver; + + @Column() + private platesNumber: string; + + @Column() + private carClass: CarClass; + + @Column() + private carBrand: string; + + public getLoggedAt() { + return this.loggedAt; + } + + public getCarBrand() { + return this.carBrand; + } + + public setCarBrand(carBrand: string) { + this.carBrand = carBrand; + } + + public setLoggedAt(loggedAt: number) { + this.loggedAt = loggedAt; + } + + public getLoggedOutAt() { + return this.loggedOutAt; + } + + public setLoggedOutAt(loggedOutAt: number) { + this.loggedOutAt = loggedOutAt; + } + + public getDriver() { + return this.driver; + } + + public setDriver(driver: Driver) { + this.driver = driver; + } + + public getPlatesNumber() { + return this.platesNumber; + } + + public setPlatesNumber(platesNumber: string) { + this.platesNumber = platesNumber; + } + + public getCarClass() { + return this.carClass; + } + + public setCarClass(carClass: CarClass) { + this.carClass = carClass; + } +} diff --git a/src/entity/driver.entity.ts b/src/entity/driver.entity.ts new file mode 100644 index 0000000..a4ca146 --- /dev/null +++ b/src/entity/driver.entity.ts @@ -0,0 +1,131 @@ +import { Entity, Column, OneToMany, OneToOne, JoinColumn } from 'typeorm'; +import { BaseEntity } from '../common/base.entity'; +import { Transit } from './transit.entity'; +import { DriverAttribute } from './driver-attribute.entity'; +import { DriverFee } from './driver-fee.entity'; + +export enum DriverStatus { + INACTIVE = 'inactive', + ACTIVE = 'active', +} + +export enum DriverType { + CANDIDATE = 'candidate', + REGULAR = 'regular', +} + +@Entity() +export class Driver extends BaseEntity { + @Column() + private status: DriverStatus; + + @Column() + private firstName: string; + + @Column() + private lastName: string; + + @Column() + private driverLicense: string; + + @Column({ nullable: true, type: 'varchar' }) + private photo: string | null; + + @Column() + private type: DriverType; + + @Column({ default: false }) + private isOccupied: boolean; + + @OneToOne(() => DriverFee, (fee) => fee.driver) + @JoinColumn() + public fee: DriverFee; + + public getAttributes() { + return this.attributes || []; + } + + public setAttributes(attributes: DriverAttribute[]) { + this.attributes = attributes; + } + + @OneToMany(() => DriverAttribute, (driverAttribute) => driverAttribute.driver) + public attributes: DriverAttribute[]; + + @OneToMany(() => Transit, (transit) => transit.driver) + public transits: Transit[]; + + public calculateEarningsForTransit(transit: Transit) { + console.log(transit); + return null; + // zdublować kod wyliczenia kosztu przejazdu + } + + public setLastName(lastName: string) { + this.lastName = lastName; + } + + public setFirstName(firstName: string) { + this.firstName = firstName; + } + + public setDriverLicense(license: string) { + this.driverLicense = license; + } + + public setStatus(status: DriverStatus) { + this.status = status; + } + + public setType(type: DriverType) { + this.type = type; + } + + public setPhoto(photo: string) { + this.photo = photo; + } + + public getLastName() { + return this.lastName; + } + + public getFirstName() { + return this.firstName; + } + + public getDriverLicense() { + return this.driverLicense; + } + + public getStatus() { + return this.status; + } + + public getType() { + return this.type; + } + + public getPhoto() { + return this.photo; + } + + public getFee() { + return this.fee; + } + + public setFee(fee: DriverFee) { + this.fee = fee; + } + + public getOccupied() { + return this.isOccupied; + } + + public setOccupied(isOccupied: boolean) { + this.isOccupied = isOccupied; + } + + public getTransits() { + return this.transits || []; + } +} diff --git a/src/entity/invoice.entity.ts b/src/entity/invoice.entity.ts new file mode 100644 index 0000000..bb4d179 --- /dev/null +++ b/src/entity/invoice.entity.ts @@ -0,0 +1,17 @@ +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity } from 'typeorm'; + +@Entity() +export class Invoice extends BaseEntity { + @Column() + private amount: number; + + @Column() + private subjectName: string; + + constructor(amount: number, subjectName: string) { + super(); + this.amount = amount; + this.subjectName = subjectName; + } +} diff --git a/src/entity/transit.entity.ts b/src/entity/transit.entity.ts new file mode 100644 index 0000000..05565f8 --- /dev/null +++ b/src/entity/transit.entity.ts @@ -0,0 +1,370 @@ +import { ForbiddenException } from '@nestjs/common'; +import { BaseEntity } from '../common/base.entity'; +import { Column, Entity, JoinColumn, ManyToMany, ManyToOne } from 'typeorm'; +import { Driver } from './driver.entity'; +import { Client, PaymentType } from './client.entity'; +import { Address } from './address.entity'; +import { CarClass } from './car-type.entity'; + +export enum Status { + DRAFT = 'draft', + CANCELLED = 'cancelled', + WAITING_FOR_DRIVER_ASSIGNMENT = 'waiting_for_driver_assignment', + DRIVER_ASSIGNMENT_FAILED = 'driver_assignment_failed', + TRANSIT_TO_PASSENGER = 'transit_to_passenger', + IN_TRANSIT = 'in_transit', + COMPLETED = 'completed', +} + +export enum DriverPaymentStatus { + NOT_PAID = 'not_paid', + PAID = 'paid', + CLAIMED = 'claimed', + RETURNED = 'returned', +} + +export enum ClientPaymentStatus { + NOT_PAID = 'not_paid', + PAID = 'paid', + RETURNED = 'returned', +} + +export enum Month { + JANUARY, + FEBRUARY, + MARCH, + APRIL, + MAY, + JUNE, + JULY, + AUGUST, + SEPTEMBER, + OCTOBER, + NOVEMBER, + DECEMBER, +} + +export enum DayOfWeek { + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, +} + +@Entity() +export class Transit extends BaseEntity { + public static readonly BASE_FEE = 8; + + @ManyToOne(() => Driver, (driver) => driver.transits, { eager: true }) + public driver: Driver | null; + + @Column({ nullable: true }) + private driverPaymentStatus: DriverPaymentStatus; + + @Column({ nullable: true }) + private clientPaymentStatus: ClientPaymentStatus; + + @Column({ nullable: true }) + private paymentType: PaymentType; + + @Column() + private status: Status; + + @Column({ type: 'bigint', nullable: true }) + private date: number; + + @ManyToOne(() => Address, { eager: true }) + @JoinColumn() + private from: Address; + + @ManyToOne(() => Address, { eager: true }) + @JoinColumn() + private to: Address; + + @Column({ nullable: true, type: 'bigint' }) + public acceptedAt: number | null; + + @Column({ nullable: true, type: 'bigint' }) + public started: number | null; + + @Column({ default: 0 }) + public pickupAddressChangeCounter: number; + + @ManyToMany(() => Driver) + public driversRejections: Driver[]; + + @ManyToMany(() => Driver) + public proposedDrivers: Driver[]; + + @Column({ default: 0, type: 'integer' }) + public awaitingDriversResponses: number; + + @Column({ nullable: true, type: 'varchar' }) + public factor: number | null; + + @Column({ nullable: false, default: 0 }) + private km: number; + + // https://stackoverflow.com/questions/37107123/sould-i-store-price-as-decimal-or-integer-in-mysql + @Column({ nullable: true, type: 'integer' }) + private price: number | null; + + @Column({ nullable: true, type: 'integer' }) + private estimatedPrice: number | null; + + @Column({ nullable: true }) + private driversFee: number; + + @Column({ type: 'bigint', nullable: true }) + public dateTime: number; + + @Column({ type: 'bigint', nullable: true }) + private published: number; + + @ManyToOne(() => Client, { eager: true }) + @JoinColumn() + private client: Client; + + @Column() + private carType: CarClass; + + @Column({ type: 'bigint', nullable: true }) + private completeAt: number; + + public getCarType() { + return this.carType as CarClass; + } + + public setCarType(carType: CarClass) { + this.carType = carType; + } + + public getDriver() { + return this.driver; + } + + public getPrice() { + return this.price; + } + + //just for testing + public setPrice(price: number) { + this.price = price; + } + + public getStatus() { + return this.status; + } + + public setStatus(status: Status) { + this.status = status; + } + + public getCompleteAt() { + return this.completeAt; + } + + public getClient() { + return this.client; + } + + public setClient(client: Client) { + this.client = client; + } + + public setDateTime(dateTime: number) { + this.dateTime = dateTime; + } + + public getDateTime() { + return this.dateTime; + } + + public getPublished() { + return this.published; + } + + public setPublished(published: number) { + this.published = published; + } + + public setDriver(driver: Driver | null) { + this.driver = driver; + } + + public getKm() { + return this.km; + } + + public setKm(km: number) { + this.km = km; + this.estimateCost(); + } + + public getAwaitingDriversResponses() { + return this.awaitingDriversResponses; + } + + public setAwaitingDriversResponses(proposedDriversCounter: number) { + this.awaitingDriversResponses = proposedDriversCounter; + } + + public getDriversRejections() { + return this.driversRejections || []; + } + + public setDriversRejections(driversRejections: Driver[]) { + this.driversRejections = driversRejections; + } + + public getProposedDrivers() { + return this.proposedDrivers || []; + } + + public setProposedDrivers(proposedDrivers: Driver[]) { + this.proposedDrivers = proposedDrivers; + } + + public getAcceptedAt() { + return this.acceptedAt; + } + + public setAcceptedAt(acceptedAt: number) { + this.acceptedAt = acceptedAt; + } + + public getStarted() { + return this.started; + } + + public setStarted(started: number) { + this.started = started; + } + + public getFrom() { + return this.from; + } + + public setFrom(from: Address) { + this.from = from; + } + + public getTo() { + return this.to; + } + + public setTo(to: Address) { + this.to = to; + } + + public getPickupAddressChangeCounter() { + return this.pickupAddressChangeCounter; + } + + public setPickupAddressChangeCounter(pickupChanges: number) { + this.pickupAddressChangeCounter = pickupChanges; + } + + public setCompleteAt(when: number) { + this.completeAt = when; + } + + public getDriversFee() { + return this.driversFee; + } + + public setDriversFee(driversFee: number) { + this.driversFee = driversFee; + } + + public getEstimatedPrice() { + return this.estimatedPrice; + } + + public setEstimatedPrice(estimatedPrice: number) { + this.estimatedPrice = estimatedPrice; + } + + public estimateCost() { + if (this.status === Status.COMPLETED) { + throw new ForbiddenException( + 'Estimating cost for completed transit is forbidden, id = ' + + this.getId(), + ); + } + + this.estimatedPrice = this.calculateCost(); + + this.price = null; + + return this.estimatedPrice; + } + + public calculateFinalCosts(): number { + if (this.status === Status.COMPLETED) { + return this.calculateCost(); + } else { + throw new ForbiddenException( + 'Cannot calculate final cost if the transit is not completed', + ); + } + } + + private calculateCost(): number { + let baseFee = Transit.BASE_FEE; + let factorToCalculate = this.factor; + if (factorToCalculate == null) { + factorToCalculate = 1; + } + let kmRate: number; + const day = new Date(); + // wprowadzenie nowych cennikow od 1.01.2019 + if (day.getFullYear() <= 2018) { + kmRate = 1.0; + baseFee++; + } else { + if ( + (day.getMonth() == Month.DECEMBER && day.getDate() == 31) || + (day.getMonth() == Month.JANUARY && + day.getDate() == 1 && + day.getHours() <= 6) + ) { + kmRate = 3.5; + baseFee += 3; + } else { + // piątek i sobota po 17 do 6 następnego dnia + if ( + (day.getDay() == DayOfWeek.FRIDAY && day.getHours() >= 17) || + (day.getDay() == DayOfWeek.SATURDAY && day.getHours() <= 6) || + (day.getDay() == DayOfWeek.SATURDAY && day.getHours() >= 17) || + (day.getDay() == DayOfWeek.SUNDAY && day.getHours() <= 6) + ) { + kmRate = 2.5; + baseFee += 2; + } else { + // pozostałe godziny weekendu + if ( + (day.getDay() == DayOfWeek.SATURDAY && + day.getHours() > 6 && + day.getHours() < 17) || + (day.getDay() == DayOfWeek.SUNDAY && day.getHours() > 6) + ) { + kmRate = 1.5; + } else { + // tydzień roboczy + kmRate = 1.0; + baseFee++; + } + } + } + } + const priceBigDecimal = Number( + (this.km * kmRate * factorToCalculate + baseFee).toFixed(2), + ); + this.price = priceBigDecimal; + return this.price; + } +} diff --git a/src/main.ts b/src/main.ts index 13cad38..58fd2f4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,6 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(3000); + await app.listen(process.env.PORT || 3000); } bootstrap(); diff --git a/src/repository/address.repository.ts b/src/repository/address.repository.ts new file mode 100644 index 0000000..5c031a6 --- /dev/null +++ b/src/repository/address.repository.ts @@ -0,0 +1,27 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Address } from '../entity/address.entity'; + +@EntityRepository(Address) +export class AddressRepository extends Repository
{ + // FIX ME: To replace with getOrCreate method instead of that? + // Actual workaround for address uniqueness problem: assign result from repo.save to variable for later usage + //@ts-expect-error to avoid params error + public async save(address: Address) { + if (!address.getId()) { + const existingAddress = await this.findOne({ + where: { hash: address.getHash() }, + }); + if (existingAddress) { + return existingAddress; + } + } + + return super.save(address); + } + + public async findByHash(hash: string) { + return this.findOne({ + where: { hash }, + }); + } +} diff --git a/src/repository/awarded-miles.repository.ts b/src/repository/awarded-miles.repository.ts new file mode 100644 index 0000000..6d4d29d --- /dev/null +++ b/src/repository/awarded-miles.repository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { AwardedMiles } from '../entity/awarded-miles.entity'; +import { Client } from '../entity/client.entity'; + +@EntityRepository(AwardedMiles) +export class AwardedMilesRepository extends Repository { + public async findAllByClient(client: Client) { + return this.find({ where: { client } }); + } +} diff --git a/src/repository/awards-account.repository.ts b/src/repository/awards-account.repository.ts new file mode 100644 index 0000000..e9231e8 --- /dev/null +++ b/src/repository/awards-account.repository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { AwardsAccount } from '../entity/awards-account.entity'; +import { Client } from '../entity/client.entity'; + +@EntityRepository(AwardsAccount) +export class AwardsAccountRepository extends Repository { + public async findByClient(client: Client) { + return this.findOne({ where: { client } }); + } +} diff --git a/src/repository/car-type.repository.ts b/src/repository/car-type.repository.ts new file mode 100644 index 0000000..aa9ed62 --- /dev/null +++ b/src/repository/car-type.repository.ts @@ -0,0 +1,19 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; +import { NotFoundException } from '@nestjs/common'; + +@EntityRepository(CarType) +export class CarTypeRepository extends Repository { + public async findByCarClass(carClass: CarClass): Promise { + const carType = await this.findOne({ where: { carClass } }); + + if (!carType) { + throw new NotFoundException('Cannot find car type'); + } + return carType; + } + + public async findByStatus(status: CarStatus): Promise { + return this.find({ where: { status } }); + } +} diff --git a/src/repository/claim-attachment.repository.ts b/src/repository/claim-attachment.repository.ts new file mode 100644 index 0000000..b07be39 --- /dev/null +++ b/src/repository/claim-attachment.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { ClaimAttachment } from '../entity/claim-attachment.entity'; + +@EntityRepository(ClaimAttachment) +export class ClaimAttachmentRepository extends Repository {} diff --git a/src/repository/claim.repository.ts b/src/repository/claim.repository.ts new file mode 100644 index 0000000..752699a --- /dev/null +++ b/src/repository/claim.repository.ts @@ -0,0 +1,15 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Claim } from '../entity/claim.entity'; +import { Client } from '../entity/client.entity'; +import { Transit } from '../entity/transit.entity'; + +@EntityRepository(Claim) +export class ClaimRepository extends Repository { + public async findByOwner(owner: Client) { + return this.find({ where: { owner } }); + } + + public async findByOwnerAndTransit(owner: Client, transit: Transit) { + return this.find({ where: { owner, transit } }); + } +} diff --git a/src/repository/client.repository.ts b/src/repository/client.repository.ts new file mode 100644 index 0000000..f921c7f --- /dev/null +++ b/src/repository/client.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Client } from '../entity/client.entity'; + +@EntityRepository(Client) +export class ClientRepository extends Repository {} diff --git a/src/repository/contract-attachment.repository.ts b/src/repository/contract-attachment.repository.ts new file mode 100644 index 0000000..ebefd27 --- /dev/null +++ b/src/repository/contract-attachment.repository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { ContractAttachment } from '../entity/contract-attachment.entity'; +import { Contract } from '../entity/contract.entity'; + +@EntityRepository(ContractAttachment) +export class ContractAttachmentRepository extends Repository { + public async findByContract(contract: Contract) { + return this.find({ where: { contract } }); + } +} diff --git a/src/repository/contract.repository.ts b/src/repository/contract.repository.ts new file mode 100644 index 0000000..91f148a --- /dev/null +++ b/src/repository/contract.repository.ts @@ -0,0 +1,9 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Contract } from '../entity/contract.entity'; + +@EntityRepository(Contract) +export class ContractRepository extends Repository { + public async findByPartnerName(partnerName: string) { + return this.find({ where: { partnerName } }); + } +} diff --git a/src/repository/driver-attribute.repository.ts b/src/repository/driver-attribute.repository.ts new file mode 100644 index 0000000..8688048 --- /dev/null +++ b/src/repository/driver-attribute.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { DriverAttribute } from '../entity/driver-attribute.entity'; + +@EntityRepository(DriverAttribute) +export class DriverAttributeRepository extends Repository {} diff --git a/src/repository/driver-fee.repository.ts b/src/repository/driver-fee.repository.ts new file mode 100644 index 0000000..2d04c1d --- /dev/null +++ b/src/repository/driver-fee.repository.ts @@ -0,0 +1,10 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { DriverFee } from '../entity/driver-fee.entity'; +import { Driver } from '../entity/driver.entity'; + +@EntityRepository(DriverFee) +export class DriverFeeRepository extends Repository { + public async findByDriver(driver: Driver) { + return this.findOne({ driver }); + } +} diff --git a/src/repository/driver-position.repository.ts b/src/repository/driver-position.repository.ts new file mode 100644 index 0000000..1cc6f2a --- /dev/null +++ b/src/repository/driver-position.repository.ts @@ -0,0 +1,55 @@ +import { Between, EntityRepository, Repository } from 'typeorm'; +import { DriverPosition } from '../entity/driver-position.entity'; +import { Driver } from '../entity/driver.entity'; +import { DriverPositionV2Dto } from '../dto/driver-position-v2.dto'; + +@EntityRepository(DriverPosition) +export class DriverPositionRepository extends Repository { + public async findAverageDriverPositionSince( + latitudeMin: number, + latitudeMax: number, + longitudeMin: number, + longitudeMax: number, + date: number, + ): Promise { + const driverPosition = await this.createQueryBuilder('driverPosition') + .leftJoinAndSelect('driverPosition.driver', 'p') + .select(`AVG(p.latitude), AVG(p.longitude), MAX(p.seenAt)`) + .where('p.longitude between :longitudeMin and :longitudeMax') + .andWhere('p.longitude between :longitudeMin and :longitudeMax') + .andWhere('p.seenAt >= :seenAt') + .groupBy('p.driver.id') + .setParameters({ + longitudeMin, + longitudeMax, + seenAt: date, + }) + .getMany(); + + return driverPosition.map( + (dp) => + new DriverPositionV2Dto( + dp.driver, + dp.latitude, + dp.longitude, + dp.seenAt, + ), + ); + } + + public async findByDriverAndSeenAtBetweenOrderBySeenAtAsc( + driver: Driver, + from: number, + to: number, + ): Promise { + return this.find({ + where: { + driver, + seenAt: Between(from, to), + }, + order: { + seenAt: 'ASC', + }, + }); + } +} diff --git a/src/repository/driver-session.repository.ts b/src/repository/driver-session.repository.ts new file mode 100644 index 0000000..10d583a --- /dev/null +++ b/src/repository/driver-session.repository.ts @@ -0,0 +1,48 @@ +import { EntityRepository, MoreThan, Repository, IsNull } from 'typeorm'; +import { DriverSession } from '../entity/driver-session.entity'; +import { CarClass } from '../entity/car-type.entity'; +import { Driver } from '../entity/driver.entity'; +import { NotFoundException } from '@nestjs/common'; + +@EntityRepository(DriverSession) +export class DriverSessionRepository extends Repository { + public async findAllByLoggedOutAtNullAndDriverInAndCarClassIn( + drivers: Driver[], + carClasses: CarClass[], + ): Promise { + console.log('To implement...', drivers, carClasses); + return []; + } + + public async findTopByDriverAndLoggedOutAtIsNullOrderByLoggedAtDesc( + driver: Driver, + ): Promise { + const session = await this.findOne({ + where: { driver, loggedOutAt: IsNull() }, + order: { + loggedAt: 'DESC', + }, + }); + + if (!session) { + throw new NotFoundException(`Session for ${driver.getId()} not exists`); + } + return session; + } + + public async findAllByDriverAndLoggedAtAfter( + driver: Driver, + since: number, + ): Promise { + return this.find({ + where: { + driver, + loggedAt: MoreThan(since), + }, + }); + } + + public async findByDriver(driver: Driver): Promise { + return this.find({ where: { driver } }); + } +} diff --git a/src/repository/driver.repository.ts b/src/repository/driver.repository.ts new file mode 100644 index 0000000..152a7d7 --- /dev/null +++ b/src/repository/driver.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Driver } from '../entity/driver.entity'; + +@EntityRepository(Driver) +export class DriverRepository extends Repository {} diff --git a/src/repository/invoice.repository.ts b/src/repository/invoice.repository.ts new file mode 100644 index 0000000..9a8509e --- /dev/null +++ b/src/repository/invoice.repository.ts @@ -0,0 +1,5 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Invoice } from '../entity/invoice.entity'; + +@EntityRepository(Invoice) +export class InvoiceRepository extends Repository {} diff --git a/src/repository/transit.repository.ts b/src/repository/transit.repository.ts new file mode 100644 index 0000000..f588573 --- /dev/null +++ b/src/repository/transit.repository.ts @@ -0,0 +1,65 @@ +import { Between, EntityRepository, Repository } from 'typeorm'; +import { Status, Transit } from '../entity/transit.entity'; +import { Driver } from '../entity/driver.entity'; +import { Client } from '../entity/client.entity'; +import { Address } from '../entity/address.entity'; + +@EntityRepository(Transit) +export class TransitRepository extends Repository { + public async findAllByDriverAndDateTimeBetween( + driver: Driver, + from: number, + to: number, + ): Promise { + return await this.find({ + where: { + driver, + dateTime: Between(from, to), + }, + }); + } + + public async findAllByClientAndFromAndStatusOrderByDateTimeDesc( + client: Client, + from: Address, + status: Status, + ): Promise { + return await this.find({ + where: { + client, + from, + status, + }, + order: { + dateTime: 'DESC', + }, + }); + } + + public async findAllByClientAndFromAndPublishedAfterAndStatusOrderByDateTimeDesc( + client: Client, + from: Address, + when: number, + status: Status, + ): Promise { + return this.find({ + where: { + client, + from, + status, + published: when, + }, + order: { + dateTime: 'DESC', + }, + }); + } + + public async findByClient(client: Client): Promise { + return this.find({ + where: { + client, + }, + }); + } +} diff --git a/src/service/awards.service.ts b/src/service/awards.service.ts new file mode 100644 index 0000000..4fa59d0 --- /dev/null +++ b/src/service/awards.service.ts @@ -0,0 +1,324 @@ +import { AwardsAccountDto } from '../dto/awards-account.dto'; +import { AwardedMiles } from '../entity/awarded-miles.entity'; +import { + Injectable, + NotFoundException, + NotAcceptableException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ClientRepository } from '../repository/client.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { AppProperties } from '../config/app-properties.config'; +import { AwardsAccountRepository } from '../repository/awards-account.repository'; +import { AwardedMilesRepository } from '../repository/awarded-miles.repository'; +import { AwardsAccount } from '../entity/awards-account.entity'; +import dayjs from 'dayjs'; +import { Client, Type } from '../entity/client.entity'; +import orderBy from 'lodash.orderby'; + +export interface IAwardsService { + findBy: (clientId: string) => Promise; + + registerToProgram: (clientId: string) => Promise; + + activateAccount: (clientId: string) => Promise; + + deactivateAccount: (clientId: string) => Promise; + + registerMiles: ( + clientId: string, + transitId: string, + ) => Promise; + + registerSpecialMiles: ( + clientId: string, + miles: number, + ) => Promise; + + removeMiles: (clientId: string, miles: number) => Promise; + + calculateBalance: (clientId: string) => Promise; + + transferMiles: ( + fromClientId: string, + toClientId: string, + miles: number, + ) => Promise; +} + +@Injectable() +export class AwardsService implements IAwardsService { + constructor( + @InjectRepository(ClientRepository) + private clientRepository: ClientRepository, + @InjectRepository(TransitRepository) + private transitRepository: TransitRepository, + @InjectRepository(AwardsAccountRepository) + private accountRepository: AwardsAccountRepository, + @InjectRepository(AwardedMilesRepository) + private milesRepository: AwardedMilesRepository, + private appProperties: AppProperties, + ) {} + + public async findBy(clientId: string): Promise { + const account = await this.getAccountForClient(clientId); + + return new AwardsAccountDto(account); + } + + public async registerToProgram(clientId: string) { + const client = await this.clientRepository.findOne(clientId); + + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + clientId); + } + + const account = new AwardsAccount(); + + account.setClient(client); + account.setActive(false); + account.setDate(Date.now()); + + await this.accountRepository.save(account); + } + + public async activateAccount(clientId: string) { + const account = await this.getAccountForClient(clientId); + + account.setActive(true); + + await this.accountRepository.save(account); + } + + public async deactivateAccount(clientId: string) { + const account = await this.getAccountForClient(clientId); + + account.setActive(false); + + await this.accountRepository.save(account); + } + + public async registerMiles(clientId: string, transitId: string) { + const account = await this.getAccountForClient(clientId); + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('transit does not exists, id = ' + transitId); + } + + const now = Date.now(); + if (!account.isAwardActive()) { + return null; + } else { + const miles = new AwardedMiles(); + miles.setTransit(transit); + miles.setDate(Date.now()); + miles.setClient(account.getClient()); + miles.setMiles(this.appProperties.getDefaultMilesBonus()); + miles.setExpirationDate( + dayjs(now) + .add(this.appProperties.getMilesExpirationInDays(), 'days') + .valueOf(), + ); + miles.setSpecial(false); + account.increaseTransactions(); + + await this.milesRepository.save(miles); + await this.accountRepository.save(account); + return miles; + } + } + + public async registerSpecialMiles(clientId: string, miles: number) { + const account = await this.getAccountForClient(clientId); + + const _miles = new AwardedMiles(); + _miles.setTransit(null); + _miles.setClient(account.getClient()); + _miles.setMiles(miles); + _miles.setDate(Date.now()); + _miles.setSpecial(true); + account.increaseTransactions(); + await this.milesRepository.save(_miles); + await this.accountRepository.save(account); + return _miles; + } + + public async removeMiles(clientId: string, miles: number) { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException( + `Client with id ${clientId} doest not exists`, + ); + } + + const account = await this.accountRepository.findByClient(client); + + if (!account) { + throw new NotFoundException( + `Awards account for client id ${clientId} doest not exists`, + ); + } else { + const balance = await this.calculateBalance(clientId); + if (balance >= miles && account.isAwardActive()) { + let milesList = await this.milesRepository.findAllByClient(client); + const transitsCounter = ( + await this.transitRepository.findByClient(client) + ).length; + + // TODO: verify below sorter + if (client.getClaims().length >= 3) { + // milesList.sort(Comparator.comparing(AwardedMiles::getExpirationDate, Comparators.nullsHigh()).reversed().thenComparing(Comparators.nullsHigh())); + milesList = orderBy(milesList, [(item) => item.getExpirationDate()]); + } else if (client.getType() === Type.VIP) { + // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getExpirationDate, Comparators.nullsLow())); + milesList = orderBy(milesList, [ + (item) => item.getSpecial(), + (item) => item.getExpirationDate(), + ]); + } else if (transitsCounter >= 15 && this.isSunday()) { + // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getExpirationDate, Comparators.nullsLow())); + milesList = orderBy(milesList, [ + (item) => item.getSpecial(), + (item) => item.getExpirationDate(), + ]); + } else if (transitsCounter >= 15) { + // milesList.sort(Comparator.comparing(AwardedMiles::isSpecial).thenComparing(AwardedMiles::getDate)); + milesList = orderBy(milesList, [ + (item) => item.getSpecial(), + (item) => item.getDate(), + ]); + } else { + // milesList.sort(Comparator.comparing(AwardedMiles::getDate)); + milesList = orderBy(milesList, (item) => item.getDate()); + } + for (const iter of milesList) { + if (miles <= 0) { + break; + } + if ( + iter.getSpecial() || + (iter.getExpirationDate() && + dayjs(iter.getExpirationDate()).isAfter(dayjs())) + ) { + if (iter.getMiles() <= miles) { + miles -= iter.getMiles(); + iter.setMiles(0); + } else { + iter.setMiles(iter.getMiles() - miles); + miles = 0; + } + await this.milesRepository.save(iter); + } + } + } else { + throw new NotAcceptableException( + 'Insufficient miles, id = ' + + clientId + + ', miles requested = ' + + miles, + ); + } + } + } + + public async calculateBalance(clientId: string) { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException( + `Client with id ${clientId} doest not exists`, + ); + } + const milesList = await this.milesRepository.findAllByClient(client); + + const sum = milesList + .filter( + (t) => + (t.getExpirationDate() != null && + t.getExpirationDate() && + dayjs(t.getExpirationDate()).isAfter(dayjs())) || + t.getSpecial(), + ) + .map((t) => t.getMiles()) + .reduce((prev, curr) => prev + curr, 0); + + return sum; + } + + public async transferMiles( + fromClientId: string, + toClientId: string, + miles: number, + ) { + const fromClient = await this.clientRepository.findOne(fromClientId); + if (!fromClient) { + throw new NotFoundException( + `Client with id ${fromClientId} doest not exists`, + ); + } + const accountFrom = await this.getAccountForClient(fromClient); + const accountTo = await this.getAccountForClient(toClientId); + + const balanceFromClient = await this.calculateBalance(fromClientId); + if (balanceFromClient >= miles && accountFrom.isAwardActive()) { + const milesList = await this.milesRepository.findAllByClient(fromClient); + + for (const iter of milesList) { + if ( + iter.getSpecial() || + dayjs(iter.getExpirationDate()).isAfter(dayjs()) + ) { + if (iter.getMiles() <= miles) { + iter.setClient(accountTo.getClient()); + miles -= iter.getMiles(); + } else { + iter.setMiles(iter.getMiles() - miles); + const _miles = new AwardedMiles(); + + _miles.setClient(accountTo.getClient()); + _miles.setSpecial(iter.getSpecial() ?? false); + _miles.setExpirationDate(iter.getExpirationDate() || Date.now()); + _miles.setMiles(miles); + + miles -= iter.getMiles(); + + await this.milesRepository.save(_miles); + } + await this.milesRepository.save(iter); + } + } + + accountFrom.increaseTransactions(); + accountTo.increaseTransactions(); + + await this.accountRepository.save(accountFrom); + await this.accountRepository.save(accountTo); + } + } + + private isSunday() { + return dayjs().get('day') === 0; + } + + private async getAccountForClient(clientId: string | Client) { + const client = + typeof clientId === 'string' + ? await this.clientRepository.findOne(clientId) + : clientId; + if (!client) { + throw new NotFoundException( + `Client with id ${clientId} doest not exists`, + ); + } + + const account = await this.accountRepository.findByClient(client); + + if (!account) { + throw new NotFoundException( + `Awards account for client id ${clientId} doest not exists`, + ); + } + + return account; + } +} diff --git a/src/service/car-type.service.ts b/src/service/car-type.service.ts new file mode 100644 index 0000000..f8077fa --- /dev/null +++ b/src/service/car-type.service.ts @@ -0,0 +1,123 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CarTypeRepository } from '../repository/car-type.repository'; +import { AppProperties } from '../config/app-properties.config'; +import { CarClass, CarStatus, CarType } from '../entity/car-type.entity'; +import { CreateCarTypeDto } from '../dto/create-car-type.dto'; +import { CarTypeDto } from '../dto/car-type.dto'; + +@Injectable() +export class CarTypeService { + constructor( + @InjectRepository(CarTypeRepository) + private carTypeRepository: CarTypeRepository, + private readonly appProperties: AppProperties, + ) {} + + public async load(id: string) { + const carType = await this.carTypeRepository.findOne(id); + if (!carType) { + throw new NotFoundException('Cannot find car type'); + } + return carType; + } + + public async loadDto(id: string): Promise { + const carType = await this.load(id); + return new CarTypeDto(carType); + } + + public async create(carTypeDTO: CreateCarTypeDto): Promise { + try { + const byCarClass = await this.carTypeRepository.findByCarClass( + carTypeDTO.carClass, + ); + byCarClass.setDescription(carTypeDTO.description); + return this.carTypeRepository.save(byCarClass); + } catch { + const carType = new CarType( + carTypeDTO.carClass, + carTypeDTO.description, + this.getMinNumberOfCars(carTypeDTO.carClass), + ); + + return this.carTypeRepository.save(carType); + } + } + + public async activate(id: string) { + const carType = await this.load(id); + + carType.activate(); + + await this.carTypeRepository.save(carType); + } + + public async deactivate(id: string) { + const carType = await this.load(id); + + carType.deactivate(); + + await this.carTypeRepository.save(carType); + } + + public async registerCar(carClass: CarClass) { + const carType = await this.carTypeRepository.findByCarClass(carClass); + + carType.registerCar(); + + await this.carTypeRepository.save(carType); + } + + public async unregisterCar(carClass: CarClass) { + const carType = await this.carTypeRepository.findByCarClass(carClass); + + carType.unregisterCar(); + + await this.carTypeRepository.save(carType); + } + + public async registerActiveCar(carClass: CarClass) { + const carType = await this.carTypeRepository.findByCarClass(carClass); + + carType.registerActiveCar(); + + await this.carTypeRepository.save(carType); + } + + public async unregisterActiveCar(carClass: CarClass) { + const carType = await this.carTypeRepository.findByCarClass(carClass); + + carType.unregisterActiveCar(); + + await this.carTypeRepository.save(carType); + } + + public async findActiveCarClasses() { + const cars = await this.carTypeRepository.findByStatus(CarStatus.ACTIVE); + return cars.map((car) => car.getCarClass()); + } + + public async removeCarType(carClass: CarClass) { + const carType = await this.carTypeRepository.findByCarClass(carClass); + if (carType) { + await this.carTypeRepository.delete(carType); + } + } + + private async findByCarClass(carClass: CarClass) { + const byCarClass = this.carTypeRepository.findByCarClass(carClass); + if (!byCarClass) { + throw new NotFoundException(`Car class does not exist: ${carClass}`); + } + return byCarClass; + } + + private getMinNumberOfCars(carClass: CarClass) { + if (carClass === CarClass.ECO) { + return this.appProperties.getMinNoOfCarsForEcoClass(); + } else { + return 10; + } + } +} diff --git a/src/service/claim-number-generator.service.ts b/src/service/claim-number-generator.service.ts new file mode 100644 index 0000000..3b1a2be --- /dev/null +++ b/src/service/claim-number-generator.service.ts @@ -0,0 +1,28 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; +import * as dayjs from 'dayjs'; +import { ClaimRepository } from '../repository/claim.repository'; +import { Claim } from '../entity/claim.entity'; + +@Injectable() +export class ClaimNumberGenerator { + constructor( + @InjectRepository(ClaimRepository) + private claimRepository: ClaimRepository, + ) {} + + public async generate(claim: Claim) { + const count = await this.claimRepository.count(); + let prefix = count; + if (count === 0) { + prefix = 1; + } + const DATE_TIME_FORMAT = 'dd/MM/yyyy'; + return ( + prefix + + count + + '---' + + dayjs(claim.getCreationDate()).format(DATE_TIME_FORMAT) + ); + } +} diff --git a/src/service/claim.service.ts b/src/service/claim.service.ts new file mode 100644 index 0000000..d9c3184 --- /dev/null +++ b/src/service/claim.service.ts @@ -0,0 +1,178 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ClientRepository } from '../repository/client.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { AppProperties } from '../config/app-properties.config'; +import { ClaimRepository } from '../repository/claim.repository'; +import { ClaimNumberGenerator } from './claim-number-generator.service'; +import { AwardsService } from './awards.service'; +import { ClientNotificationService } from './client-notification.service'; +import { DriverNotificationService } from './driver-notification.service'; +import { ClaimDto } from '../dto/claim.dto'; +import { Claim, ClaimStatus, CompletionMode } from '../entity/claim.entity'; +import { Type } from '../entity/client.entity'; + +@Injectable() +export class ClaimService { + constructor( + @InjectRepository(ClientRepository) + private clientRepository: ClientRepository, + @InjectRepository(TransitRepository) + private transitRepository: TransitRepository, + @InjectRepository(ClaimRepository) + private claimRepository: ClaimRepository, + private claimNumberGenerator: ClaimNumberGenerator, + private awardsService: AwardsService, + private clientNotificationService: ClientNotificationService, + private driverNotificationService: DriverNotificationService, + private appProperties: AppProperties, + ) {} + + public async create(claimDTO: ClaimDto): Promise { + let claim = new Claim(); + claim.setCreationDate(Date.now()); + claim.setClaimNo(await this.claimNumberGenerator.generate(claim)); + claim = await this.update(claimDTO, claim); + return claim; + } + + public async find(id: string): Promise { + const claim = await this.claimRepository.findOne(id); + if (!claim) { + throw new NotFoundException('Claim does not exists'); + } + return claim; + } + + public async update(claimDTO: ClaimDto, claim: Claim) { + const client = await this.clientRepository.findOne(claimDTO.getClientId()); + const transit = await this.transitRepository.findOne( + claimDTO.getTransitId(), + ); + if (client == null) { + throw new NotFoundException('Client does not exists'); + } + if (transit == null) { + throw new NotFoundException('Transit does not exists'); + } + if (claimDTO.isDraft()) { + claim.setStatus(ClaimStatus.DRAFT); + } else { + claim.setStatus(ClaimStatus.NEW); + } + claim.setOwner(client); + claim.setTransit(transit); + claim.setCreationDate(Date.now()); + claim.setReason(claimDTO.getReason()); + claim.setIncidentDescription(claimDTO.getIncidentDescription()); + return this.claimRepository.save(claim); + } + + public async setStatus(newStatus: ClaimStatus, id: string) { + const claim = await this.find(id); + claim.setStatus(newStatus); + await this.claimRepository.save(claim); + return claim; + } + + public async tryToResolveAutomatically(id: string): Promise { + const claim = await this.find(id); + if ( + ( + await this.claimRepository.findByOwnerAndTransit( + claim.getOwner(), + claim.getTransit(), + ) + ).length > 1 + ) { + claim.setStatus(ClaimStatus.ESCALATED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.MANUAL); + return claim; + } + if ( + (await this.claimRepository.findByOwner(claim.getOwner())).length <= 3 + ) { + claim.setStatus(ClaimStatus.REFUNDED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.AUTOMATIC); + await this.clientNotificationService.notifyClientAboutRefund( + claim.getClaimNo(), + claim.getOwner().getId(), + ); + return claim; + } + if (claim.getOwner().getType() === Type.VIP) { + if ( + (claim.getTransit().getPrice() ?? 0) < + this.appProperties.getAutomaticRefundForVipThreshold() + ) { + claim.setStatus(ClaimStatus.REFUNDED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.AUTOMATIC); + await this.clientNotificationService.notifyClientAboutRefund( + claim.getClaimNo(), + claim.getOwner().getId(), + ); + await this.awardsService.registerSpecialMiles( + claim.getOwner().getId(), + 10, + ); + } else { + claim.setStatus(ClaimStatus.ESCALATED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.MANUAL); + const driver = claim.getTransit().getDriver(); + if (driver) { + await this.driverNotificationService.askDriverForDetailsAboutClaim( + claim.getClaimNo(), + driver.getId(), + ); + } + } + } else { + if ( + (await this.transitRepository.findByClient(claim.getOwner())).length >= + this.appProperties.getNoOfTransitsForClaimAutomaticRefund() + ) { + if ( + (claim.getTransit().getPrice() ?? 0) < + this.appProperties.getAutomaticRefundForVipThreshold() + ) { + claim.setStatus(ClaimStatus.REFUNDED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.AUTOMATIC); + await this.clientNotificationService.notifyClientAboutRefund( + claim.getClaimNo(), + claim.getOwner().getId(), + ); + } else { + claim.setStatus(ClaimStatus.ESCALATED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.MANUAL); + await this.clientNotificationService.askForMoreInformation( + claim.getClaimNo(), + claim.getOwner().getId(), + ); + } + } else { + claim.setStatus(ClaimStatus.ESCALATED); + claim.setCompletionDate(Date.now()); + claim.setChangeDate(Date.now()); + claim.setCompletionMode(CompletionMode.MANUAL); + await this.driverNotificationService.askDriverForDetailsAboutClaim( + claim.getClaimNo(), + claim.getOwner().getId(), + ); + } + } + + return claim; + } +} diff --git a/src/service/client-notification.service.ts b/src/service/client-notification.service.ts new file mode 100644 index 0000000..ce80799 --- /dev/null +++ b/src/service/client-notification.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ClientNotificationService { + public notifyClientAboutRefund(claimNo: string, clientId: string) { + // ... + console.log(`To implement...`); + console.log({ claimNo, clientId }); + } + + public askForMoreInformation(claimNo: string, clientId: string) { + // ... + console.log(`To implement...`); + console.log({ claimNo, clientId }); + } +} diff --git a/src/service/client.service.ts b/src/service/client.service.ts new file mode 100644 index 0000000..e89e251 --- /dev/null +++ b/src/service/client.service.ts @@ -0,0 +1,65 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { ClientRepository } from '../repository/client.repository'; +import { Client, PaymentType, Type } from '../entity/client.entity'; +import { ClientDto } from '../dto/client.dto'; + +@Injectable() +export class ClientService { + constructor( + @InjectRepository(ClientRepository) + private clientRepository: ClientRepository, + ) {} + + public async registerClient( + name: string, + lastName: string, + type: Type, + paymentType: PaymentType, + ) { + const client = new Client(); + client.setName(name); + client.setLastName(lastName); + client.setType(type); + client.setDefaultPaymentType(paymentType); + return this.clientRepository.save(client); + } + + public async changeDefaultPaymentType( + clientId: string, + paymentType: PaymentType, + ) { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + clientId); + } + client.setDefaultPaymentType(paymentType); + await this.clientRepository.save(client); + } + + public async upgradeToVIP(clientId: string) { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + clientId); + } + client.setType(Type.VIP); + await this.clientRepository.save(client); + } + + public async downgradeToRegular(clientId: string) { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + clientId); + } + client.setType(Type.NORMAL); + await this.clientRepository.save(client); + } + + public async load(id: string) { + const client = await this.clientRepository.findOne(id); + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + id); + } + return new ClientDto(client); + } +} diff --git a/src/service/contract.service.ts b/src/service/contract.service.ts new file mode 100644 index 0000000..7680201 --- /dev/null +++ b/src/service/contract.service.ts @@ -0,0 +1,143 @@ +import { + Injectable, + NotFoundException, + NotAcceptableException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ContractRepository } from '../repository/contract.repository'; +import { ContractAttachmentRepository } from '../repository/contract-attachment.repository'; +import { ContractDto } from '../dto/contract.dto'; +import { Contract, ContractStatus } from '../entity/contract.entity'; +import { + ContractAttachment, + ContractAttachmentStatus, +} from '../entity/contract-attachment.entity'; +import { ContractAttachmentDto } from '../dto/contract-attachment.dto'; +import { CreateContractDto } from '../dto/create-contract.dto'; +import { CreateContractAttachmentDto } from '../dto/create-contract-attachment.dto'; + +@Injectable() +export class ContractService { + constructor( + @InjectRepository(ContractRepository) + private contractRepository: ContractRepository, + @InjectRepository(ContractAttachmentRepository) + private contractAttachmentRepository: ContractAttachmentRepository, + ) {} + + public async createContract(createContractDto: CreateContractDto) { + const contract = new Contract(); + contract.setPartnerName(createContractDto.partnerName); + contract.setCreationDate(Date.now()); + const partnerContractsCount = + ( + await this.contractRepository.findByPartnerName( + createContractDto.partnerName, + ) + ).length + 1; + contract.setSubject(createContractDto.subject); + contract.setContractNo( + 'C/' + partnerContractsCount + '/' + createContractDto.partnerName, + ); + return this.contractRepository.save(contract); + } + + public async acceptContract(id: string) { + const contract = await this.find(id); + const attachments = await this.contractAttachmentRepository.findByContract( + contract, + ); + if ( + attachments.every( + (a) => + a.getStatus() === ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES, + ) + ) { + contract.setStatus(ContractStatus.ACCEPTED); + contract.setAcceptedAt(Date.now()); + await this.contractAttachmentRepository.save(attachments); + await this.contractRepository.save(contract); + } else { + throw new NotAcceptableException( + 'Not all attachments accepted by both sides', + ); + } + } + + public async rejectContract(id: string) { + const contract = await this.find(id); + contract.setStatus(ContractStatus.REJECTED); + await this.contractRepository.save(contract); + } + + public async rejectAttachment(attachmentId: string) { + const contractAttachment = await this.contractAttachmentRepository.findOne( + attachmentId, + ); + if (!contractAttachment) { + throw new NotFoundException('Contract attachment does not exist'); + } + contractAttachment.setStatus(ContractAttachmentStatus.REJECTED); + contractAttachment.setRejectedAt(Date.now()); + await this.contractAttachmentRepository.save(contractAttachment); + } + + public async acceptAttachment(attachmentId: string) { + const contractAttachment = await this.contractAttachmentRepository.findOne( + attachmentId, + ); + if (!contractAttachment) { + throw new NotFoundException('Contract attachment does not exist'); + } + + if ( + contractAttachment.getStatus() === + ContractAttachmentStatus.ACCEPTED_BY_ONE_SIDE || + contractAttachment.getStatus() === + ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES + ) { + contractAttachment.setStatus( + ContractAttachmentStatus.ACCEPTED_BY_BOTH_SIDES, + ); + } else { + contractAttachment.setStatus( + ContractAttachmentStatus.ACCEPTED_BY_ONE_SIDE, + ); + } + + contractAttachment.setAcceptedAt(Date.now()); + + await this.contractAttachmentRepository.save(contractAttachment); + } + + public async find(id: string) { + const contract = await this.contractRepository.findOne(id); + if (!contract) { + throw new NotFoundException('Contract does not exist'); + } + return contract; + } + + public async findDto(id: string) { + return new ContractDto(await this.find(id)); + } + + public async proposeAttachment( + contractId: string, + contractAttachmentDto: CreateContractAttachmentDto, + ) { + const contract = await this.find(contractId); + const contractAttachment = new ContractAttachment(); + contractAttachment.setContract(contract); + contractAttachment.setData(contractAttachmentDto.data); + await this.contractAttachmentRepository.save(contractAttachment); + contract.getAttachments().push(contractAttachment); + await this.contractRepository.save(contract); + return new ContractAttachmentDto(contractAttachment); + } + + public async removeAttachment(contractId: string, attachmentId: string) { + //TODO sprawdzenie czy nalezy do kontraktu (JIRA: II-14455) + await this.contractAttachmentRepository.delete(attachmentId); + } +} diff --git a/src/service/distance-calculator.service.ts b/src/service/distance-calculator.service.ts new file mode 100644 index 0000000..34a038e --- /dev/null +++ b/src/service/distance-calculator.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class DistanceCalculator { + public static degreesToRadians(degrees: number) { + return degrees * (Math.PI / 180); + } + + public calculateByMap( + latitudeFrom: number, + longitudeFrom: number, + latitudeTo: number, + longitudeTo: number, + ) { + // ... + console.log({ latitudeFrom, longitudeFrom, latitudeTo, longitudeTo }); + return 42; + } + + public calculateByGeo( + latitudeFrom: number, + longitudeFrom: number, + latitudeTo: number, + longitudeTo: number, + ) { + // https://www.geeksforgeeks.org/program-distance-two-points-earth/ + // The math module contains a function + // named toRadians which converts from + // degrees to radians. + const lon1 = DistanceCalculator.degreesToRadians(longitudeFrom); + const lon2 = DistanceCalculator.degreesToRadians(longitudeTo); + const lat1 = DistanceCalculator.degreesToRadians(latitudeFrom); + const lat2 = DistanceCalculator.degreesToRadians(latitudeTo); + + // Haversine formula + const dlon = lon2 - lon1; + const dlat = lat2 - lat1; + const a = + Math.pow(Math.sin(dlat / 2), 2) + + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2); + + const c = 2 * Math.asin(Math.sqrt(a)); + + // Radius of earth in kilometers. Use 3956 for miles + const r = 6371; + + // calculate the result + const distanceInKMeters = c * r; + + return distanceInKMeters; + } +} diff --git a/src/service/driver-fee.service.ts b/src/service/driver-fee.service.ts new file mode 100644 index 0000000..b0e29a6 --- /dev/null +++ b/src/service/driver-fee.service.ts @@ -0,0 +1,51 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DriverFeeRepository } from '../repository/driver-fee.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { FeeType } from '../entity/driver-fee.entity'; + +@Injectable() +export class DriverFeeService { + constructor( + @InjectRepository(DriverFeeRepository) + private driverFeeRepository: DriverFeeRepository, + @InjectRepository(TransitRepository) + private transitRepository: TransitRepository, + ) {} + + public async calculateDriverFee(transitId: string) { + const transit = await this.transitRepository.findOne(transitId); + if (!transit) { + throw new NotFoundException('transit does not exist, id = ' + transitId); + } + if (transit.getDriversFee() != null) { + return transit.getDriversFee(); + } + const transitPrice = transit.getPrice() ?? 0; + + const driver = transit.getDriver(); + + if (!driver) { + throw new NotFoundException( + 'driver not exist for transit = ' + transitId, + ); + } + const driverFee = await this.driverFeeRepository.findByDriver(driver); + if (!driverFee) { + throw new NotFoundException( + 'driver Fees not defined for driver, driver id = ' + driver.getId(), + ); + } + let finalFee; + if (driverFee.getFeeType() === FeeType.FLAT) { + finalFee = transitPrice - driverFee.getAmount(); + } else { + finalFee = (transitPrice * driverFee.getAmount()) / 100; + } + + return Math.max( + finalFee, + driverFee.getMin() == null ? 0 : driverFee.getMin(), + ); + } +} diff --git a/src/service/driver-notification.service.ts b/src/service/driver-notification.service.ts new file mode 100644 index 0000000..e21427b --- /dev/null +++ b/src/service/driver-notification.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class DriverNotificationService { + public notifyAboutPossibleTransit(driverId: string, transitId: string) { + // ... + console.log(`To implement...`); + console.log({ driverId, transitId }); + } + + public notifyAboutChangedTransitAddress(driverId: string, transitId: string) { + // ... + console.log(`To implement...`); + console.log({ driverId, transitId }); + } + + public notifyAboutCancelledTransit(driverId: string, transitId: string) { + // ... + console.log(`To implement...`); + console.log({ driverId, transitId }); + } + + public askDriverForDetailsAboutClaim(claimNo: string, driverId: string) { + // ... + console.log(`To implement...`); + console.log({ driverId, claimNo }); + } +} diff --git a/src/service/driver-session.service.ts b/src/service/driver-session.service.ts new file mode 100644 index 0000000..01593f7 --- /dev/null +++ b/src/service/driver-session.service.ts @@ -0,0 +1,78 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DriverRepository } from '../repository/driver.repository'; +import { DriverSessionRepository } from '../repository/driver-session.repository'; +import { CarTypeService } from './car-type.service'; +import { CarClass } from '../entity/car-type.entity'; +import { DriverSession } from '../entity/driver-session.entity'; + +@Injectable() +export class DriverSessionService { + constructor( + @InjectRepository(DriverSessionRepository) + private driverSessionRepository: DriverSessionRepository, + @InjectRepository(DriverRepository) + private driverRepository: DriverRepository, + private carTypeService: CarTypeService, + ) {} + + public async logIn( + driverId: string, + plateNumber: string, + carClass: CarClass, + carBrand: string, + ) { + const session = new DriverSession(); + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException(`Driver with id ${driverId} not exists`); + } + session.setDriver(driver); + session.setLoggedAt(Date.now()); + session.setCarClass(carClass); + session.setPlatesNumber(plateNumber); + session.setCarBrand(carBrand); + await this.carTypeService.registerActiveCar(session.getCarClass()); + return this.driverSessionRepository.save(session); + } + + public async logOut(sessionId: string) { + const session = await this.driverSessionRepository.findOne(sessionId); + if (!session) { + throw new NotFoundException('Session does not exist'); + } + await this.carTypeService.unregisterCar(session.getCarClass()); + session.setLoggedOutAt(Date.now()); + + await this.driverSessionRepository.save(session); + } + + public async logOutCurrentSession(driverId: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException(`Driver with id ${driverId} not exists`); + } + + const session = + await this.driverSessionRepository.findTopByDriverAndLoggedOutAtIsNullOrderByLoggedAtDesc( + driver, + ); + if (session) { + session.setLoggedOutAt(Date.now()); + await this.carTypeService.unregisterCar(session.getCarClass()); + await this.driverSessionRepository.save(session); + } + } + + public async findByDriver(driverId: string): Promise { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException(`Driver with id ${driverId} not exists`); + } + + return this.driverSessionRepository.findByDriver(driver); + } +} diff --git a/src/service/driver-tracking.service.ts b/src/service/driver-tracking.service.ts new file mode 100644 index 0000000..f40cbae --- /dev/null +++ b/src/service/driver-tracking.service.ts @@ -0,0 +1,79 @@ +import { + Injectable, + NotFoundException, + NotAcceptableException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DriverRepository } from '../repository/driver.repository'; +import { DriverPositionRepository } from '../repository/driver-position.repository'; +import { DistanceCalculator } from './distance-calculator.service'; +import { DriverPosition } from '../entity/driver-position.entity'; +import { DriverStatus } from '../entity/driver.entity'; + +@Injectable() +export class DriverTrackingService { + constructor( + @InjectRepository(DriverRepository) + private driverRepository: DriverRepository, + @InjectRepository(DriverPositionRepository) + private positionRepository: DriverPositionRepository, + private distanceCalculator: DistanceCalculator, + ) {} + + public async registerPosition( + driverId: string, + latitude: number, + longitude: number, + ): Promise { + const driver = await this.driverRepository.findOne(driverId); + if (!driver) { + throw new NotFoundException('Driver does not exists, id = ' + driverId); + } + if (driver.getStatus() !== DriverStatus.ACTIVE) { + throw new NotAcceptableException( + 'Driver is not active, cannot register position, id = ' + driverId, + ); + } + const position = new DriverPosition(); + position.setDriver(driver); + position.setSeenAt(Date.now()); + position.setLatitude(latitude); + position.setLongitude(longitude); + return await this.positionRepository.save(position); + } + + public async calculateTravelledDistance( + driverId: string, + from: number, + to: number, + ) { + const driver = await this.driverRepository.findOne(driverId); + if (!driver) { + throw new NotFoundException('Driver does not exists, id = ' + driverId); + } + const positions = + await this.positionRepository.findByDriverAndSeenAtBetweenOrderBySeenAtAsc( + driver, + from, + to, + ); + let distanceTravelled = 0; + + if (positions.length > 1) { + let previousPosition = positions[0]; + + for (const position of positions.slice(1)) { + distanceTravelled += this.distanceCalculator.calculateByGeo( + previousPosition.getLatitude(), + previousPosition.getLongitude(), + position.getLatitude(), + position.getLongitude(), + ); + + previousPosition = position; + } + } + + return distanceTravelled; + } +} diff --git a/src/service/driver.service.ts b/src/service/driver.service.ts new file mode 100644 index 0000000..38ccc42 --- /dev/null +++ b/src/service/driver.service.ts @@ -0,0 +1,184 @@ +import { + Injectable, + NotAcceptableException, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { Driver, DriverStatus, DriverType } from '../entity/driver.entity'; +import { DriverDto } from '../dto/driver.dto'; +import { CreateDriverDto } from '../dto/create-driver.dto'; +import { DriverRepository } from '../repository/driver.repository'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DriverAttributeRepository } from '../repository/driver-attribute.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { DriverFeeService } from './driver-fee.service'; +import dayjs from 'dayjs'; + +@Injectable() +export class DriverService { + public static DRIVER_LICENSE_REGEX = '^[A-Z9]{5}\\d{6}[A-Z9]{2}\\d[A-Z]{2}$'; + + constructor( + @InjectRepository(DriverRepository) + private driverRepository: DriverRepository, + @InjectRepository(DriverAttributeRepository) + private driverAttributeRepository: DriverAttributeRepository, + @InjectRepository(DriverRepository) + private transitRepository: TransitRepository, + private driverFeeService: DriverFeeService, + ) {} + + public async createDriver({ + photo, + driverLicense, + lastName, + firstName, + }: CreateDriverDto): Promise { + const driver = new Driver(); + if (driver.getStatus() === DriverStatus.ACTIVE) { + if ( + !driverLicense || + !driverLicense.match(DriverService.DRIVER_LICENSE_REGEX) + ) { + throw new NotAcceptableException( + 'Illegal license no = ' + driverLicense, + ); + } + } + driver.setDriverLicense(driverLicense); + driver.setLastName(lastName); + driver.setFirstName(firstName); + driver.setStatus(DriverStatus.INACTIVE); + driver.setType(DriverType.CANDIDATE); + if (photo !== null) { + if (Buffer.from(photo, 'base64').toString('base64') === photo) { + driver.setPhoto(photo); + } else { + throw new NotAcceptableException('Illegal photo in base64'); + } + } + + return this.driverRepository.save(driver); + } + + public async loadDriver(driverId: string): Promise { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException( + `Driver with id ${driverId} does not exists.`, + ); + } + + return new DriverDto(driver); + } + + public async changeDriverStatus(driverId: string, status: DriverStatus) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException( + `Driver with id ${driverId} does not exists.`, + ); + } + if (status === DriverStatus.ACTIVE) { + const license = driver.getDriverLicense(); + + if (!license) { + throw new ForbiddenException( + `Status cannot be ACTIVE. Illegal license no ${license}`, + ); + } + } + + driver.setStatus(status); + await this.driverRepository.update(driver.getId(), driver); + } + + public async changeLicenseNumber(newLicense: string, driverId: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException( + `Driver with id ${driverId} does not exists.`, + ); + } + if (!newLicense || !newLicense.match(DriverService.DRIVER_LICENSE_REGEX)) { + throw new NotAcceptableException( + 'Illegal new license no = ' + newLicense, + ); + } + + if (!(driver.getStatus() === DriverStatus.ACTIVE)) { + throw new NotAcceptableException( + 'Driver is not active, cannot change license', + ); + } + + driver.setDriverLicense(newLicense); + await this.driverRepository.save(driver); + } + + public async changePhoto(driverId: string, photo: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException( + `Driver with id ${driverId} does not exists.`, + ); + } + + if (!photo || Buffer.from(photo, 'base64').toString('base64') === photo) { + throw new NotAcceptableException('Illegal photo in base64'); + } + driver.setPhoto(photo); + await this.driverRepository.save(driver); + } + + public async calculateDriverMonthlyPayment( + driverId: string, + year: number, + month: number, + ) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException( + `Driver with id ${driverId} does not exists.`, + ); + } + + const yearMonth = dayjs(`${year}-${month}`, 'YYYY-M'); + const from = yearMonth.startOf('month'); + const to = yearMonth.endOf('month'); + + const transitsList = + await this.transitRepository.findAllByDriverAndDateTimeBetween( + driver, + from.valueOf(), + to.valueOf(), + ); + + const sum = ( + await Promise.all( + transitsList.map((t) => + this.driverFeeService.calculateDriverFee(t.getId()), + ), + ) + ).reduce((prev, curr) => prev + curr, 0); + + return sum; + } + + public async calculateDriverYearlyPayment( + driverId: string, + year: number, + ): Promise> { + const payments = new Map(); + const months = Array.from({ length: 5 }).map((_, i) => i); + for (const m of months) { + payments.set(m, this.calculateDriverMonthlyPayment(driverId, year, m)); + } + return payments; + } +} diff --git a/src/service/geocoding.service.ts b/src/service/geocoding.service.ts new file mode 100644 index 0000000..f1b8573 --- /dev/null +++ b/src/service/geocoding.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { Address } from '../entity/address.entity'; + +@Injectable() +export class GeocodingService { + public geocodeAddress(address: Address) { + //TODO ... call do zewnętrznego serwisu + console.log('To implement', address); + const geocoded = [0, 1]; + + geocoded[0] = 1; //latitude + geocoded[1] = 1; //longitude + + return geocoded; + } +} diff --git a/src/service/invoice-generator.service.ts b/src/service/invoice-generator.service.ts new file mode 100644 index 0000000..57d5216 --- /dev/null +++ b/src/service/invoice-generator.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { InvoiceRepository } from '../repository/invoice.repository'; +import { Invoice } from '../entity/invoice.entity'; + +@Injectable() +export class InvoiceGenerator { + constructor( + @InjectRepository(InvoiceRepository) + private invoiceRepository: InvoiceRepository, + ) {} + + public async generate(amount: number, subjectName: string) { + return this.invoiceRepository.save(new Invoice(amount, subjectName)); + } +} diff --git a/src/service/transit-analyzer.service.ts b/src/service/transit-analyzer.service.ts new file mode 100644 index 0000000..4ba8969 --- /dev/null +++ b/src/service/transit-analyzer.service.ts @@ -0,0 +1,104 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { ClientRepository } from '../repository/client.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { AddressRepository } from '../repository/address.repository'; +import { Address } from '../entity/address.entity'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Status, Transit } from '../entity/transit.entity'; +import { Client } from '../entity/client.entity'; +import dayjs from 'dayjs'; + +@Injectable() +export class TransitAnalyzerService { + constructor( + @InjectRepository(ClientRepository) + private clientRepository: ClientRepository, + @InjectRepository(TransitRepository) + private transitRepository: TransitRepository, + @InjectRepository(AddressRepository) + private addressRepository: AddressRepository, + ) {} + + public async analyze( + clientId: string, + addressId: string, + ): Promise { + const client = await this.clientRepository.findOne(clientId); + if (!client) { + throw new NotFoundException('Client does not exists, id = ' + clientId); + } + const address = await this.addressRepository.findOne(addressId); + if (!address) { + throw new NotFoundException('Address does not exists, id = ' + addressId); + } + return this._analyze(client, address, null); + } + + // Brace yourself, deadline is coming... They made me to do it this way. + // Tested! + private async _analyze( + client: Client, + from: Address, + t: Transit | null, + ): Promise { + let ts: Transit[] = []; + + if (!t) { + ts = + await this.transitRepository.findAllByClientAndFromAndStatusOrderByDateTimeDesc( + client, + from, + Status.COMPLETED, + ); + } else { + ts = + await this.transitRepository.findAllByClientAndFromAndPublishedAfterAndStatusOrderByDateTimeDesc( + client, + from, + t.getPublished(), + Status.COMPLETED, + ); + } + + // Workaround for performance reasons. + if (ts.length > 1000 && client.getId() == '666') { + // No one will see a difference for this customer ;) + ts = ts.slice(0, 1000); + } + + // if (ts.isEmpty()) { + // return List.of(t.getTo()); + // } + + if (t) { + ts = ts.filter((_t) => + dayjs(t.getCompleteAt()).add(15, 'minutes').isAfter(_t.getStarted()), + ); + // Before 2018-01-01: + //.filter(t -> t.getCompleteAt().plus(15, ChronoUnit.MINUTES).isAfter(t.getPublished())) + } + + if (!ts.length && t) { + return [t.getTo()]; + } + + const mappedTs: Address[][] = []; + + for (const _t of ts) { + const result = []; + result.push(_t.getFrom()); + result.push(...(await this._analyze(client, _t.getTo(), _t))); + mappedTs.push(result); + } + + function compare(a: Address[], b: Address[]) { + if (a.length > b.length) return -1; + if (a.length < b.length) return 1; + return 0; + } + + mappedTs.sort(compare); + + return mappedTs[0]?.length ? mappedTs[0] : []; + } +} diff --git a/src/service/transit.service.ts b/src/service/transit.service.ts new file mode 100644 index 0000000..85e992e --- /dev/null +++ b/src/service/transit.service.ts @@ -0,0 +1,621 @@ +import { + Injectable, + NotAcceptableException, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ClientRepository } from '../repository/client.repository'; +import { TransitRepository } from '../repository/transit.repository'; +import { DriverRepository } from '../repository/driver.repository'; +import { DriverPositionRepository } from '../repository/driver-position.repository'; +import { DriverSessionRepository } from '../repository/driver-session.repository'; +import { AddressRepository } from '../repository/address.repository'; +import { AwardsService } from './awards.service'; +import { DriverFeeService } from './driver-fee.service'; +import { CarTypeService } from './car-type.service'; +import { GeocodingService } from './geocoding.service'; +import { InvoiceGenerator } from './invoice-generator.service'; +import { DistanceCalculator } from './distance-calculator.service'; +import { TransitDto } from '../dto/transit.dto'; +import { AddressDto } from '../dto/address.dto'; +import { CarClass } from '../entity/car-type.entity'; +import { Address } from '../entity/address.entity'; +import { Status, Transit } from '../entity/transit.entity'; +import { DriverNotificationService } from './driver-notification.service'; +import * as dayjs from 'dayjs'; +import { DriverStatus } from '../entity/driver.entity'; +import { DriverPositionV2Dto } from '../dto/driver-position-v2.dto'; +import { CreateTransitDto } from '../dto/create-transit.dto'; + +@Injectable() +export class TransitService { + constructor( + @InjectRepository(ClientRepository) + private clientRepository: ClientRepository, + @InjectRepository(TransitRepository) + private transitRepository: TransitRepository, + @InjectRepository(DriverRepository) + private driverRepository: DriverRepository, + @InjectRepository(DriverPositionRepository) + private driverPositionRepository: DriverPositionRepository, + @InjectRepository(DriverSessionRepository) + private driverSessionRepository: DriverSessionRepository, + @InjectRepository(AddressRepository) + private addressRepository: AddressRepository, + private awardsService: AwardsService, + private driverFeeService: DriverFeeService, + private carTypeService: CarTypeService, + private geocodingService: GeocodingService, + private invoiceGenerator: InvoiceGenerator, + private distanceCalculator: DistanceCalculator, + private notificationService: DriverNotificationService, + ) {} + + public async createTransit(transitDto: CreateTransitDto) { + const from = await this.addressFromDto(new AddressDto(transitDto.from)); + const to = await this.addressFromDto(new AddressDto(transitDto.to)); + + if (!from || !to) { + throw new NotAcceptableException( + 'Cannot create transit for empty address', + ); + } + return this._createTransit( + transitDto.clientId, + from, + to, + transitDto.carClass, + ); + } + + public async _createTransit( + clientId: string, + from: Address, + to: Address, + carClass: CarClass, + ) { + const client = await this.clientRepository.findOne(clientId); + + if (!client) { + throw new NotFoundException('Client does not exist, id = ' + clientId); + } + + const transit = new Transit(); + + // FIXME later: add some exceptions handling + const geoFrom = this.geocodingService.geocodeAddress(from); + const geoTo = this.geocodingService.geocodeAddress(to); + + transit.setClient(client); + transit.setFrom(from); + transit.setTo(to); + transit.setCarType(carClass); + transit.setStatus(Status.DRAFT); + transit.setDateTime(Date.now()); + transit.setKm( + this.distanceCalculator.calculateByMap( + geoFrom[0], + geoFrom[1], + geoTo[0], + geoTo[1], + ), + ); + + return this.transitRepository.save(transit); + } + + public async _changeTransitAddressFrom(transitId: string, address: Address) { + const newAddress = await this.addressRepository.save(address); + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + if (!newAddress) { + throw new NotAcceptableException('Cannot process without address'); + } + + // FIXME later: add some exceptions handling + const geoFromNew = this.geocodingService.geocodeAddress(newAddress); + const geoFromOld = this.geocodingService.geocodeAddress(transit.getFrom()); + + // https://www.geeksforgeeks.org/program-distance-two-points-earth/ + // The math module contains a function + // named toRadians which converts from + // degrees to radians. + const lon1 = DistanceCalculator.degreesToRadians(geoFromNew[1]); + const lon2 = DistanceCalculator.degreesToRadians(geoFromOld[1]); + const lat1 = DistanceCalculator.degreesToRadians(geoFromNew[0]); + const lat2 = DistanceCalculator.degreesToRadians(geoFromOld[0]); + + // Haversine formula + const dlon = lon2 - lon1; + const dlat = lat2 - lat1; + const a = + Math.pow(Math.sin(dlat / 2), 2) + + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon / 2), 2); + + const c = 2 * Math.asin(Math.sqrt(a)); + + // Radius of earth in kilometers. Use 3956 for miles + const r = 6371; + + // calculate the result + const distanceInKMeters = c * r; + + if ( + transit.getStatus() !== Status.DRAFT || + transit.getStatus() === Status.WAITING_FOR_DRIVER_ASSIGNMENT || + transit.getPickupAddressChangeCounter() > 2 || + distanceInKMeters > 0.25 + ) { + throw new NotAcceptableException( + "Address 'from' cannot be changed, id = " + transitId, + ); + } + + transit.setFrom(newAddress); + transit.setKm( + this.distanceCalculator.calculateByMap( + geoFromNew[0], + geoFromNew[1], + geoFromOld[0], + geoFromOld[1], + ), + ); + transit.setPickupAddressChangeCounter( + transit.getPickupAddressChangeCounter() + 1, + ); + await this.transitRepository.save(transit); + + for (const driver of transit.getProposedDrivers()) { + await this.notificationService.notifyAboutChangedTransitAddress( + driver.getId(), + transitId, + ); + } + } + + public async changeTransitAddressTo( + transitId: string, + newAddress: AddressDto, + ) { + return this._changeTransitAddressTo( + transitId, + newAddress.toAddressEntity(), + ); + } + + private async _changeTransitAddressTo( + transitId: string, + newAddress: Address, + ) { + const savedAddress = await this.addressRepository.save(newAddress); + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + if (transit.getStatus() === Status.COMPLETED) { + throw new NotAcceptableException( + "Address 'to' cannot be changed, id = " + transitId, + ); + } + + // FIXME later: add some exceptions handling + const geoFrom = this.geocodingService.geocodeAddress(transit.getFrom()); + const geoTo = this.geocodingService.geocodeAddress(savedAddress); + transit.setTo(savedAddress); + transit.setKm( + this.distanceCalculator.calculateByMap( + geoFrom[0], + geoFrom[1], + geoTo[0], + geoTo[1], + ), + ); + + const driver = transit.getDriver(); + await this.transitRepository.save(transit); + if (driver) { + this.notificationService.notifyAboutChangedTransitAddress( + driver.getId(), + transitId, + ); + } + } + + public changeTransitAddressFrom(transitId: string, newAddress: AddressDto) { + return this._changeTransitAddressFrom( + transitId, + newAddress.toAddressEntity(), + ); + } + + public async cancelTransit(transitId: string) { + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + if ( + ![ + Status.DRAFT, + Status.WAITING_FOR_DRIVER_ASSIGNMENT, + Status.TRANSIT_TO_PASSENGER, + ].includes(transit.getStatus()) + ) { + throw new NotAcceptableException( + 'Transit cannot be cancelled, id = ' + transitId, + ); + } + + const driver = transit.getDriver(); + if (driver) { + this.notificationService.notifyAboutCancelledTransit( + driver.getId(), + transitId, + ); + } + + transit.setStatus(Status.CANCELLED); + transit.setDriver(null); + transit.setKm(0); + transit.setAwaitingDriversResponses(0); + await this.transitRepository.save(transit); + } + + public async publishTransit(transitId: string) { + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + transit.setStatus(Status.WAITING_FOR_DRIVER_ASSIGNMENT); + transit.setPublished(Date.now()); + await this.transitRepository.save(transit); + + return this.findDriversForTransit(transitId); + } + + // Abandon hope all ye who enter here... + public async findDriversForTransit(transitId: string) { + const transit = await this.transitRepository.findOne(transitId); + + if (transit) { + if (transit.getStatus() === Status.WAITING_FOR_DRIVER_ASSIGNMENT) { + let distanceToCheck = 0; + + // Tested on production, works as expected. + // If you change this code and the system will collapse AGAIN, I'll find you... + while (true) { + if (transit.getAwaitingDriversResponses() > 4) { + return transit; + } + + distanceToCheck++; + + // FIXME: to refactor when the final business logic will be determined + if ( + dayjs(transit.getPublished()) + .add(300, 'seconds') + .isBefore(dayjs()) || + distanceToCheck >= 20 || + // Should it be here? How is it even possible due to previous status check above loop? + transit.getStatus() === Status.CANCELLED + ) { + transit.setStatus(Status.DRIVER_ASSIGNMENT_FAILED); + transit.setDriver(null); + transit.setKm(0); + transit.setAwaitingDriversResponses(0); + await this.transitRepository.save(transit); + return transit; + } + let geocoded: number[] = [0, 0]; + + try { + geocoded = this.geocodingService.geocodeAddress(transit.getFrom()); + } catch (e) { + // Geocoding failed! Ask Jessica or Bryan for some help if needed. + } + + const longitude = geocoded[1]; + const latitude = geocoded[0]; + + //https://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters + //Earth’s radius, sphere + //double R = 6378; + const R = 6371; // Changed to 6371 due to Copy&Paste pattern from different source + + //offsets in meters + const dn = distanceToCheck; + const de = distanceToCheck; + + //Coordinate offsets in radians + const dLat = dn / R; + const dLon = de / (R * Math.cos((Math.PI * latitude) / 180)); + + //Offset positions, decimal degrees + const latitudeMin = latitude - (dLat * 180) / Math.PI; + const latitudeMax = latitude + (dLat * 180) / Math.PI; + const longitudeMin = longitude - (dLon * 180) / Math.PI; + const longitudeMax = longitude + (dLon * 180) / Math.PI; + + let driversAvgPositions = + await this.driverPositionRepository.findAverageDriverPositionSince( + latitudeMin, + latitudeMax, + longitudeMin, + longitudeMax, + dayjs().subtract(5, 'minutes').valueOf(), + ); + + if (driversAvgPositions.length) { + const comparator = ( + d1: DriverPositionV2Dto, + d2: DriverPositionV2Dto, + ) => { + const a = Math.sqrt( + Math.pow(latitude - d1.getLatitude(), 2) + + Math.pow(longitude - d1.getLongitude(), 2), + ); + const b = Math.sqrt( + Math.pow(latitude - d2.getLatitude(), 2) + + Math.pow(longitude - d2.getLongitude(), 2), + ); + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }; + driversAvgPositions.sort(comparator); + driversAvgPositions = driversAvgPositions.slice(0, 20); + + const carClasses: CarClass[] = []; + const activeCarClasses = + await this.carTypeService.findActiveCarClasses(); + if (activeCarClasses.length === 0) { + return transit; + } + if (transit.getCarType()) { + if (activeCarClasses.includes(transit.getCarType())) { + carClasses.push(transit.getCarType()); + } else { + return transit; + } + } else { + carClasses.push(...activeCarClasses); + } + + const drivers = driversAvgPositions.map((item) => item.getDriver()); + + const fetchedCars = + await this.driverSessionRepository.findAllByLoggedOutAtNullAndDriverInAndCarClassIn( + drivers, + carClasses, + ); + const activeDriverIdsInSpecificCar = fetchedCars.map((ds) => + ds.getDriver().getId(), + ); + + driversAvgPositions = driversAvgPositions.filter((dp) => + activeDriverIdsInSpecificCar.includes(dp.getDriver().getId()), + ); + + // Iterate across average driver positions + for (const driverAvgPosition of driversAvgPositions) { + const driver = driverAvgPosition.getDriver(); + if ( + driver.getStatus() === DriverStatus.ACTIVE && + !driver.getOccupied() + ) { + if ( + !Array.from(transit.getDriversRejections()).includes(driver) + ) { + transit.getProposedDrivers().push(driver); + transit.setAwaitingDriversResponses( + transit.getAwaitingDriversResponses() + 1, + ); + await this.notificationService.notifyAboutPossibleTransit( + driver.getId(), + transitId, + ); + } + } else { + // Not implemented yet! + } + } + + await this.transitRepository.save(transit); + } else { + // Next iteration, no drivers at specified area + continue; + } + } + } else { + throw new NotAcceptableException( + 'Wrong status for transit id = ' + transitId, + ); + } + } else { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + } + + public async acceptTransit(driverId: string, transitId: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException('Driver does not exist, id = ' + driverId); + } else { + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException( + 'Transit does not exist, id = ' + transitId, + ); + } else { + if (transit.getDriver()) { + throw new NotAcceptableException( + 'Transit already accepted, id = ' + transitId, + ); + } else { + if (!Array.from(transit.getProposedDrivers()).includes(driver)) { + throw new NotAcceptableException( + 'Driver out of possible drivers, id = ' + transitId, + ); + } else { + if (Array.from(transit.getDriversRejections()).includes(driver)) { + throw new NotAcceptableException( + 'Driver out of possible drivers, id = ' + transitId, + ); + } else { + transit.setDriver(driver); + transit.setAwaitingDriversResponses(0); + transit.setAcceptedAt(Date.now()); + transit.setStatus(Status.TRANSIT_TO_PASSENGER); + await this.transitRepository.save(transit); + driver.setOccupied(true); + await this.driverRepository.save(driver); + } + } + } + } + } + } + + public async startTransit(driverId: string, transitId: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException('Driver does not exist, id = ' + driverId); + } + + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + if (transit.getStatus() !== Status.TRANSIT_TO_PASSENGER) { + throw new NotAcceptableException( + 'Transit cannot be started, id = ' + transitId, + ); + } + + transit.setStatus(Status.IN_TRANSIT); + transit.setStarted(Date.now()); + await this.transitRepository.save(transit); + } + + public async rejectTransit(driverId: string, transitId: string) { + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException('Driver does not exist, id = ' + driverId); + } + + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + transit.getDriversRejections().push(driver); + transit.setAwaitingDriversResponses( + transit.getAwaitingDriversResponses() - 1, + ); + await this.transitRepository.save(transit); + } + + public completeTransitFromDto( + driverId: string, + transitId: string, + destinationAddress: AddressDto, + ) { + return this.completeTransit( + driverId, + transitId, + destinationAddress.toAddressEntity(), + ); + } + + public async completeTransit( + driverId: string, + transitId: string, + destinationAddress: Address, + ) { + await this.addressRepository.save(destinationAddress); + const driver = await this.driverRepository.findOne(driverId); + + if (!driver) { + throw new NotFoundException('Driver does not exist, id = ' + driverId); + } + + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + if (transit.getStatus() === Status.IN_TRANSIT) { + // FIXME later: add some exceptions handling + const geoFrom = this.geocodingService.geocodeAddress(transit.getFrom()); + const geoTo = this.geocodingService.geocodeAddress(transit.getTo()); + + transit.setTo(destinationAddress); + transit.setKm( + this.distanceCalculator.calculateByMap( + geoFrom[0], + geoFrom[1], + geoTo[0], + geoTo[1], + ), + ); + transit.setStatus(Status.COMPLETED); + transit.calculateFinalCosts(); + driver.setOccupied(false); + transit.setCompleteAt(Date.now()); + const driverFee = await this.driverFeeService.calculateDriverFee( + transitId, + ); + transit.setDriversFee(driverFee); + await this.driverRepository.save(driver); + await this.awardsService.registerMiles( + transit.getClient().getId(), + transitId, + ); + await this.transitRepository.save(transit); + await this.invoiceGenerator.generate( + transit.getPrice() ?? 0, + transit.getClient().getName() + ' ' + transit.getClient().getLastName(), + ); + } else { + throw new NotAcceptableException( + 'Cannot complete Transit, id = ' + transitId, + ); + } + } + + public async loadTransit(transitId: string) { + const transit = await this.transitRepository.findOne(transitId); + + if (!transit) { + throw new NotFoundException('Transit does not exist, id = ' + transitId); + } + + return new TransitDto(transit); + } + + private async addressFromDto(addressDTO: AddressDto) { + const address = addressDTO.toAddressEntity(); + return this.addressRepository.save(address); + } +} diff --git a/tsconfig.json b/tsconfig.json index adb614c..e08b719 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,8 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, + "strictNullChecks": true, + "noImplicitAny": true, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false diff --git a/yarn.lock b/yarn.lock index 062080a..014b4d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -608,6 +608,16 @@ tslib "2.3.1" uuid "8.3.2" +"@nestjs/config@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-1.1.6.tgz#bbb3578f052d9ef9d13ed5071870ff1bfcfcae7c" + integrity sha512-HYizKt6Dr6gcZl8FmZbTfQxP0MG8oXMh+gVFT0XCwYDAq26BOKyhPsIxrKsryicVeKViRgetCUhlJY9EqaekZA== + dependencies: + dotenv "10.0.0" + dotenv-expand "5.1.0" + lodash "4.17.21" + uuid "8.3.2" + "@nestjs/core@^8.0.0": version "8.2.6" resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-8.2.6.tgz#08eb38203fb01a828227ea25972d38bfef5c818f" @@ -651,6 +661,13 @@ optional "0.1.4" tslib "2.3.1" +"@nestjs/typeorm@^8.0.3": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-8.0.3.tgz#e62feb1f3ec7fa0d1353f774f3f6915594c34a4e" + integrity sha512-tf9rTXP6LeFInkwd+tktQhtLRsKp4RRYImprqT8gcHcJDx+xMP1IygnXELOKwF5vo2/mnhrGtBlRQ/iiS6170g== + dependencies: + uuid "8.3.2" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -695,6 +712,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sqltools/formatter@^1.2.2": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" + integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -857,6 +879,18 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash.orderby@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz#126543bb597477dc9b27d748b5822244f577915c" + integrity sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.178" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8" + integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -872,6 +906,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.21.tgz#474d7589a30afcf5291f59bd49cca9ad171ffde4" integrity sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A== +"@types/object-hash@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-2.2.1.tgz#67c169f8f033e0b62abbf81df2d00f4598d540b9" + integrity sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -932,6 +971,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/zen-observable@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" + integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== + "@typescript-eslint/eslint-plugin@^5.0.0": version "5.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.1.tgz#870195d0f2146b36d11fc71131b75aba52354c69" @@ -1289,6 +1333,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -1297,6 +1346,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" + integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== + append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -1502,6 +1556,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1510,6 +1569,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + busboy@^0.2.11: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" @@ -1631,6 +1698,19 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-transformer@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@^0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" + integrity sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw== + dependencies: + libphonenumber-js "^1.9.43" + validator "^13.7.0" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -1638,6 +1718,18 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + cli-spinners@^2.5.0: version "2.6.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" @@ -1850,6 +1942,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dayjs@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" + integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -1857,7 +1954,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -1958,6 +2055,21 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dotenv-expand@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +dotenv@^8.2.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2610,6 +2722,11 @@ hexoid@1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -2660,6 +2777,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +husky@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -2667,7 +2789,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -2711,7 +2833,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3315,7 +3437,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.0.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -3439,6 +3561,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +libphonenumber-js@^1.9.43: + version "1.9.46" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.46.tgz#7ddae167654fb96306209b09e4a05cb7e41e0524" + integrity sha512-QqTX4UVsGy24njtCgLRspiKpxfRniRBZE/P+d0vQXuYWQ+hwDS6X0ouo0O/SRyf7bhhMCE71b6vAvLMtY5PfEw== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -3466,7 +3593,12 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash.orderby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" + integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= + +lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3603,6 +3735,11 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.5" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -3637,6 +3774,15 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3693,12 +3839,12 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -object-assign@^4, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-hash@2.2.0: +object-hash@2.2.0, object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== @@ -3805,6 +3951,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -3822,11 +3973,23 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1: +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -3867,6 +4030,57 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pg-connection-string@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" + integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== + +pg-protocol@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" + integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" + integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.5.0" + pg-pool "^3.4.1" + pg-protocol "^1.5.0" + pg-types "^2.1.0" + pgpass "1.x" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -3894,6 +4108,28 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -4156,7 +4392,7 @@ rxjs@^7.2.0: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4171,6 +4407,11 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -4249,6 +4490,14 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4322,6 +4571,11 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +split2@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -4352,7 +4606,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4528,6 +4782,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + throat@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -4720,6 +4988,28 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typeorm@^0.2.41: + version "0.2.41" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.41.tgz#88758101ac158dc0a0a903d70eaacea2974281cc" + integrity sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw== + dependencies: + "@sqltools/formatter" "^1.2.2" + app-root-path "^3.0.0" + buffer "^6.0.3" + chalk "^4.1.0" + cli-highlight "^2.1.11" + debug "^4.3.1" + dotenv "^8.2.0" + glob "^7.1.6" + js-yaml "^4.0.0" + mkdirp "^1.0.4" + reflect-metadata "^0.1.13" + sha.js "^2.4.11" + tslib "^2.1.0" + xml2js "^0.4.23" + yargs "^17.0.1" + zen-observable-ts "^1.0.0" + typescript@4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" @@ -4781,6 +5071,11 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -4959,6 +5254,19 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" @@ -4989,7 +5297,12 @@ yargs-parser@20.x, yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^16.2.0: +yargs-parser@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" + integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== + +yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -5002,7 +5315,33 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.1: + version "17.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +zen-observable-ts@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" + integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== + dependencies: + "@types/zen-observable" "0.8.3" + zen-observable "0.8.15" + +zen-observable@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" + integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==