diff --git a/.circleci/config.yml b/.circleci/config.yml index 8eb7042..9ea3a2d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -65,6 +65,7 @@ workflows: only: - develop - pm-1127_1 + - ps-466 # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/sql/reports/topcoder/member-payments.sql b/sql/reports/topcoder/member-payments.sql new file mode 100644 index 0000000..d78c43b --- /dev/null +++ b/sql/reports/topcoder/member-payments.sql @@ -0,0 +1,95 @@ +WITH base AS ( + SELECT + DATE(p.created_at) AS payment_created_date, + + -- Payment info + p.payment_id, + w.description AS payment_desc, + + -- Challenge system id + COALESCE( + NULLIF(TRIM(w.external_id), ''), + NULLIF(TRIM(c."id"::text), '') + ) AS challenge_system_id, + + p.payment_status, + w.category::text AS payment_type_desc, + + -- Member info + mem.handle AS member_handle, + NULL::text AS payment_method_desc, + mem."userId" AS member_user_id, + + -- Billing / client info + ba."name" AS billing_account_name, + cl."name" AS customer_name, + cl."codeName" AS sfdc_account_name, + NULL::text AS parent_name, + + -- Challenge dates + DATE(c."createdAt") AS challenge_created_date, + + -- Amount + COALESCE(p.gross_amount, p.total_amount) AS gross_amount + FROM finance.payment p + JOIN finance.winnings w + ON w.winning_id = p.winnings_id + JOIN members.member mem + ON mem."userId"::text = w.winner_id + + LEFT JOIN challenges."Challenge" c + ON c."id" = w.external_id + + LEFT JOIN challenges."ChallengeBilling" cb + ON cb."challengeId" = c."id" + + LEFT JOIN "billing-accounts"."BillingAccount" ba + ON ba."id" = COALESCE( + NULLIF(p.billing_account, '')::int, + NULLIF(cb."billingAccountId", '')::int + ) + + LEFT JOIN "billing-accounts"."Client" cl + ON cl."id" = ba."clientId" + + LEFT JOIN projects.projects proj + ON proj.id = c."projectId"::bigint + + WHERE DATE(p.created_at) >= $1::date + AND DATE(p.created_at) < $2::date +) +SELECT + payment_created_date AS "payment_create_date.date_date", + payment_id AS "payment.payment_id", + payment_desc AS "payment.payment_desc", + COALESCE(challenge_system_id, '-') AS "challenge.challenge_system_id", + payment_status AS "payment.payment_status_desc", + payment_type_desc AS "payment.payment_type_desc", + member_handle AS "payee.handle", + payment_method_desc AS "payee.payment_method_desc", + billing_account_name AS "billing_account_budgets.billing_account_name", + customer_name AS "billing_account_budgets.customer_name", + sfdc_account_name AS "sfdc_account.name", + member_user_id AS "member_profile_basic.user_id", + parent_name AS "sfdc_account.parent_name", + challenge_created_date AS "challenge.create_date", + COALESCE(SUM(gross_amount), 0) AS "user_payment.gross_amount" +FROM base +GROUP BY + payment_created_date, + payment_id, + payment_desc, + challenge_system_id, + payment_status, + payment_type_desc, + member_handle, + payment_method_desc, + billing_account_name, + customer_name, + sfdc_account_name, + member_user_id, + parent_name, + challenge_created_date +ORDER BY + payment_created_date DESC +LIMIT 5000; diff --git a/src/reports/topcoder/topcoder-reports.controller.ts b/src/reports/topcoder/topcoder-reports.controller.ts index 45ba657..8bcb4ee 100644 --- a/src/reports/topcoder/topcoder-reports.controller.ts +++ b/src/reports/topcoder/topcoder-reports.controller.ts @@ -75,6 +75,18 @@ export class TopcoderReportsController { return this.reports.get30DayPayments(); } + @Get("/member-payments") + @ApiOperation({ + summary: + "List of member payments (optional query params: startDate, endDate in YYYY-MM-DD)", + }) + getMemberPayments( + @Query("startDate") startDate?: string, + @Query("endDate") endDate?: string, + ) { + return this.reports.getMemberPayments(startDate, endDate); + } + @Get("/90-day-member-spend") @ApiOperation({ summary: "Total gross amount paid to members in the last 90 days", diff --git a/src/reports/topcoder/topcoder-reports.service.ts b/src/reports/topcoder/topcoder-reports.service.ts index c30b517..634ab09 100644 --- a/src/reports/topcoder/topcoder-reports.service.ts +++ b/src/reports/topcoder/topcoder-reports.service.ts @@ -449,6 +449,58 @@ export class TopcoderReportsService { })); } + async getMemberPayments(startDate?: string, endDate?: string) { + const defaultEnd = new Date(); + const defaultStart = new Date( + defaultEnd.getTime() - 30 * 24 * 60 * 60 * 1000, + ); + + const start = startDate ?? defaultStart.toISOString().slice(0, 10); + const end = endDate ?? defaultEnd.toISOString().slice(0, 10); + + const query = this.sql.load("reports/topcoder/member-payments.sql"); + type MemberPaymentRow = { + "payment_create_date.date_date": Date | string | null; + "payment.payment_id": string | number | null; + "payment.payment_desc": string | null; + "challenge.challenge_system_id": string | null; + "payment.payment_status_desc": string | null; + "payment.payment_type_desc": string | null; + "payee.handle": string | null; + "payee.payment_method_desc": string | null; + "billing_account_budgets.billing_account_name": string | null; + "billing_account_budgets.customer_name": string | null; + "sfdc_account.name": string | null; + "member_profile_basic.user_id": string | number | null; + "sfdc_account.parent_name": string | null; + "challenge.create_date": Date | string | null; + "user_payment.gross_amount": string | number | null; + }; + + const rows = await this.db.query(query, [start, end]); + + return rows.map((row) => ({ + paymentCreateDate: this.normalizeDate( + row["payment_create_date.date_date"], + ), + paymentId: row["payment.payment_id"] ?? null, + paymentDesc: row["payment.payment_desc"] ?? null, + challengeSystemId: row["challenge.challenge_system_id"] ?? null, + paymentStatus: row["payment.payment_status_desc"] ?? null, + paymentType: row["payment.payment_type_desc"] ?? null, + payeeHandle: row["payee.handle"] ?? null, + payeePaymentMethod: row["payee.payment_method_desc"] ?? null, + billingAccountName: + row["billing_account_budgets.billing_account_name"] ?? null, + customerName: row["billing_account_budgets.customer_name"] ?? null, + sfdcAccountName: row["sfdc_account.name"] ?? null, + memberUserId: row["member_profile_basic.user_id"] ?? null, + parentName: row["sfdc_account.parent_name"] ?? null, + challengeCreatedAt: this.normalizeDate(row["challenge.create_date"]), + grossAmount: this.toNullableNumber(row["user_payment.gross_amount"]), + })); + } + async getRegistrantCountries(challengeId: string) { const query = this.sql.load("reports/topcoder/registrant-countries.sql"); const rows = await this.db.query(query, [