From d5e78f15e8c74519f11770dc7d4a95d1c87e98ef Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 1 Sep 2025 14:50:37 +0300 Subject: [PATCH 1/3] PM-1110 - sfdc payment report --- package.json | 7 +- pnpm-lock.yaml | 3 + src/app.module.ts | 7 +- src/common/logger.ts | 30 +++++ src/dto/api-response.dto.ts | 30 +++++ src/reports/reports.module.ts | 10 -- src/sfdc-reports/sfdc-reports.controller.ts | 47 +++++++ src/sfdc-reports/sfdc-reports.dto.ts | 112 ++++++++++++++++ src/sfdc-reports/sfdc-reports.module.ts | 10 ++ src/sfdc-reports/sfdc-reports.service.ts | 124 ++++++++++++++++++ .../topgear-reports.controller.ts} | 14 +- src/topgear-reports/topgear-reports.module.ts | 10 ++ .../topgear-reports.service.ts} | 2 +- 13 files changed, 383 insertions(+), 23 deletions(-) create mode 100644 src/common/logger.ts create mode 100644 src/dto/api-response.dto.ts delete mode 100644 src/reports/reports.module.ts create mode 100644 src/sfdc-reports/sfdc-reports.controller.ts create mode 100644 src/sfdc-reports/sfdc-reports.dto.ts create mode 100644 src/sfdc-reports/sfdc-reports.module.ts create mode 100644 src/sfdc-reports/sfdc-reports.service.ts rename src/{reports/reports.controller.ts => topgear-reports/topgear-reports.controller.ts} (82%) create mode 100644 src/topgear-reports/topgear-reports.module.ts rename src/{reports/reports.service.ts => topgear-reports/topgear-reports.service.ts} (96%) diff --git a/package.json b/package.json index 5ced3df..3889d92 100644 --- a/package.json +++ b/package.json @@ -12,21 +12,22 @@ "lint": "eslint \"src/**/*.ts\" --fix" }, "dependencies": { + "@nestjs/cli": "^10.3.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^11.2.0", - "@nestjs/cli": "^10.3.0", "@prisma/client": "^5.17.0", + "@types/express": "^4.17.21", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "date-fns": "^3.6.0", + "json-stringify-safe": "^5.0.1", "pg": "^8.11.5", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", - "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4", - "@types/express": "^4.17.21" + "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 540c2ce..e88c990 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: date-fns: specifier: ^3.6.0 version: 3.6.0 + json-stringify-safe: + specifier: ^5.0.1 + version: 5.0.1 pg: specifier: ^8.11.5 version: 8.16.3 diff --git a/src/app.module.ts b/src/app.module.ts index c9d8aa7..a946cd2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,15 +1,18 @@ import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"; import { ConfigModule } from "@nestjs/config"; -import { ReportsModule } from "./reports/reports.module"; import { DbModule } from "./db/db.module"; import { AuthMiddleware } from "./auth/auth.middleware"; import { HealthModule } from "./health/health.module"; +import { TopgearReportsModule } from "./topgear-reports/topgear-reports.module"; +import { SfdcReportsModule } from "./sfdc-reports/sfdc-reports.module"; + @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), DbModule, - ReportsModule, + TopgearReportsModule, + SfdcReportsModule, HealthModule, ], }) diff --git a/src/common/logger.ts b/src/common/logger.ts new file mode 100644 index 0000000..20d8e9d --- /dev/null +++ b/src/common/logger.ts @@ -0,0 +1,30 @@ +import { Logger as NestLogger } from "@nestjs/common"; +import * as stringify from "json-stringify-safe"; +console.log('here', stringify); + + +export class Logger extends NestLogger { + log(...messages: any[]): void { + super.log(this.formatMessages(messages)); + } + + debug(...messages: any[]): void { + super.debug(this.formatMessages(messages)); + } + + info(...messages: any[]): void { + super.log(this.formatMessages(messages)); // NestJS doesn't have a dedicated `info` method, so we use `log`. + } + + error(...messages: any[]): void { + super.error(this.formatMessages(messages)); + } + + private formatMessages(messages: any[]): string { + return [...messages] + .map((msg) => + typeof msg === "object" ? stringify(msg, null, 2) : String(msg), + ) + .join(" "); + } +} diff --git a/src/dto/api-response.dto.ts b/src/dto/api-response.dto.ts new file mode 100644 index 0000000..fce845f --- /dev/null +++ b/src/dto/api-response.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export enum ResponseStatusType { + SUCCESS = "success", + ERROR = "error", +} + +export class Error { + code: number; + message: string; +} + +export class ResponseDto { + @ApiProperty({ + description: "Type of the response", + enum: ResponseStatusType, + example: ResponseStatusType.SUCCESS, + }) + status: ResponseStatusType; + + @ApiProperty({ + description: "The response data", + }) + data: T; + + @ApiProperty({ + description: "The error message", + }) + error: Error; +} diff --git a/src/reports/reports.module.ts b/src/reports/reports.module.ts deleted file mode 100644 index 8b1968b..0000000 --- a/src/reports/reports.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from "@nestjs/common"; -import { ReportsController } from "./reports.controller"; -import { ReportsService } from "./reports.service"; -import { SqlLoaderService } from "../common/sql-loader.service"; - -@Module({ - controllers: [ReportsController], - providers: [ReportsService, SqlLoaderService], -}) -export class ReportsModule {} diff --git a/src/sfdc-reports/sfdc-reports.controller.ts b/src/sfdc-reports/sfdc-reports.controller.ts new file mode 100644 index 0000000..a6b685b --- /dev/null +++ b/src/sfdc-reports/sfdc-reports.controller.ts @@ -0,0 +1,47 @@ +import { Controller, Get, Query, UseGuards } from "@nestjs/common"; + +import { + ApiBearerAuth, + ApiOperation, + ApiQuery, + ApiResponse, + ApiTags, +} from "@nestjs/swagger"; + +import { PermissionsGuard } from "../auth/guards/permissions.guard"; +import { Scopes } from "../auth/decorators/scopes.decorator"; +import { Scopes as AppScopes } from "../app-constants"; + +import { SfdcReportsService } from "./sfdc-reports.service"; +import { + PaymentsReportQueryDto, + PaymentsReportResponse, +} from "./sfdc-reports.dto"; +import { ResponseDto } from "src/dto/api-response.dto"; + +@ApiTags("Sfdc Reports") +@Controller("/sfdc") +export class SfdcReportsController { + constructor(private readonly reportsService: SfdcReportsService) {} + + @Get("/payments") + // @UseGuards(PermissionsGuard) + // @Scopes(AppScopes.AllReports) + @ApiBearerAuth() + @ApiOperation({ + summary: "Export search winnings result in csv file format", + description: "Roles: Payment Admin, Payment Editor, Payment Viewer", + }) + @ApiQuery({ + type: PaymentsReportQueryDto, + }) + @ApiResponse({ + status: 200, + description: "Export winnings successfully.", + type: ResponseDto, + }) + async getPaymentsReport(@Query() query: PaymentsReportQueryDto) { + const report = await this.reportsService.getPaymentsReport(query); + return report; + } +} diff --git a/src/sfdc-reports/sfdc-reports.dto.ts b/src/sfdc-reports/sfdc-reports.dto.ts new file mode 100644 index 0000000..4b11774 --- /dev/null +++ b/src/sfdc-reports/sfdc-reports.dto.ts @@ -0,0 +1,112 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Transform } from "class-transformer"; +import { + IsOptional, + IsString, + IsNotEmpty, + IsNumber, + IsDateString, +} from "class-validator"; + +const transformArray = ({ value }: { value: string }) => + Array.isArray(value) ? value : [value]; + +export class PaymentsReportQueryDto { + @ApiProperty({ + required: false, + description: + "List of billing account IDs associated with the payments to retrieve", + example: ["80001012"], + }) + @IsOptional() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @Transform(transformArray) + billingAccountIds?: string[]; + + @ApiProperty({ + required: false, + description: "Challenge name to search for", + example: ["Task Payment for member"], + }) + @IsOptional() + @IsString() + @IsNotEmpty() + challengeName?: string; + + @ApiProperty({ + required: false, + description: "List of challenge IDs", + example: ["e74c3e37-73c9-474e-a838-a38dd4738906"], + }) + @IsOptional() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @Transform(transformArray) + challengeIds?: string[]; + + @ApiProperty({ + required: false, + description: "Start date for the report query in ISO format", + example: "2023-01-01T00:00:00.000Z", + }) + @IsOptional() + @IsDateString() + startDate?: Date; + + @ApiProperty({ + required: false, + description: "End date for the report query in ISO format", + example: "2023-01-31T23:59:59.000Z", + }) + @IsOptional() + @IsDateString() + endDate?: Date; + + @ApiProperty({ + required: false, + description: "List of user handles", + example: ["user_01", "user_02"], + }) + @IsOptional() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @Transform(transformArray) + handles?: string[]; + + @ApiProperty({ + required: false, + description: "Minimum payment amount for filtering the report", + example: 100, + }) + @IsOptional() + @IsNumber() + @Transform(({ value }) => parseFloat(value)) + minPaymentAmount?: number; + + @ApiProperty({ + required: false, + description: "Maximum payment amount for filtering the report", + example: 1000, + }) + @IsOptional() + @IsNumber() + @Transform(({ value }) => parseFloat(value)) + maxPaymentAmount?: number; +} + +export class PaymentsReportResponse { + billingAccountId: string; + challengeName: string; + challengeId: string; + paymentDate: string; + paymentId: string; + paymentStatus: string; + winnerId: string; + winnerHandle: string; + winnerFirstName: string; + winnerLastName: string; + isTask: boolean; + challengeFee: number; + paymentAmount: number; +} diff --git a/src/sfdc-reports/sfdc-reports.module.ts b/src/sfdc-reports/sfdc-reports.module.ts new file mode 100644 index 0000000..9466f0c --- /dev/null +++ b/src/sfdc-reports/sfdc-reports.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { SfdcReportsController } from "./sfdc-reports.controller"; +import { SfdcReportsService } from "./sfdc-reports.service"; +import { SqlLoaderService } from "../common/sql-loader.service"; + +@Module({ + controllers: [SfdcReportsController], + providers: [SfdcReportsService, SqlLoaderService], +}) +export class SfdcReportsModule {} diff --git a/src/sfdc-reports/sfdc-reports.service.ts b/src/sfdc-reports/sfdc-reports.service.ts new file mode 100644 index 0000000..a3d4fee --- /dev/null +++ b/src/sfdc-reports/sfdc-reports.service.ts @@ -0,0 +1,124 @@ +import { Injectable } from "@nestjs/common"; +import { DbService } from "../db/db.service"; +import { Logger } from "src/common/logger"; +import { PaymentsReportQueryDto } from "./sfdc-reports.dto"; + +@Injectable() +export class SfdcReportsService { + private readonly logger = new Logger(SfdcReportsService.name); + + constructor( + private readonly db: DbService, + ) {} + + async getPaymentsReport(filters: PaymentsReportQueryDto) { + this.logger.debug("Starting getPaymentsReport with filters:", filters); + + const params: any[] = []; + const whereClauses: string[] = []; + + // Filter by billing accounts + if (filters.billingAccountIds) { + params.push(filters.billingAccountIds); + whereClauses.push(`p.billing_account = ANY($${params.length})`); + } + + // Filter by challenge ids + if (filters.challengeIds) { + params.push(filters.challengeIds); + whereClauses.push(`c.id = ANY($${params.length})`); + } + + // Filter by handles + if (filters.handles) { + params.push(filters.handles); + whereClauses.push(` + w.winner_id IN ( + SELECT m."userId"::text + FROM members.member m + WHERE m.handle = ANY($${params.length}) + ) + `); + } + + // Filter by challenge name + if (filters.challengeName) { + params.push(`%${filters.challengeName}%`); + whereClauses.push(` + w.external_id IN ( + SELECT c.id + FROM challenges."Challenge" c + WHERE c.name ILIKE $${params.length} + ) + `); + } + + if (filters.startDate) { + params.push(filters.startDate); + whereClauses.push(`p.created_at >= $${params.length}::timestamptz`); + } + + if (filters.endDate) { + params.push(filters.endDate); + whereClauses.push(`p.created_at <= $${params.length}::timestamptz`); + } + + if (filters.minPaymentAmount) { + params.push(filters.minPaymentAmount); + whereClauses.push(`p.total_amount >= $${params.length}::numeric`); + } + + if (filters.maxPaymentAmount) { + params.push(filters.maxPaymentAmount); + whereClauses.push(`p.total_amount <= $${params.length}::numeric`); + } + + // Final WHERE clause + const whereSQL = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""; + + const payments = await this.db.query<{ + billingAccountId: string, + challengeName: string, + challengeId: string, + paymentDate: Date, + paymentId: string, + paymentStatus: string, + winnerId: string, + winnerHandle: string, + winnerFirstName: string, + winnerLastName: string, + isTask: boolean, + challengeFee: number, + paymentAmount: number, + }>( + ` + SELECT + p.payment_id as "paymentId", + p.created_at as "paymentDate", + p.billing_account as "billingAccountId", + p.payment_status as "paymentStatus", + p.challenge_fee as "challengeFee", + p.total_amount as "paymentAmount", + w.external_id as "challengeId", + w.category, + (w.category = 'TASK_PAYMENT') AS "isTask", + c.name AS "challengeName", + m.handle AS "winnerHandle", + m."userId" as "winnerId", + m."firstName" as "winnerFirstName", + m."lastName" as "winnerLastName" + FROM finance.payment p + LEFT JOIN finance.winnings w ON w.winning_id = p.winnings_id + LEFT JOIN challenges."Challenge" c ON c.id = w.external_id + LEFT JOIN members.member m ON m."userId" = w.winner_id::bigint + ${whereSQL} + LIMIT 1000 + `, + params as string[], + ); + + this.logger.debug("Mapped payments to the final report format"); + + return payments; + } +} diff --git a/src/reports/reports.controller.ts b/src/topgear-reports/topgear-reports.controller.ts similarity index 82% rename from src/reports/reports.controller.ts rename to src/topgear-reports/topgear-reports.controller.ts index a5e5db6..dab970d 100644 --- a/src/reports/reports.controller.ts +++ b/src/topgear-reports/topgear-reports.controller.ts @@ -11,14 +11,14 @@ import { PermissionsGuard } from "../auth/guards/permissions.guard"; import { Scopes } from "../auth/decorators/scopes.decorator"; import { Scopes as AppScopes } from "../app-constants"; -import { ReportsService } from "./reports.service"; +import { TopgearReportsService } from "./topgear-reports.service"; -@ApiTags("Topcoder Reports") -@Controller("/") -export class ReportsController { - constructor(private readonly reports: ReportsService) {} +@ApiTags("Topgear Reports") +@Controller("/topgear") +export class TopgearReportsController { + constructor(private readonly reports: TopgearReportsService) {} - @Get("topgear/hourly") + @Get("hourly") @UseGuards(PermissionsGuard) @Scopes(AppScopes.AllReports, AppScopes.TopgearHourly) @ApiBearerAuth() @@ -27,7 +27,7 @@ export class ReportsController { return this.reports.getTopgearHourly(); } - @Get("topgear/payments") + @Get("payments") @UseGuards(PermissionsGuard) @Scopes(AppScopes.AllReports, AppScopes.TopgearHourly) @ApiBearerAuth() diff --git a/src/topgear-reports/topgear-reports.module.ts b/src/topgear-reports/topgear-reports.module.ts new file mode 100644 index 0000000..ec3bf99 --- /dev/null +++ b/src/topgear-reports/topgear-reports.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { TopgearReportsController } from "./topgear-reports.controller"; +import { TopgearReportsService } from "./topgear-reports.service"; +import { SqlLoaderService } from "../common/sql-loader.service"; + +@Module({ + controllers: [TopgearReportsController], + providers: [TopgearReportsService, SqlLoaderService], +}) +export class TopgearReportsModule {} diff --git a/src/reports/reports.service.ts b/src/topgear-reports/topgear-reports.service.ts similarity index 96% rename from src/reports/reports.service.ts rename to src/topgear-reports/topgear-reports.service.ts index e99cfe9..51eec40 100644 --- a/src/reports/reports.service.ts +++ b/src/topgear-reports/topgear-reports.service.ts @@ -8,7 +8,7 @@ import { } from "../common/validation.util"; @Injectable() -export class ReportsService { +export class TopgearReportsService { constructor( private readonly db: DbService, private readonly sql: SqlLoaderService, From d494b7dd93ffac39dfbe1171af26136e30199e9f Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 1 Sep 2025 14:51:36 +0300 Subject: [PATCH 2/3] re-enable auth guards --- src/sfdc-reports/sfdc-reports.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfdc-reports/sfdc-reports.controller.ts b/src/sfdc-reports/sfdc-reports.controller.ts index a6b685b..7f2f778 100644 --- a/src/sfdc-reports/sfdc-reports.controller.ts +++ b/src/sfdc-reports/sfdc-reports.controller.ts @@ -25,8 +25,8 @@ export class SfdcReportsController { constructor(private readonly reportsService: SfdcReportsService) {} @Get("/payments") - // @UseGuards(PermissionsGuard) - // @Scopes(AppScopes.AllReports) + @UseGuards(PermissionsGuard) + @Scopes(AppScopes.AllReports) @ApiBearerAuth() @ApiOperation({ summary: "Export search winnings result in csv file format", From c6824864f06a53564fcd154e86299033de15fc25 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 2 Sep 2025 09:46:21 +0300 Subject: [PATCH 3/3] Use sql file to report sfdc payments --- sql/reports/sfdc/payments.sql | 41 ++++++++++ src/sfdc-reports/sfdc-reports.service.ts | 99 ++++-------------------- 2 files changed, 55 insertions(+), 85 deletions(-) create mode 100644 sql/reports/sfdc/payments.sql diff --git a/sql/reports/sfdc/payments.sql b/sql/reports/sfdc/payments.sql new file mode 100644 index 0000000..6b72ea4 --- /dev/null +++ b/sql/reports/sfdc/payments.sql @@ -0,0 +1,41 @@ +SELECT + p.payment_id as "paymentId", + p.created_at as "paymentDate", + p.billing_account as "billingAccountId", + p.payment_status as "paymentStatus", + p.challenge_fee as "challengeFee", + p.total_amount as "paymentAmount", + w.external_id as "challengeId", + w.category, + (w.category = 'TASK_PAYMENT') AS "isTask", + c.name AS "challengeName", + m.handle AS "winnerHandle", + m."userId" as "winnerId", + m."firstName" as "winnerFirstName", + m."lastName" as "winnerLastName" +FROM finance.payment p +LEFT JOIN finance.winnings w + ON w.winning_id = p.winnings_id +LEFT JOIN challenges."Challenge" c + ON c.id = w.external_id +LEFT JOIN members.member m + ON m."userId" = w.winner_id::bigint +WHERE + ($1::text[] IS NULL OR p.billing_account = ANY($1::text[])) + AND ($2::text[] IS NULL OR c.id = ANY($2::text[])) + AND ($3::text[] IS NULL OR w.winner_id::text IN ( + SELECT m2."userId"::text + FROM members.member m2 + WHERE m2.handle = ANY($3::text[]) + )) + AND ($4::text IS NULL OR w.external_id IN ( + SELECT c2.id + FROM challenges."Challenge" c2 + WHERE c2.name ILIKE '%' || $4 || '%' + )) + AND ($5::timestamptz IS NULL OR p.created_at >= $5::timestamptz) + AND ($6::timestamptz IS NULL OR p.created_at <= $6::timestamptz) + AND ($7::numeric IS NULL OR p.total_amount >= $7::numeric) + AND ($8::numeric IS NULL OR p.total_amount <= $8::numeric) +ORDER BY p.created_at DESC +LIMIT 1000; diff --git a/src/sfdc-reports/sfdc-reports.service.ts b/src/sfdc-reports/sfdc-reports.service.ts index a3d4fee..f2724ee 100644 --- a/src/sfdc-reports/sfdc-reports.service.ts +++ b/src/sfdc-reports/sfdc-reports.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common"; import { DbService } from "../db/db.service"; import { Logger } from "src/common/logger"; import { PaymentsReportQueryDto } from "./sfdc-reports.dto"; +import { SqlLoaderService } from "src/common/sql-loader.service"; @Injectable() export class SfdcReportsService { @@ -9,72 +10,13 @@ export class SfdcReportsService { constructor( private readonly db: DbService, + private readonly sql: SqlLoaderService, ) {} async getPaymentsReport(filters: PaymentsReportQueryDto) { this.logger.debug("Starting getPaymentsReport with filters:", filters); - const params: any[] = []; - const whereClauses: string[] = []; - - // Filter by billing accounts - if (filters.billingAccountIds) { - params.push(filters.billingAccountIds); - whereClauses.push(`p.billing_account = ANY($${params.length})`); - } - - // Filter by challenge ids - if (filters.challengeIds) { - params.push(filters.challengeIds); - whereClauses.push(`c.id = ANY($${params.length})`); - } - - // Filter by handles - if (filters.handles) { - params.push(filters.handles); - whereClauses.push(` - w.winner_id IN ( - SELECT m."userId"::text - FROM members.member m - WHERE m.handle = ANY($${params.length}) - ) - `); - } - - // Filter by challenge name - if (filters.challengeName) { - params.push(`%${filters.challengeName}%`); - whereClauses.push(` - w.external_id IN ( - SELECT c.id - FROM challenges."Challenge" c - WHERE c.name ILIKE $${params.length} - ) - `); - } - - if (filters.startDate) { - params.push(filters.startDate); - whereClauses.push(`p.created_at >= $${params.length}::timestamptz`); - } - - if (filters.endDate) { - params.push(filters.endDate); - whereClauses.push(`p.created_at <= $${params.length}::timestamptz`); - } - - if (filters.minPaymentAmount) { - params.push(filters.minPaymentAmount); - whereClauses.push(`p.total_amount >= $${params.length}::numeric`); - } - - if (filters.maxPaymentAmount) { - params.push(filters.maxPaymentAmount); - whereClauses.push(`p.total_amount <= $${params.length}::numeric`); - } - - // Final WHERE clause - const whereSQL = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : ""; + const query = this.sql.load("reports/sfdc/payments.sql"); const payments = await this.db.query<{ billingAccountId: string, @@ -91,30 +33,17 @@ export class SfdcReportsService { challengeFee: number, paymentAmount: number, }>( - ` - SELECT - p.payment_id as "paymentId", - p.created_at as "paymentDate", - p.billing_account as "billingAccountId", - p.payment_status as "paymentStatus", - p.challenge_fee as "challengeFee", - p.total_amount as "paymentAmount", - w.external_id as "challengeId", - w.category, - (w.category = 'TASK_PAYMENT') AS "isTask", - c.name AS "challengeName", - m.handle AS "winnerHandle", - m."userId" as "winnerId", - m."firstName" as "winnerFirstName", - m."lastName" as "winnerLastName" - FROM finance.payment p - LEFT JOIN finance.winnings w ON w.winning_id = p.winnings_id - LEFT JOIN challenges."Challenge" c ON c.id = w.external_id - LEFT JOIN members.member m ON m."userId" = w.winner_id::bigint - ${whereSQL} - LIMIT 1000 - `, - params as string[], + query, + [ + filters.billingAccountIds, + filters.challengeIds, + filters.handles, + filters.challengeName, + filters.startDate, + filters.endDate, + filters.minPaymentAmount, + filters.maxPaymentAmount, + ] ); this.logger.debug("Mapped payments to the final report format");