diff --git a/migration/1770287692177-AddTotalCustodyBalanceChfAuditPeriod.js b/migration/1770287692177-AddTotalCustodyBalanceChfAuditPeriod.js new file mode 100644 index 0000000000..1a042c275e --- /dev/null +++ b/migration/1770287692177-AddTotalCustodyBalanceChfAuditPeriod.js @@ -0,0 +1,26 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class AddTotalCustodyBalanceChfAuditPeriod1770287692177 { + name = 'AddTotalCustodyBalanceChfAuditPeriod1770287692177' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_data" ADD "totalCustodyBalanceChfAuditPeriod" float`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "totalCustodyBalanceChfAuditPeriod"`); + } +} diff --git a/migration/1770300000000-AddOutputDateToTransaction.js b/migration/1770300000000-AddOutputDateToTransaction.js new file mode 100644 index 0000000000..9e60fe3a53 --- /dev/null +++ b/migration/1770300000000-AddOutputDateToTransaction.js @@ -0,0 +1,53 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class AddOutputDateToTransaction1770300000000 { + name = 'AddOutputDateToTransaction1770300000000'; + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "dbo"."transaction" ADD "outputDate" datetime2`); + + // Populate from buy_crypto + await queryRunner.query(` + UPDATE t + SET t."outputDate" = bc."outputDate" + FROM "dbo"."transaction" t + INNER JOIN "dbo"."buy_crypto" bc ON bc."transactionId" = t."id" + WHERE bc."outputDate" IS NOT NULL + `); + + // Populate from buy_fiat + await queryRunner.query(` + UPDATE t + SET t."outputDate" = bf."outputDate" + FROM "dbo"."transaction" t + INNER JOIN "dbo"."buy_fiat" bf ON bf."transactionId" = t."id" + WHERE bf."outputDate" IS NOT NULL + `); + + // Populate from ref_reward + await queryRunner.query(` + UPDATE t + SET t."outputDate" = rr."outputDate" + FROM "dbo"."transaction" t + INNER JOIN "dbo"."ref_reward" rr ON rr."transactionId" = t."id" + WHERE rr."outputDate" IS NOT NULL + `); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "dbo"."transaction" DROP COLUMN "outputDate"`); + } +}; diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto-out.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto-out.service.ts index d950ec84dc..aaa8322024 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto-out.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto-out.service.ts @@ -10,6 +10,7 @@ import { CustodyOrderService } from 'src/subdomains/core/custody/services/custod import { LiquidityOrderContext } from 'src/subdomains/supporting/dex/entities/liquidity-order.entity'; import { DexService } from 'src/subdomains/supporting/dex/services/dex.service'; import { FeeService } from 'src/subdomains/supporting/payment/services/fee.service'; +import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service'; import { PayoutOrderContext } from 'src/subdomains/supporting/payout/entities/payout-order.entity'; import { PayoutRequest } from 'src/subdomains/supporting/payout/interfaces'; import { PayoutService } from 'src/subdomains/supporting/payout/services/payout.service'; @@ -39,6 +40,7 @@ export class BuyCryptoOutService { private readonly fiatService: FiatService, private readonly custodyOrderService: CustodyOrderService, private readonly feeService: FeeService, + private readonly transactionService: TransactionService, ) {} async payoutTransactions(): Promise { @@ -201,6 +203,8 @@ export class BuyCryptoOutService { await this.buyCryptoRepo.save(tx); + if (tx.transaction) await this.transactionService.completeTransaction(tx.transaction.id, tx.outputDate); + const custodyOrder = await this.custodyOrderService.getCustodyOrderByTx(tx); if (custodyOrder) { diff --git a/src/subdomains/core/referral/reward/services/ref-reward-out.service.ts b/src/subdomains/core/referral/reward/services/ref-reward-out.service.ts index 57cfd652c9..58c80dbc93 100644 --- a/src/subdomains/core/referral/reward/services/ref-reward-out.service.ts +++ b/src/subdomains/core/referral/reward/services/ref-reward-out.service.ts @@ -5,6 +5,7 @@ import { LiquidityOrderContext } from 'src/subdomains/supporting/dex/entities/li import { DexService } from 'src/subdomains/supporting/dex/services/dex.service'; import { PayoutOrderContext } from 'src/subdomains/supporting/payout/entities/payout-order.entity'; import { PayoutRequest } from 'src/subdomains/supporting/payout/interfaces'; +import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service'; import { PayoutService } from 'src/subdomains/supporting/payout/services/payout.service'; import { RefReward, RewardStatus } from '../ref-reward.entity'; import { RefRewardRepository } from '../ref-reward.repository'; @@ -19,13 +20,14 @@ export class RefRewardOutService { private readonly payoutService: PayoutService, private readonly dexService: DexService, private readonly refRewardService: RefRewardService, + private readonly transactionService: TransactionService, ) {} async checkPaidTransaction(): Promise { try { const transactionsPaidOut = await this.refRewardRepo.find({ where: { status: RewardStatus.PAYING_OUT }, - relations: { user: true }, + relations: { user: true, transaction: true }, }); await this.checkCompletion(transactionsPaidOut); @@ -94,6 +96,8 @@ export class RefRewardOutService { if (isComplete) { await this.refRewardRepo.update(...tx.complete(payoutTxId)); + if (tx.transaction) await this.transactionService.completeTransaction(tx.transaction.id, tx.outputDate); + await this.dexService.completeOrders(LiquidityOrderContext.REF_PAYOUT, tx.id.toString()); await this.refRewardService.updatePaidRefCredit([tx.user?.id]); diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts index db48b42aa7..b32bb0dbe0 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts @@ -17,6 +17,7 @@ import { PayInStatus } from 'src/subdomains/supporting/payin/entities/crypto-inp import { CryptoPaymentMethod, FiatPaymentMethod } from 'src/subdomains/supporting/payment/dto/payment-method.enum'; import { FeeService } from 'src/subdomains/supporting/payment/services/fee.service'; import { TransactionHelper } from 'src/subdomains/supporting/payment/services/transaction-helper'; +import { TransactionService } from 'src/subdomains/supporting/payment/services/transaction.service'; import { Price, PriceStep } from 'src/subdomains/supporting/pricing/domain/entities/price'; import { PriceCurrency, @@ -44,6 +45,7 @@ export class BuyFiatPreparationService { private readonly countryService: CountryService, private readonly buyFiatNotificationService: BuyFiatNotificationService, private readonly fiatOutputService: FiatOutputService, + private readonly transactionService: TransactionService, ) {} async doAmlCheck(): Promise { @@ -365,6 +367,9 @@ export class BuyFiatPreparationService { ...entity.complete(entity.fiatOutput.remittanceInfo, entity.fiatOutput.outputDate, entity.fiatOutput.bankTx), ); + if (entity.transaction) + await this.transactionService.completeTransaction(entity.transaction.id, entity.outputDate); + // send webhook await this.buyFiatService.triggerWebhook(entity); } catch (e) { diff --git a/src/subdomains/generic/support/dto/user-data-support.dto.ts b/src/subdomains/generic/support/dto/user-data-support.dto.ts index 68ce06fa13..3569ca3268 100644 --- a/src/subdomains/generic/support/dto/user-data-support.dto.ts +++ b/src/subdomains/generic/support/dto/user-data-support.dto.ts @@ -94,6 +94,7 @@ export class KycFileListEntry { pep?: boolean; complexOrgStructure?: boolean; totalVolumeChfAuditPeriod?: number; + totalCustodyBalanceChfAuditPeriod?: number; } export class KycFileYearlyStats { diff --git a/src/subdomains/generic/support/support.service.ts b/src/subdomains/generic/support/support.service.ts index 495ecaab22..376f803080 100644 --- a/src/subdomains/generic/support/support.service.ts +++ b/src/subdomains/generic/support/support.service.ts @@ -161,6 +161,7 @@ export class SupportService { pep: userData.pep, complexOrgStructure: userData.complexOrgStructure, totalVolumeChfAuditPeriod: userData.totalVolumeChfAuditPeriod, + totalCustodyBalanceChfAuditPeriod: userData.totalCustodyBalanceChfAuditPeriod, }; } diff --git a/src/subdomains/generic/user/models/user-data/dto/update-user-data.dto.ts b/src/subdomains/generic/user/models/user-data/dto/update-user-data.dto.ts index a54fe56231..5e605151c5 100644 --- a/src/subdomains/generic/user/models/user-data/dto/update-user-data.dto.ts +++ b/src/subdomains/generic/user/models/user-data/dto/update-user-data.dto.ts @@ -249,6 +249,10 @@ export class UpdateUserDataDto { @IsNumber() totalVolumeChfAuditPeriod?: number; + @IsOptional() + @IsNumber() + totalCustodyBalanceChfAuditPeriod?: number; + @IsOptional() @IsBoolean() olkypayAllowed?: boolean; diff --git a/src/subdomains/generic/user/models/user-data/user-data.entity.ts b/src/subdomains/generic/user/models/user-data/user-data.entity.ts index 70c22c1cb2..d5430deb17 100644 --- a/src/subdomains/generic/user/models/user-data/user-data.entity.ts +++ b/src/subdomains/generic/user/models/user-data/user-data.entity.ts @@ -138,6 +138,9 @@ export class UserData extends IEntity { @Column({ type: 'float', nullable: true }) totalVolumeChfAuditPeriod?: number; + @Column({ type: 'float', nullable: true }) + totalCustodyBalanceChfAuditPeriod?: number; + // TODO remove @Column({ length: 256, nullable: true }) allBeneficialOwnersName?: string; diff --git a/src/subdomains/generic/user/models/user-data/user-data.service.ts b/src/subdomains/generic/user/models/user-data/user-data.service.ts index e435cdedec..6992b42f16 100644 --- a/src/subdomains/generic/user/models/user-data/user-data.service.ts +++ b/src/subdomains/generic/user/models/user-data/user-data.service.ts @@ -235,6 +235,7 @@ export class UserDataService { 'userData.pep', 'userData.complexOrgStructure', 'userData.totalVolumeChfAuditPeriod', + 'userData.totalCustodyBalanceChfAuditPeriod', 'country.name', ]) .where('userData.kycFileId > 0') diff --git a/src/subdomains/supporting/payment/entities/transaction.entity.ts b/src/subdomains/supporting/payment/entities/transaction.entity.ts index 7bee9def4a..43c37dcb0e 100644 --- a/src/subdomains/supporting/payment/entities/transaction.entity.ts +++ b/src/subdomains/supporting/payment/entities/transaction.entity.ts @@ -91,6 +91,9 @@ export class Transaction extends IEntity { @Column({ type: 'datetime2', nullable: true }) mailSendDate?: Date; + @Column({ type: 'datetime2', nullable: true }) + outputDate?: Date; + // References @OneToOne(() => BuyCrypto, (buyCrypto) => buyCrypto.transaction, { nullable: true }) buyCrypto?: BuyCrypto; @@ -183,6 +186,6 @@ export class Transaction extends IEntity { } get completionDate(): Date | undefined { - return this.buyCrypto?.outputDate ?? this.buyFiat?.outputDate ?? this.refReward?.outputDate; + return this.outputDate ?? this.buyCrypto?.outputDate ?? this.buyFiat?.outputDate ?? this.refReward?.outputDate; } } diff --git a/src/subdomains/supporting/payment/services/transaction.service.ts b/src/subdomains/supporting/payment/services/transaction.service.ts index f4ff249ffe..eaa916a4a7 100644 --- a/src/subdomains/supporting/payment/services/transaction.service.ts +++ b/src/subdomains/supporting/payment/services/transaction.service.ts @@ -188,6 +188,10 @@ export class TransactionService { return this.repo.find({ where: { userData: { id: userDataId } }, relations }); } + async completeTransaction(transactionId: number, outputDate: Date): Promise { + await this.repo.update(transactionId, { outputDate }); + } + async getTransactionByKey(key: string, value: any): Promise { return this.repo .createQueryBuilder('transaction')