diff --git a/services/workflows-service/prisma/data-migrations b/services/workflows-service/prisma/data-migrations index 41cfb6b47f..537aece50e 160000 --- a/services/workflows-service/prisma/data-migrations +++ b/services/workflows-service/prisma/data-migrations @@ -1 +1 @@ -Subproject commit 41cfb6b47fdff56308fd94f1e13058003fc8d61b +Subproject commit 537aece50e80611faf0a2245f758dc40ff2e6f63 diff --git a/services/workflows-service/src/alert/alert.service.intg.test.ts b/services/workflows-service/src/alert/alert.service.intg.test.ts index e357f2c721..9f6170e3f9 100644 --- a/services/workflows-service/src/alert/alert.service.intg.test.ts +++ b/services/workflows-service/src/alert/alert.service.intg.test.ts @@ -70,6 +70,13 @@ const createTransactionsWithCounterpartyAsync = async ( return baseTransactionFactory; }; +const createFutureDate = (daysToAdd: number) => { + const currentDate = new Date(); + const futureDate = new Date(currentDate); + futureDate.setDate(currentDate.getDate() + daysToAdd); + return futureDate; +}; + describe('AlertService', () => { let prismaService: PrismaService; let alertService: AlertService; @@ -429,7 +436,7 @@ describe('AlertService', () => { }); }); - it.only('When there are more than or equal to 15 chargeback transactions, an alert should be created', async () => { + it('When there are more than or equal to 15 chargeback transactions, an alert should be created', async () => { // Arrange const business1Transactions = await baseTransactionFactory .withBusinessOriginator() @@ -1003,8 +1010,8 @@ describe('AlertService', () => { .withBusinessBeneficiary() .direction(TransactionDirection.inbound) .paymentMethod(PaymentMethod.credit_card) - .amount(2) - .count(ALERT_DEFINITIONS.PAY_HCA_CC.inlineRule.options.amountThreshold + 1) + .amount(ALERT_DEFINITIONS.PAY_HCA_CC.inlineRule.options.amountThreshold + 1) + .count(1) .create(); // Act @@ -1015,7 +1022,7 @@ describe('AlertService', () => { expect(alerts).toHaveLength(1); expect(alerts[0]?.alertDefinitionId).toEqual(alertDefinition.id); expect(alerts[0] as any).toMatchObject({ - executionDetails: { executionRow: { transactionCount: '1001', totalAmount: 2002 } }, + executionDetails: { executionRow: { transactionCount: '1', totalAmount: 1001 } }, }); }); @@ -1067,6 +1074,36 @@ describe('AlertService', () => { expect(ALERT_DEFINITIONS.PAY_HCA_APM.inlineRule.options.excludePaymentMethods).toBe(true); }); + it('When there more than 1k credit card transactions, an alert should be created', async () => { + // Arrange + await baseTransactionFactory + .withBusinessBeneficiary() + .direction(TransactionDirection.inbound) + .paymentMethod(PaymentMethod.debit_card) + .amount(ALERT_DEFINITIONS.PAY_HCA_APM.inlineRule.options.amountThreshold + 1) + .count(1) + .create(); + + await baseTransactionFactory + .withBusinessBeneficiary() + .direction(TransactionDirection.inbound) + .paymentMethod(PaymentMethod.apple_pay) + .amount(ALERT_DEFINITIONS.PAY_HCA_APM.inlineRule.options.amountThreshold + 1) + .transactionDate(createFutureDate(1)) + .count(1) + .create(); + // Act + await alertService.checkAllAlerts(); + + // Assert + const alerts = await prismaService.alert.findMany(); + expect(alerts).toHaveLength(1); + expect(alerts[0]?.alertDefinitionId).toEqual(alertDefinition.id); + expect(alerts[0] as any).toMatchObject({ + executionDetails: { executionRow: { transactionCount: '1', totalAmount: 1001 } }, + }); + }); + it('When there more than 1k credit card transactions, an alert should be created', async () => { // Arrange await baseTransactionFactory diff --git a/services/workflows-service/src/alert/alert.service.ts b/services/workflows-service/src/alert/alert.service.ts index 07b232dd4a..92d992f2f0 100644 --- a/services/workflows-service/src/alert/alert.service.ts +++ b/services/workflows-service/src/alert/alert.service.ts @@ -466,13 +466,17 @@ export class AlertService { buildTransactionsFiltersByAlert(alert: Alert & { alertDefinition: AlertDefinition }) { const filters: { - endDate: Date; + endDate: Date | undefined; startDate: Date | undefined; } = { - endDate: alert.updatedAt || alert.createdAt, + endDate: undefined, startDate: undefined, }; + const endDate = alert.updatedAt || alert.createdAt; + endDate.setHours(23, 59, 59, 999); + filters.endDate = endDate; + const inlineRule = alert?.alertDefinition?.inlineRule as InlineRule; // @ts-ignore - TODO: Replace logic with proper implementation for each rule @@ -491,7 +495,7 @@ export class AlertService { } } - const startDate = new Date(filters.endDate); + let startDate = new Date(endDate); let subtractValue = 0; @@ -516,8 +520,12 @@ export class AlertService { } startDate.setHours(0, 0, 0, 0); + startDate = new Date(startDate.getTime() - subtractValue); + + const oldestDate = new Date(Math.min(startDate.getTime(), new Date(alert.createdAt).getTime())); - filters.startDate = new Date(startDate.getTime() - subtractValue); + oldestDate.setHours(0, 0, 0, 0); + filters.startDate = oldestDate; return filters; } diff --git a/services/workflows-service/src/data-analytics/data-analytics.service.ts b/services/workflows-service/src/data-analytics/data-analytics.service.ts index 3f128a8f4e..fd7017f338 100644 --- a/services/workflows-service/src/data-analytics/data-analytics.service.ts +++ b/services/workflows-service/src/data-analytics/data-analytics.service.ts @@ -278,6 +278,7 @@ export class DataAnalyticsService { Prisma.sql`"transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( `${timeAmount} ${timeUnit}`, )}'`, + Prisma.sql`"transactionDate" <= NOW()`, ]; if (!isEmpty(transactionType)) { @@ -396,6 +397,7 @@ export class DataAnalyticsService { AND "transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( `${timeAmount} ${timeUnit}`, )}' + AND "transactionDate" <= NOW() GROUP BY "${Prisma.raw(subjectColumn)}" ) @@ -454,6 +456,7 @@ export class DataAnalyticsService { WHERE "tr"."projectId" = ${projectId} AND "tr"."counterpartyBeneficiaryId" IS NOT NULL + AND "tr"."transactionDate" <= NOW() GROUP BY "tr"."counterpartyBeneficiaryId" ) @@ -492,9 +495,10 @@ export class DataAnalyticsService { Prisma.sql`"tr"."businessId" IS NOT NULL`, // TODO: should we use equation instead of IN clause? Prisma.sql`"tr"."transactionType"::text IN (${Prisma.join(transactionType, ',')})`, - Prisma.sql`"transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( + Prisma.sql`"tr"."transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( `${timeAmount} ${timeUnit}`, )}'`, + Prisma.sql`"tr"."transactionDate" <= NOW()`, ]; if (Array.isArray(paymentMethods.length)) { @@ -516,7 +520,7 @@ export class DataAnalyticsService { switch (havingAggregate) { case AggregateType.COUNT: - havingClause = `${AggregateType.COUNT}(id)`; + havingClause = `${AggregateType.COUNT}("id")`; break; case AggregateType.SUM: havingClause = `${AggregateType.SUM}("tr"."transactionBaseAmount")`; @@ -555,6 +559,7 @@ export class DataAnalyticsService { Prisma.sql`"tr"."paymentMethod"::text ${Prisma.raw(paymentMethod.operator)} ${ paymentMethod.value }`, + Prisma.sql`"transactionDate" <= NOW()`, !!timeAmount && !!timeUnit && Prisma.sql`"tr"."transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( @@ -633,6 +638,7 @@ export class DataAnalyticsService { paymentMethod.value }`, historicalTransactionClause, + Prisma.sql`"transactionDate" <= NOW()`, ]; return await this._executeQuery>( @@ -672,6 +678,7 @@ export class DataAnalyticsService { Prisma.sql`"tr"."projectId" = ${projectId}`, Prisma.sql`"tr"."counterpartyOriginatorId" IS NOT NULL`, Prisma.sql`"cpOriginator"."correlationId" LIKE '%****%'`, + Prisma.sql`"tr"."transactionDate" <= NOW()`, !!timeAmount && !!timeUnit && Prisma.sql`"tr"."transactionDate" >= CURRENT_DATE - INTERVAL '${Prisma.raw( @@ -733,6 +740,7 @@ export class DataAnalyticsService { paymentMethod.value }`, !!customerType && Prisma.sql`b."businessType" = ${customerType}`, + Prisma.sql`"tr"."transactionDate" <= NOW()`, ].filter(Boolean); const sqlQuery = Prisma.sql`WITH tx_by_business AS