import { BigNumber } from 'ethers' ; import { FindManyOptions , Repository } from 'typeorm' ; import { InjectRepository } from '@nestjs/typeorm' ; import { BadRequestException , ConflictException , Inject , Injectable , InternalServerErrorException , NotFoundException } from '@nestjs/common' ; import { EventBus } from '@nestjs/cqrs' ; import { Device as IrecDevice , DeviceCreateParams , DeviceState } from '@energyweb/issuer-irec-api-wrapper' ; import { ILoggedInUser } from '@energyweb/origin-backend-core' ; import { UserService } from '@energyweb/origin-backend' ; import { IREC_SERVICE , IrecService } from '@energyweb/origin-organization-irec-api' ; import { Device } from './device.entity' ; import { CodeNameDTO , CreateDeviceDTO , ImportIrecDeviceDTO , UpdateDeviceDTO } from './dto' ; import { DeviceCreatedEvent , DeviceStatusChangedEvent } from './events' ; import { IREC_DEVICE_TYPES , IREC_FUEL_TYPES } from './Fuels' ; @ Injectable ( ) export class DeviceService { constructor ( @ InjectRepository ( Device ) private readonly repository , private readonly eventBus , @ Inject ( IREC_SERVICE ) private readonly irecService , private readonly userService ) { } async findOne ( id ) { return this . repository . findOne ( id ) ; } async create ( user , newDevice ) { if ( ! this . isValidDeviceType ( newDevice . deviceType ) ) { throw new BadRequestException ( 'Invalid device type' ) ; } if ( ! this . isValidFuelType ( newDevice . fuelType ) ) { throw new BadRequestException ( 'Invalid fuel type' ) ; } const platformAdmin = await this . userService . getPlatformAdmin ( ) ; const irecTradeAccountCode = newDevice . irecTradeAccountCode || ( await this . irecService . getTradeAccountCode ( platformAdmin . organization . id ) ) ; if ( ! irecTradeAccountCode ) { throw new InternalServerErrorException ( 'Platform admin IREC account does not have a trade account' ) ; } const issuerOrg = await this . irecService . getUserOrganization ( platformAdmin . organization . id ) ; const registrantOrg = await this . irecService . getUserOrganization ( user ) ; const deviceData = { ... CreateDeviceDTO . sanitize ( newDevice ) , defaultAccount : irecTradeAccountCode , registrantOrganization : registrantOrg . code , issuer : issuerOrg . code , active : true } ; const irecDevice = await this . irecService . createDevice ( user , { ... deviceData , capacity : BigNumber . from ( deviceData . capacity ) . div ( 1e6 ) . toString ( ) , address : this . getAddressLine ( newDevice ) } ) ; const deviceToStore = new Device ( { ... deviceData , ... irecDevice , irecTradeAccountCode , capacity : deviceData . capacity , status : DeviceState . InProgress , ownerId : user . ownerId } ) ; const storedDevice = await this . repository . save ( deviceToStore ) ; this . eventBus . publish ( new DeviceCreatedEvent ( storedDevice , user . id ) ) ; return storedDevice ; } async findAll ( options ? ) { return this . repository . find ( options ) ; } async update ( user , id , deviceData ) { const device = await this . findOne ( id ) ; if ( ! device ) { return null ; } if ( device . status === DeviceState . Approved ) { throw new BadRequestException ( 'Approved device is not allowed to edit' ) ; } const updatedDevice = { ... device , ... deviceData } ; const irecDevice = await this . irecService . updateDevice ( user , device . code , { ... deviceData , address : this . getAddressLine ( updatedDevice ) } ) ; await this . repository . update ( device . id , { ... irecDevice , address : updatedDevice . address , status : DeviceState . InProgress } ) ; return this . findOne ( id ) ; } async updateStatus ( id , status ) { await this . repository . update ( id , { status } ) ; } async approveDevice ( id ) { const device = await this . findOne ( id ) ; const allowedStatuses = [ DeviceState . InProgress , DeviceState . Submitted ] ; if ( ! allowedStatuses . includes ( device . status ) ) { throw new BadRequestException ( 'Device state have to be in "In-Progress" state' ) ; } const platformAdmin = await this . userService . getPlatformAdmin ( ) ; await this . irecService . approveDevice ( platformAdmin . organization . id , device . code ) ; await this . repository . update ( id , { status : DeviceState . Approved } ) ; device . status = DeviceState . Approved ; this . eventBus . publish ( new DeviceStatusChangedEvent ( device , device . status ) ) ; return device ; } async rejectDevice ( id ) { const device = await this . findOne ( id ) ; if ( device . status === DeviceState . Draft ) { throw new BadRequestException ( 'Device state have to be not in "Draft" state' ) ; } const platformAdmin = await this . userService . getPlatformAdmin ( ) ; await this . irecService . rejectDevice ( platformAdmin . organization . id , device . code ) ; await this . repository . update ( id , { status : DeviceState . Rejected } ) ; device . status = DeviceState . Rejected ; this . eventBus . publish ( new DeviceStatusChangedEvent ( device , device . status ) ) ; return device ; } getDeviceTypes ( ) { return IREC_DEVICE_TYPES ; } getFuelTypes ( ) { return IREC_FUEL_TYPES ; } isValidDeviceType ( deviceType ) { return ! ! this . getDeviceTypes ( ) . find ( ( device ) => device . code === deviceType ) ; } isValidFuelType ( fuelType ) { return ! ! this . getFuelTypes ( ) . find ( ( fuel ) => fuel . code === fuelType ) ; } async getDevicesToImport ( user ) { const irecDevices = await this . irecService . getDevices ( user ) ; const devices = await this . repository . find ( { where : { ownerId : user . ownerId } } ) ; const deviceCodes = devices . map ( ( d ) => d . code ) ; return irecDevices . filter ( ( d ) => ! deviceCodes . includes ( d . code ) ) . map ( ( d ) => { return { ... d , capacity : this . castCapacity ( d . capacity ) } ; } ) ; } async importIrecDevice ( user , deviceToImport ) { const irecDevices = await this . irecService . getDevices ( user ) ; const irecDevice = irecDevices . find ( ( d ) => d . code === deviceToImport . code ) ; if ( ! irecDevice ) { throw new NotFoundException ( `Device with code ${ deviceToImport . code } not found` ) ; } const device = await this . repository . findOne ( { where : { code : deviceToImport . code } } ) ; if ( device ) { throw new ConflictException ( `Device with code ${ deviceToImport . code } already exists` ) ; } const deviceToStore = new Device ( { ... deviceToImport , ... irecDevice , capacity : this . castCapacity ( irecDevice . capacity ) , ownerId : user . ownerId } ) ; const storedDevice = await this . repository . save ( deviceToStore ) ; this . eventBus . publish ( new DeviceCreatedEvent ( storedDevice , user . id ) ) ; return storedDevice ; } getAddressLine ( device ) { return `${ device . countryCode }, ${ device . postalCode }, ${ device . region }, ${ device . subregion }` ; } castCapacity ( capacity ) { return Math . floor ( Number ( capacity ) * 1e6 ) . toString ( ) ; } }