diff --git a/migration/1768216257525-MonthlyVolume.js b/migration/1768216257525-MonthlyVolume.js new file mode 100644 index 0000000000..c1fb8f6575 --- /dev/null +++ b/migration/1768216257525-MonthlyVolume.js @@ -0,0 +1,48 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class MonthlyVolume1768216257525 { + name = 'MonthlyVolume1768216257525' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "deposit_route" ADD "monthlyVolume" float CONSTRAINT "DF_65fa016979e56325f08fcdebd9f" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlyBuyVolume" float NOT NULL CONSTRAINT "DF_69fd224d86005d56d0bfc42e783" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlySellVolume" float NOT NULL CONSTRAINT "DF_8b0a3a1acd4e2a9ec6ac9b22f0a" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user_data" ADD "monthlyCryptoVolume" float NOT NULL CONSTRAINT "DF_c2bc7d2a9c5923a33cd61f3cd9d" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "buy" ADD "monthlyVolume" float NOT NULL CONSTRAINT "DF_ae3f992d494992c7b5bd3c098f2" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user" ADD "monthlyBuyVolume" float NOT NULL CONSTRAINT "DF_b71cae68540eac82fa1aff6e553" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user" ADD "monthlySellVolume" float NOT NULL CONSTRAINT "DF_9c2e491c0ea4fc32aed0721c830" DEFAULT 0`); + await queryRunner.query(`ALTER TABLE "user" ADD "monthlyCryptoVolume" float NOT NULL CONSTRAINT "DF_57236c7391c6825b830c1ba49ba" DEFAULT 0`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_57236c7391c6825b830c1ba49ba"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlyCryptoVolume"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_9c2e491c0ea4fc32aed0721c830"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlySellVolume"`); + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "DF_b71cae68540eac82fa1aff6e553"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "monthlyBuyVolume"`); + await queryRunner.query(`ALTER TABLE "buy" DROP CONSTRAINT "DF_ae3f992d494992c7b5bd3c098f2"`); + await queryRunner.query(`ALTER TABLE "buy" DROP COLUMN "monthlyVolume"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_c2bc7d2a9c5923a33cd61f3cd9d"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlyCryptoVolume"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_8b0a3a1acd4e2a9ec6ac9b22f0a"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlySellVolume"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP CONSTRAINT "DF_69fd224d86005d56d0bfc42e783"`); + await queryRunner.query(`ALTER TABLE "user_data" DROP COLUMN "monthlyBuyVolume"`); + await queryRunner.query(`ALTER TABLE "deposit_route" DROP CONSTRAINT "DF_65fa016979e56325f08fcdebd9f"`); + await queryRunner.query(`ALTER TABLE "deposit_route" DROP COLUMN "monthlyVolume"`); + } +} diff --git a/migration/1768216892259-BankAddressMax.js b/migration/1768216892259-BankAddressMax.js new file mode 100644 index 0000000000..d2e53ae05b --- /dev/null +++ b/migration/1768216892259-BankAddressMax.js @@ -0,0 +1,26 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class BankAddressMax1768216892259 { + name = 'BankAddressMax1768216892259' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "bank_account" ALTER COLUMN "bankAddress" nvarchar(MAX)`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "bank_account" ALTER COLUMN "bankAddress" nvarchar(256)`); + } +} diff --git a/src/integration/blockchain/shared/frankencoin/frankencoin-based.service.ts b/src/integration/blockchain/shared/frankencoin/frankencoin-based.service.ts index 62082fc19f..139971490a 100644 --- a/src/integration/blockchain/shared/frankencoin/frankencoin-based.service.ts +++ b/src/integration/blockchain/shared/frankencoin/frankencoin-based.service.ts @@ -79,18 +79,18 @@ export abstract class FrankencoinBasedService { } } - async getCoinGeckoPrice(contractaddress: string): Promise { + async getCoinGeckoPrice(contractAddress: string): Promise { try { const price = await this.pricingService.getPriceFrom( PriceSource.COIN_GECKO, - contractaddress.toLowerCase(), + contractAddress.toLowerCase(), 'usd', 'contract', ); if (price) return price.price; } catch (e) { - this.logger.error(`Failed to get CoinGecko price for collateral ${contractaddress}:`, e); + this.logger.error(`Failed to get CoinGecko price for collateral ${contractAddress}:`, e); } } diff --git a/src/shared/utils/__tests__/async-field.spec.ts b/src/shared/utils/__tests__/async-field.spec.ts index b245bb8c6e..26d3c8e2bc 100644 --- a/src/shared/utils/__tests__/async-field.spec.ts +++ b/src/shared/utils/__tests__/async-field.spec.ts @@ -181,7 +181,7 @@ describe('AsyncField', () => { try { await field; - } catch (e) { + } catch (_e) { // Expected } diff --git a/src/shared/utils/util.ts b/src/shared/utils/util.ts index b935bfcbe1..a370298998 100644 --- a/src/shared/utils/util.ts +++ b/src/shared/utils/util.ts @@ -350,6 +350,16 @@ export class Util { return this.daysAfter(-days, from); } + static startOfMonth(from?: Date): Date { + const date = from ?? new Date(); + return new Date(date.getFullYear(), date.getMonth(), 1); + } + + static startOfYear(from?: Date): Date { + const date = from ?? new Date(); + return new Date(date.getFullYear(), 0, 1); + } + static sameDay(a: Date, b: Date): boolean { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); } diff --git a/src/subdomains/core/aml/services/aml.service.ts b/src/subdomains/core/aml/services/aml.service.ts index 76f8a7ec49..b67c1f5f64 100644 --- a/src/subdomains/core/aml/services/aml.service.ts +++ b/src/subdomains/core/aml/services/aml.service.ts @@ -220,9 +220,10 @@ export class AmlService { if (entity instanceof BuyFiat) return this.countryService.getCountryWithSymbol(entity.sell.iban.substring(0, 2)); if (entity.cryptoInput) return undefined; - return this.countryService.getCountryWithSymbol( - entity.checkoutTx?.cardIssuerCountry ?? entity.bankTx.iban.substring(0, 2), - ); + const countryCode = entity.checkoutTx?.cardIssuerCountry ?? entity.bankTx?.iban?.substring(0, 2); + if (!countryCode) return undefined; + + return this.countryService.getCountryWithSymbol(countryCode); } private async getBankData(entity: BuyFiat | BuyCrypto): Promise { diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts index dd209b7a05..e1996d3079 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts @@ -153,12 +153,10 @@ export class BuyCryptoPreparationService { referenceChfPrice, ); - const ibanCountry = - entity.bankTx?.iban || entity.checkoutTx?.cardIssuerCountry - ? await this.countryService.getCountryWithSymbol( - entity.bankTx?.iban.substring(0, 2) ?? entity.checkoutTx.cardIssuerCountry, - ) - : undefined; + const ibanCountryCode = entity.bankTx?.iban?.substring(0, 2) ?? entity.checkoutTx?.cardIssuerCountry; + const ibanCountry = ibanCountryCode + ? await this.countryService.getCountryWithSymbol(ibanCountryCode) + : undefined; const virtualIban = entity.bankTx?.virtualIban ? await this.virtualIbanService.getByIban(entity.bankTx.virtualIban) diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index 02a4b4d545..b33aff8a80 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -864,50 +864,56 @@ export class BuyCryptoService { async updateBuyVolume(buyIds: number[]): Promise { buyIds = buyIds.filter((u, j) => buyIds.indexOf(u) === j).filter((i) => i); // distinct, not null - for (const id of buyIds) { - const { volume } = await this.buyCryptoRepo - .createQueryBuilder('buyCrypto') - .select('SUM(amountInChf)', 'volume') - .where('buyId = :id', { id: id }) - .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) - .getRawOne<{ volume: number }>(); + const startOfYear = Util.startOfYear(); + const startOfMonth = Util.startOfMonth(); - const newYear = new Date(new Date().getFullYear(), 0, 1); - const { annualVolume } = await this.buyCryptoRepo + for (const id of buyIds) { + const { volume, annualVolume, monthlyVolume } = await this.buyCryptoRepo .createQueryBuilder('buyCrypto') - .select('SUM(amountInChf)', 'annualVolume') + .select('SUM(buyCrypto.amountInChf)', 'volume') + .addSelect( + 'SUM(CASE WHEN bankTx.bookingDate >= :startOfYear THEN buyCrypto.amountInChf ELSE 0 END)', + 'annualVolume', + ) + .addSelect( + 'SUM(CASE WHEN bankTx.bookingDate >= :startOfMonth THEN buyCrypto.amountInChf ELSE 0 END)', + 'monthlyVolume', + ) .leftJoin('buyCrypto.bankTx', 'bankTx') - .where('buyCrypto.buyId = :id', { id: id }) + .where('buyCrypto.buyId = :id', { id }) .andWhere('buyCrypto.amlCheck = :check', { check: CheckStatus.PASS }) - .andWhere('bankTx.bookingDate >= :year', { year: newYear }) - .getRawOne<{ annualVolume: number }>(); + .setParameters({ startOfYear, startOfMonth }) + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); - await this.buyService.updateVolume(id, volume ?? 0, annualVolume ?? 0); + await this.buyService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0); } } async updateCryptoRouteVolume(cryptoRouteIds: number[]): Promise { cryptoRouteIds = cryptoRouteIds.filter((u, j) => cryptoRouteIds.indexOf(u) === j).filter((i) => i); // distinct, not null - for (const id of cryptoRouteIds) { - const { volume } = await this.buyCryptoRepo - .createQueryBuilder('buyCrypto') - .select('SUM(amountInChf)', 'volume') - .where('cryptoRouteId = :id', { id: id }) - .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) - .getRawOne<{ volume: number }>(); + const startOfYear = Util.startOfYear(); + const startOfMonth = Util.startOfMonth(); - const newYear = new Date(new Date().getFullYear(), 0, 1); - const { annualVolume } = await this.buyCryptoRepo + for (const id of cryptoRouteIds) { + const { volume, annualVolume, monthlyVolume } = await this.buyCryptoRepo .createQueryBuilder('buyCrypto') - .select('SUM(amountInChf)', 'annualVolume') + .select('SUM(buyCrypto.amountInChf)', 'volume') + .addSelect( + 'SUM(CASE WHEN cryptoInput.created >= :startOfYear THEN buyCrypto.amountInChf ELSE 0 END)', + 'annualVolume', + ) + .addSelect( + 'SUM(CASE WHEN cryptoInput.created >= :startOfMonth THEN buyCrypto.amountInChf ELSE 0 END)', + 'monthlyVolume', + ) .leftJoin('buyCrypto.cryptoInput', 'cryptoInput') - .where('buyCrypto.cryptoRouteId = :id', { id: id }) + .where('buyCrypto.cryptoRouteId = :id', { id }) .andWhere('buyCrypto.amlCheck = :check', { check: CheckStatus.PASS }) - .andWhere('cryptoInput.created >= :year', { year: newYear }) - .getRawOne<{ annualVolume: number }>(); + .setParameters({ startOfYear, startOfMonth }) + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); - await this.swapService.updateVolume(id, volume ?? 0, annualVolume ?? 0); + await this.swapService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0); } } diff --git a/src/subdomains/core/buy-crypto/routes/buy/buy.entity.ts b/src/subdomains/core/buy-crypto/routes/buy/buy.entity.ts index bf2c219a66..8a90513964 100644 --- a/src/subdomains/core/buy-crypto/routes/buy/buy.entity.ts +++ b/src/subdomains/core/buy-crypto/routes/buy/buy.entity.ts @@ -22,6 +22,9 @@ export class Buy extends IEntity { @Column({ type: 'float', default: 0 }) annualVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlyVolume: number; // CHF + @Column({ default: true }) active: boolean; diff --git a/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts b/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts index 0e9ef98556..f0480bbae4 100644 --- a/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts +++ b/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts @@ -63,10 +63,16 @@ export class BuyService { await this.buyRepo.update({ annualVolume: Not(0) }, { annualVolume: 0 }); } - async updateVolume(buyId: number, volume: number, annualVolume: number): Promise { + @DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async resetMonthlyVolumes(): Promise { + await this.buyRepo.update({ monthlyVolume: Not(0) }, { monthlyVolume: 0 }); + } + + async updateVolume(buyId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.buyRepo.update(buyId, { volume: Util.round(volume, Config.defaultVolumeDecimal), annualVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); // update user volume @@ -76,16 +82,23 @@ export class BuyService { select: ['id', 'user'], }); const userVolume = await this.getUserVolume(user.id); - await this.userService.updateBuyVolume(user.id, userVolume.volume, userVolume.annualVolume); + + await this.userService.updateBuyVolume( + user.id, + userVolume.volume, + userVolume.annualVolume, + userVolume.monthlyVolume, + ); } - async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number }> { + async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number; monthlyVolume: number }> { return this.buyRepo .createQueryBuilder('buy') .select('SUM(volume)', 'volume') .addSelect('SUM(annualVolume)', 'annualVolume') + .addSelect('SUM(monthlyVolume)', 'monthlyVolume') .where('userId = :id', { id: userId }) - .getRawOne<{ volume: number; annualVolume: number }>(); + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); } async getTotalVolume(): Promise { diff --git a/src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts b/src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts index 701d193f2f..4229f6995b 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/swap.entity.ts @@ -16,6 +16,9 @@ export class Swap extends DepositRoute { @Column({ type: 'float', default: 0 }) annualVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlyVolume: number; // CHF + @ManyToOne(() => User, (user) => user.swaps, { nullable: false }) declare user: User; diff --git a/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts b/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts index e14c1ab40f..bf66c89ea0 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts @@ -90,10 +90,16 @@ export class SwapService { await this.swapRepo.update({ annualVolume: Not(0) }, { annualVolume: 0 }); } - async updateVolume(swapId: number, volume: number, annualVolume: number): Promise { + @DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async resetMonthlyVolumes(): Promise { + await this.swapRepo.update({ monthlyVolume: Not(0) }, { monthlyVolume: 0 }); + } + + async updateVolume(swapId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.swapRepo.update(swapId, { volume: Util.round(volume, Config.defaultVolumeDecimal), annualVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); // update user volume @@ -103,16 +109,23 @@ export class SwapService { select: ['id', 'user'], }); const userVolume = await this.getUserVolume(user.id); - await this.userService.updateCryptoVolume(user.id, userVolume.volume, userVolume.annualVolume); + + await this.userService.updateCryptoVolume( + user.id, + userVolume.volume, + userVolume.annualVolume, + userVolume.monthlyVolume, + ); } - async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number }> { + async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number; monthlyVolume: number }> { return this.swapRepo .createQueryBuilder('crypto') .select('SUM(volume)', 'volume') .addSelect('SUM(annualVolume)', 'annualVolume') + .addSelect('SUM(monthlyVolume)', 'monthlyVolume') .where('userId = :id', { id: userId }) - .getRawOne<{ volume: number; annualVolume: number }>(); + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); } async getTotalVolume(): Promise { diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index e88bedeac0..c83829a118 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -481,25 +481,28 @@ export class BuyFiatService { async updateSellVolume(sellIds: number[]): Promise { sellIds = sellIds.filter((u, j) => sellIds.indexOf(u) === j).filter((i) => i); // distinct, not null - for (const id of sellIds) { - const { volume } = await this.buyFiatRepo - .createQueryBuilder('buyFiat') - .select('SUM(amountInChf)', 'volume') - .where('sellId = :id', { id: id }) - .andWhere('amlCheck = :check', { check: CheckStatus.PASS }) - .getRawOne<{ volume: number }>(); + const startOfYear = Util.startOfYear(); + const startOfMonth = Util.startOfMonth(); - const newYear = new Date(new Date().getFullYear(), 0, 1); - const { annualVolume } = await this.buyFiatRepo + for (const id of sellIds) { + const { volume, annualVolume, monthlyVolume } = await this.buyFiatRepo .createQueryBuilder('buyFiat') - .select('SUM(amountInChf)', 'annualVolume') + .select('SUM(buyFiat.amountInChf)', 'volume') + .addSelect( + 'SUM(CASE WHEN cryptoInput.created >= :startOfYear THEN buyFiat.amountInChf ELSE 0 END)', + 'annualVolume', + ) + .addSelect( + 'SUM(CASE WHEN cryptoInput.created >= :startOfMonth THEN buyFiat.amountInChf ELSE 0 END)', + 'monthlyVolume', + ) .leftJoin('buyFiat.cryptoInput', 'cryptoInput') - .where('buyFiat.sellId = :id', { id: id }) + .where('buyFiat.sellId = :id', { id }) .andWhere('buyFiat.amlCheck = :check', { check: CheckStatus.PASS }) - .andWhere('cryptoInput.created >= :year', { year: newYear }) - .getRawOne<{ annualVolume: number }>(); + .setParameters({ startOfYear, startOfMonth }) + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); - await this.sellService.updateVolume(id, volume ?? 0, annualVolume ?? 0); + await this.sellService.updateVolume(id, volume ?? 0, annualVolume ?? 0, monthlyVolume ?? 0); } } diff --git a/src/subdomains/core/sell-crypto/route/sell.entity.ts b/src/subdomains/core/sell-crypto/route/sell.entity.ts index ee95b0b421..b8122b9694 100644 --- a/src/subdomains/core/sell-crypto/route/sell.entity.ts +++ b/src/subdomains/core/sell-crypto/route/sell.entity.ts @@ -20,6 +20,9 @@ export class Sell extends DepositRoute { @Column({ type: 'float', default: 0 }) annualVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlyVolume: number; // CHF + @ManyToOne(() => User, (user) => user.sells, { nullable: false }) declare user: User; diff --git a/src/subdomains/core/sell-crypto/route/sell.service.ts b/src/subdomains/core/sell-crypto/route/sell.service.ts index 7615935b9b..b615cec990 100644 --- a/src/subdomains/core/sell-crypto/route/sell.service.ts +++ b/src/subdomains/core/sell-crypto/route/sell.service.ts @@ -217,10 +217,16 @@ export class SellService { await this.sellRepo.update({ annualVolume: Not(0) }, { annualVolume: 0 }); } - async updateVolume(sellId: number, volume: number, annualVolume: number): Promise { + @DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async resetMonthlyVolumes(): Promise { + await this.sellRepo.update({ monthlyVolume: Not(0) }, { monthlyVolume: 0 }); + } + + async updateVolume(sellId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.sellRepo.update(sellId, { volume: Util.round(volume, Config.defaultVolumeDecimal), annualVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); // update user volume @@ -230,16 +236,23 @@ export class SellService { select: ['id', 'user'], }); const userVolume = await this.getUserVolume(user.id); - await this.userService.updateSellVolume(user.id, userVolume.volume, userVolume.annualVolume); + + await this.userService.updateSellVolume( + user.id, + userVolume.volume, + userVolume.annualVolume, + userVolume.monthlyVolume, + ); } - async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number }> { + async getUserVolume(userId: number): Promise<{ volume: number; annualVolume: number; monthlyVolume: number }> { return this.sellRepo .createQueryBuilder('sell') .select('SUM(volume)', 'volume') .addSelect('SUM(annualVolume)', 'annualVolume') + .addSelect('SUM(monthlyVolume)', 'monthlyVolume') .where('userId = :id', { id: userId }) - .getRawOne<{ volume: number; annualVolume: number }>(); + .getRawOne<{ volume: number; annualVolume: number; monthlyVolume: number }>(); } async getTotalVolume(): Promise { 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 3677e0cdf9..7f5b423fe9 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 @@ -295,18 +295,28 @@ export class UserData extends IEntity { apiFilterCT?: string; // Volumes + + @Column({ type: 'float', default: 0 }) + monthlyBuyVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualBuyVolume: number; // CHF @Column({ type: 'float', default: 0 }) buyVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlySellVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualSellVolume: number; // CHF @Column({ type: 'float', default: 0 }) sellVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlyCryptoVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualCryptoVolume: number; // CHF 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 c1f735f9d6..eff8ad313c 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 @@ -862,8 +862,18 @@ export class UserDataService { // --- VOLUMES --- // @DfxCron(CronExpression.EVERY_YEAR) async resetAnnualVolumes(): Promise { - await this.userDataRepo.update({ annualBuyVolume: Not(0) }, { annualBuyVolume: 0 }); - await this.userDataRepo.update({ annualSellVolume: Not(0) }, { annualSellVolume: 0 }); + await this.userDataRepo.update( + [{ annualBuyVolume: Not(0) }, { annualSellVolume: Not(0) }, { annualCryptoVolume: Not(0) }], + { annualBuyVolume: 0, annualSellVolume: 0, annualCryptoVolume: 0 }, + ); + } + + @DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async resetMonthlyVolumes(): Promise { + await this.userDataRepo.update( + [{ monthlyBuyVolume: Not(0) }, { monthlySellVolume: Not(0) }, { monthlyCryptoVolume: Not(0) }], + { monthlyBuyVolume: 0, monthlySellVolume: 0, monthlyCryptoVolume: 0 }, + ); } async updateVolumes(userDataId: number): Promise { @@ -871,27 +881,37 @@ export class UserDataService { .createQueryBuilder('user') .select('SUM(buyVolume)', 'buyVolume') .addSelect('SUM(annualBuyVolume)', 'annualBuyVolume') + .addSelect('SUM(monthlyBuyVolume)', 'monthlyBuyVolume') .addSelect('SUM(sellVolume)', 'sellVolume') .addSelect('SUM(annualSellVolume)', 'annualSellVolume') + .addSelect('SUM(monthlySellVolume)', 'monthlySellVolume') .addSelect('SUM(cryptoVolume)', 'cryptoVolume') .addSelect('SUM(annualCryptoVolume)', 'annualCryptoVolume') + .addSelect('SUM(monthlyCryptoVolume)', 'monthlyCryptoVolume') + .where('userDataId = :id', { id: userDataId }) .getRawOne<{ buyVolume: number; annualBuyVolume: number; + monthlyBuyVolume: number; sellVolume: number; annualSellVolume: number; + monthlySellVolume: number; cryptoVolume: number; annualCryptoVolume: number; + monthlyCryptoVolume: number; }>(); await this.userDataRepo.update(userDataId, { buyVolume: Util.round(volumes.buyVolume, Config.defaultVolumeDecimal), annualBuyVolume: Util.round(volumes.annualBuyVolume, Config.defaultVolumeDecimal), + monthlyBuyVolume: Util.round(volumes.monthlyBuyVolume, Config.defaultVolumeDecimal), sellVolume: Util.round(volumes.sellVolume, Config.defaultVolumeDecimal), annualSellVolume: Util.round(volumes.annualSellVolume, Config.defaultVolumeDecimal), + monthlySellVolume: Util.round(volumes.monthlySellVolume, Config.defaultVolumeDecimal), cryptoVolume: Util.round(volumes.cryptoVolume, Config.defaultVolumeDecimal), annualCryptoVolume: Util.round(volumes.annualCryptoVolume, Config.defaultVolumeDecimal), + monthlyCryptoVolume: Util.round(volumes.monthlyCryptoVolume, Config.defaultVolumeDecimal), }); } diff --git a/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts b/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts index 0efdbab538..e1d70390ff 100644 --- a/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts +++ b/src/subdomains/generic/user/models/user/dto/user-dto.mapper.ts @@ -62,9 +62,9 @@ export class UserDtoMapper { private static mapVolumes(user: UserData | User): VolumesDto { const dto: VolumesDto = { - buy: { total: user.buyVolume, annual: user.annualBuyVolume }, - sell: { total: user.sellVolume, annual: user.annualSellVolume }, - swap: { total: user.cryptoVolume, annual: user.annualCryptoVolume }, + buy: { total: user.buyVolume, annual: user.annualBuyVolume, monthly: user.monthlyBuyVolume }, + sell: { total: user.sellVolume, annual: user.annualSellVolume, monthly: user.monthlySellVolume }, + swap: { total: user.cryptoVolume, annual: user.annualCryptoVolume, monthly: user.monthlyCryptoVolume }, }; return Object.assign(new VolumesDto(), dto); diff --git a/src/subdomains/generic/user/models/user/dto/user.dto.ts b/src/subdomains/generic/user/models/user/dto/user.dto.ts index 130dd84a95..d73b86e376 100644 --- a/src/subdomains/generic/user/models/user/dto/user.dto.ts +++ b/src/subdomains/generic/user/models/user/dto/user.dto.ts @@ -13,6 +13,9 @@ export class VolumeInformation { @ApiProperty() annual: number; + + @ApiProperty() + monthly: number; } export class TradingLimit { diff --git a/src/subdomains/generic/user/models/user/user.entity.ts b/src/subdomains/generic/user/models/user/user.entity.ts index 8ddea97a29..8b150f0d7a 100644 --- a/src/subdomains/generic/user/models/user/user.entity.ts +++ b/src/subdomains/generic/user/models/user/user.entity.ts @@ -67,18 +67,27 @@ export class User extends IEntity { @Column({ length: 256, nullable: true }) apiFilterCT?: string; + @Column({ type: 'float', default: 0 }) + monthlyBuyVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualBuyVolume: number; // CHF @Column({ type: 'float', default: 0 }) buyVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlySellVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualSellVolume: number; // CHF @Column({ type: 'float', default: 0 }) sellVolume: number; // CHF + @Column({ type: 'float', default: 0 }) + monthlyCryptoVolume: number; // CHF + @Column({ type: 'float', default: 0 }) annualCryptoVolume: number; // CHF diff --git a/src/subdomains/generic/user/models/user/user.service.ts b/src/subdomains/generic/user/models/user/user.service.ts index 0a134d5b8d..d316677ea3 100644 --- a/src/subdomains/generic/user/models/user/user.service.ts +++ b/src/subdomains/generic/user/models/user/user.service.ts @@ -375,32 +375,45 @@ export class UserService { // --- VOLUMES --- // @DfxCron(CronExpression.EVERY_YEAR) async resetAnnualVolumes(): Promise { - await this.userRepo.update({ annualBuyVolume: Not(0) }, { annualBuyVolume: 0 }); - await this.userRepo.update({ annualSellVolume: Not(0) }, { annualSellVolume: 0 }); + await this.userRepo.update( + [{ annualBuyVolume: Not(0) }, { annualSellVolume: Not(0) }, { annualCryptoVolume: Not(0) }], + { annualBuyVolume: 0, annualSellVolume: 0, annualCryptoVolume: 0 }, + ); + } + + @DfxCron(CronExpression.EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT) + async resetMonthlyVolumes(): Promise { + await this.userRepo.update( + [{ monthlyBuyVolume: Not(0) }, { monthlySellVolume: Not(0) }, { monthlyCryptoVolume: Not(0) }], + { monthlyBuyVolume: 0, monthlySellVolume: 0, monthlyCryptoVolume: 0 }, + ); } - async updateBuyVolume(userId: number, volume: number, annualVolume: number): Promise { + async updateBuyVolume(userId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.userRepo.update(userId, { buyVolume: Util.round(volume, Config.defaultVolumeDecimal), annualBuyVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlyBuyVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); await this.updateUserDataVolume(userId); } - async updateCryptoVolume(userId: number, volume: number, annualVolume: number): Promise { + async updateCryptoVolume(userId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.userRepo.update(userId, { cryptoVolume: Util.round(volume, Config.defaultVolumeDecimal), annualCryptoVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlyCryptoVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); await this.updateUserDataVolume(userId); } - async updateSellVolume(userId: number, volume: number, annualVolume: number): Promise { + async updateSellVolume(userId: number, volume: number, annualVolume: number, monthlyVolume: number): Promise { await this.userRepo.update(userId, { sellVolume: Util.round(volume, Config.defaultVolumeDecimal), annualSellVolume: Util.round(annualVolume, Config.defaultVolumeDecimal), + monthlySellVolume: Util.round(monthlyVolume, Config.defaultVolumeDecimal), }); await this.updateUserDataVolume(userId); @@ -615,9 +628,9 @@ export class UserService { user.buyVolume + user.sellVolume + user.cryptoVolume >= Config.support.blackSquad.limit ? Config.support.blackSquad.link : undefined, - buyVolume: { total: user.buyVolume, annual: user.annualBuyVolume }, - sellVolume: { total: user.sellVolume, annual: user.annualSellVolume }, - cryptoVolume: { total: user.cryptoVolume, annual: user.annualCryptoVolume }, + buyVolume: { total: user.buyVolume, annual: user.annualBuyVolume, monthly: user.monthlyBuyVolume }, + sellVolume: { total: user.sellVolume, annual: user.annualSellVolume, monthly: user.monthlySellVolume }, + cryptoVolume: { total: user.cryptoVolume, annual: user.annualCryptoVolume, monthly: user.monthlyCryptoVolume }, stakingBalance: 0, }; } diff --git a/src/subdomains/supporting/bank/bank-account/bank-account.entity.ts b/src/subdomains/supporting/bank/bank-account/bank-account.entity.ts index 935fb519a4..e9c01e2cfc 100644 --- a/src/subdomains/supporting/bank/bank-account/bank-account.entity.ts +++ b/src/subdomains/supporting/bank/bank-account/bank-account.entity.ts @@ -55,7 +55,7 @@ export class BankAccount extends IEntity implements BankAccountInfos { @Column({ length: 256, nullable: true }) bankName?: string; - @Column({ length: 256, nullable: true }) + @Column({ length: 'MAX', nullable: true }) bankAddress?: string; @Column({ length: 256, nullable: true })