Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fcdaa8a
Return engagement details when returning payment info, for wallet-admin
jmgasper Mar 31, 2026
a53e51a
PM-4429 - use metadata from incoming payment to determine if payment …
vas3a Mar 31, 2026
cbd2c7b
PM-4429 - Move getDefaultWinnerCategory call outside the winners map …
vas3a Mar 31, 2026
b2a9fbe
PM-4623: validate wallet-admin engagement payment details flow
jmgasper Apr 1, 2026
30cd3ad
Merge pull request #151 from topcoder-platform/PM-4429_tass-payments
vas3a Apr 1, 2026
7997cc6
Merge pull request #152 from topcoder-platform/PM-4623
jmgasper Apr 1, 2026
e82a3bc
PM-4623: fall back to engagement lookup for payment details
jmgasper Apr 2, 2026
30dc0c6
PM-4657 - Role‑Based Access Control in Wallet Admin
vas3a Apr 2, 2026
c017896
pr feedback
vas3a Apr 2, 2026
7d07f77
Merge pull request #154 from topcoder-platform/PM-4657_taas-admin-fil…
vas3a Apr 2, 2026
4262056
Merge pull request #153 from topcoder-platform/PM-4623-1
jmgasper Apr 2, 2026
aaf73fa
PM-4773 - topgear payments
vas3a Apr 7, 2026
b9610b3
Merge pull request #156 from topcoder-platform/PM-4773_topgear-payments
vas3a Apr 7, 2026
f6a2be8
Migration to ensure TAAS payments
vas3a Apr 8, 2026
6bddfd9
deploy
vas3a Apr 8, 2026
3e2e885
PM-4789 categorize topgear payments
vas3a Apr 8, 2026
79c40a2
Merge branch 'dev' of github.com:topcoder-platform/tc-finance-api int…
vas3a Apr 8, 2026
ef5813c
Additional fields for wallet-admin to show expanded engagement details
jmgasper Apr 9, 2026
b9fe66b
PM-4822: add Payment Creator to wallet export CSV
jmgasper Apr 10, 2026
020fdc2
Merge pull request #158 from topcoder-platform/PM-4822
jmgasper Apr 10, 2026
c31b6f4
Merge pull request #157 from topcoder-platform/PM-4789_payment-migrat…
vas3a Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ workflows:
only:
- dev
- engagements
- PM-4165_allow-editing-credited-payments
- PM-4789_payment-migrations
- 'build-prod':
context: org-global
filters:
Expand Down
1 change: 1 addition & 0 deletions packages/finance-prisma-client/client.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('.') }
1 change: 1 addition & 0 deletions packages/finance-prisma-client/default.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
module.exports = { ...require('#main-entry-point') }
31 changes: 17 additions & 14 deletions packages/finance-prisma-client/edge.js

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions packages/finance-prisma-client/index-browser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file

Object.defineProperty(exports, "__esModule", { value: true });

Expand All @@ -20,12 +21,12 @@ exports.Prisma = Prisma
exports.$Enums = {}

/**
* Prisma Client JS version: 6.18.0
* Query Engine version: 34b5a692b7bd79939a9a2c3ef97d816e749cda2f
* Prisma Client JS version: 6.19.2
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
Prisma.prismaVersion = {
client: "6.18.0",
engine: "34b5a692b7bd79939a9a2c3ef97d816e749cda2f"
client: "6.19.2",
engine: "c2990dca591cba766e3b7ef5d9e8a84796e47ab7"
}

Prisma.PrismaClientKnownRequestError = () => {
Expand Down Expand Up @@ -330,7 +331,6 @@ exports.tax_form_status = exports.$Enums.tax_form_status = {
exports.winnings_type = exports.$Enums.winnings_type = {
PAYMENT: 'PAYMENT',
REWARD: 'REWARD',
ENGAGEMENT: 'ENGAGEMENT',
POINTS: 'POINTS'
};

Expand Down Expand Up @@ -405,6 +405,8 @@ exports.winnings_category = exports.$Enums.winnings_category = {
PROJECT_COPILOT_PAYMENT: 'PROJECT_COPILOT_PAYMENT',
PROJECT_DEPLOYMENT_TASK_PAYMENT: 'PROJECT_DEPLOYMENT_TASK_PAYMENT',
PROJECT_ENHANCEMENTS_PAYMENT: 'PROJECT_ENHANCEMENTS_PAYMENT',
TAAS_PAYMENT: 'TAAS_PAYMENT',
TOPGEAR_PAYMENT: 'TOPGEAR_PAYMENT',
TASK_PAYMENT: 'TASK_PAYMENT',
TASK_REVIEW_PAYMENT: 'TASK_REVIEW_PAYMENT',
TASK_COPILOT_PAYMENT: 'TASK_COPILOT_PAYMENT',
Expand Down
7 changes: 4 additions & 3 deletions packages/finance-prisma-client/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ export const winnings_category: {
PROJECT_COPILOT_PAYMENT: 'PROJECT_COPILOT_PAYMENT',
PROJECT_DEPLOYMENT_TASK_PAYMENT: 'PROJECT_DEPLOYMENT_TASK_PAYMENT',
PROJECT_ENHANCEMENTS_PAYMENT: 'PROJECT_ENHANCEMENTS_PAYMENT',
TAAS_PAYMENT: 'TAAS_PAYMENT',
TOPGEAR_PAYMENT: 'TOPGEAR_PAYMENT',
TASK_PAYMENT: 'TASK_PAYMENT',
TASK_REVIEW_PAYMENT: 'TASK_REVIEW_PAYMENT',
TASK_COPILOT_PAYMENT: 'TASK_COPILOT_PAYMENT',
Expand All @@ -261,7 +263,6 @@ export type winnings_category = (typeof winnings_category)[keyof typeof winnings
export const winnings_type: {
PAYMENT: 'PAYMENT',
REWARD: 'REWARD',
ENGAGEMENT: 'ENGAGEMENT',
POINTS: 'POINTS'
};

Expand Down Expand Up @@ -634,8 +635,8 @@ export namespace Prisma {
export import Exact = $Public.Exact

/**
* Prisma Client JS version: 6.18.0
* Query Engine version: 34b5a692b7bd79939a9a2c3ef97d816e749cda2f
* Prisma Client JS version: 6.19.2
* Query Engine version: c2990dca591cba766e3b7ef5d9e8a84796e47ab7
*/
export type PrismaVersion = {
client: string
Expand Down
39 changes: 19 additions & 20 deletions packages/finance-prisma-client/index.js

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions packages/finance-prisma-client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "prisma-client-e17c36f18681bf015999b8d76d0baf8e6478dbd38d1ad849f2eb1d7e04dc23e9",
"name": "prisma-client-8e6584db420344c104dfb4ed4bc1d325cd5765efdf218c5bc263260e360dc646",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
Expand Down Expand Up @@ -151,7 +151,7 @@
},
"./*": "./*"
},
"version": "6.18.0",
"version": "6.19.2",
"sideEffects": false,
"imports": {
"#wasm-engine-loader": {
Expand Down
4 changes: 2 additions & 2 deletions packages/finance-prisma-client/query_engine_bg.js

Large diffs are not rendered by default.

Binary file modified packages/finance-prisma-client/query_engine_bg.wasm
Binary file not shown.
9 changes: 5 additions & 4 deletions packages/finance-prisma-client/runtime/edge-esm.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions packages/finance-prisma-client/runtime/edge.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/finance-prisma-client/runtime/index-browser.js

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions packages/finance-prisma-client/runtime/library.js

Large diffs are not rendered by default.

25 changes: 13 additions & 12 deletions packages/finance-prisma-client/runtime/react-native.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions packages/finance-prisma-client/runtime/wasm-compiler-edge.js

Large diffs are not rendered by default.

40 changes: 21 additions & 19 deletions packages/finance-prisma-client/runtime/wasm-engine-edge.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/finance-prisma-client/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ model winnings {

@@index([category, created_at(sort: Desc)], map: "idx_winnings_category_created_at")
@@index([created_at(sort: Desc)], map: "idx_winnings_created_at")
@@index([type, created_at(sort: Desc)], map: "idx_winnings_type_created_at")
@@index([winner_id, created_at(sort: Desc)], map: "idx_winnings_winner_created_at")
@@index([winner_id, winning_id], map: "idx_winnings_winner_id_only")
}
Expand Down Expand Up @@ -327,6 +328,8 @@ enum winnings_category {
PROJECT_COPILOT_PAYMENT
PROJECT_DEPLOYMENT_TASK_PAYMENT
PROJECT_ENHANCEMENTS_PAYMENT
TAAS_PAYMENT
TOPGEAR_PAYMENT
TASK_PAYMENT
TASK_REVIEW_PAYMENT
TASK_COPILOT_PAYMENT
Expand All @@ -336,6 +339,5 @@ enum winnings_category {
enum winnings_type {
PAYMENT
REWARD
ENGAGEMENT
POINTS
}
1 change: 1 addition & 0 deletions packages/finance-prisma-client/wasm-edge-light-loader.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm?module')
1 change: 1 addition & 0 deletions packages/finance-prisma-client/wasm-worker-loader.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

/* !!! This is code generated by Prisma. Do not edit directly. !!!
/* eslint-disable */
// biome-ignore-all lint: generated file
export default import('./query_engine_bg.wasm')
29 changes: 16 additions & 13 deletions packages/finance-prisma-client/wasm.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TYPE "winnings_category" ADD VALUE IF NOT EXISTS 'TAAS_PAYMENT';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TYPE "winnings_category" ADD VALUE IF NOT EXISTS 'TOPGEAR_PAYMENT';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Backfill existing winnings rows where the linked challenge metadata marks payment_type=taas.
DO $$
BEGIN
IF to_regclass('challenges."ChallengeMetadata"') IS NOT NULL THEN
UPDATE winnings w
SET category = 'TAAS_PAYMENT'
FROM challenges."ChallengeMetadata" cm
WHERE cm."challengeId" = w.external_id
AND lower(cm.name) = 'payment_type'
AND lower(cm.value) = 'taas'
AND w.type = 'PAYMENT'
AND w.created_at >= TIMESTAMP '2026-04-01 00:00:00'
AND w.category IS DISTINCT FROM 'TAAS_PAYMENT';
END IF;
END;
$$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Backfill payment winnings to TOPGEAR_PAYMENT based on linked payment billing accounts.
UPDATE winnings w
SET category = 'TOPGEAR_PAYMENT'
WHERE w.type = 'PAYMENT'
AND w.created_at >= TIMESTAMP '2026-04-01 00:00:00'
AND w.category IS DISTINCT FROM 'TOPGEAR_PAYMENT'
AND EXISTS (
SELECT 1
FROM payment p
WHERE p.winnings_id = w.winning_id
AND p.billing_account IN ('80000062', '80002800')
);
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ enum winnings_category {
PROJECT_COPILOT_PAYMENT
PROJECT_DEPLOYMENT_TASK_PAYMENT
PROJECT_ENHANCEMENTS_PAYMENT
TAAS_PAYMENT
TOPGEAR_PAYMENT
TASK_PAYMENT
TASK_REVIEW_PAYMENT
TASK_COPILOT_PAYMENT
Expand Down
194 changes: 194 additions & 0 deletions src/api/admin/admin.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
jest.mock('./admin.service', () => ({
AdminService: class {},
}));

jest.mock('../repository/winnings.repo', () => ({
WinningsRepository: class {},
}));

jest.mock('src/shared/topcoder/members.service', () => ({
TopcoderMembersService: class {},
}));

jest.mock('src/shared/access-control', () => ({
AccessControlService: class {},
}));

import { AdminController } from './admin.controller';
import { PaymentStatus } from 'src/dto/payment.dto';
import {
WinningRequestDto,
WinningsCategory,
WinningsType,
} from 'src/dto/winning.dto';

describe('AdminController', () => {
let controller: AdminController;
let winningsRepo: {
searchWinnings: jest.Mock;
};
let tcMembersService: {
getHandlesByUserIds: jest.Mock;
};
let accessControlService: {
applyFilters: jest.Mock;
};

beforeEach(() => {
winningsRepo = {
searchWinnings: jest.fn(),
};
tcMembersService = {
getHandlesByUserIds: jest.fn(),
};
accessControlService = {
applyFilters: jest.fn(),
};

controller = new AdminController(
{} as any,
winningsRepo as any,
tcMembersService as any,
accessControlService as any,
);
});

it('includes the payment creator column for engagement payment exports only', async () => {
accessControlService.applyFilters.mockResolvedValue({
type: WinningsType.PAYMENT,
} satisfies WinningRequestDto);
winningsRepo.searchWinnings
.mockResolvedValueOnce({
data: {
winnings: [
{
id: 'winning-1',
winnerId: '1001',
createdBy: '9001',
origin: 'Topcoder',
category: WinningsCategory.ENGAGEMENT_PAYMENT,
title: 'Engagement payment',
description: 'Monthly engagement payment',
externalId: 'assignment-1',
details: [
{
status: PaymentStatus.OWED,
totalAmount: 1200,
datePaid: new Date('2026-04-09T01:02:03.000Z'),
billingAccount: '80001063',
},
],
paymentStatus: {} as any,
attributes: {},
createdAt: new Date('2026-04-09T01:02:03.000Z'),
updatedAt: new Date('2026-04-09T04:05:06.000Z'),
releaseDate: new Date('2026-04-24T07:08:09.000Z'),
},
{
id: 'winning-2',
winnerId: '1002',
createdBy: '9002',
origin: 'Topcoder',
category: WinningsCategory.ONE_OFF_PAYMENT,
title: 'One-off payment',
description: 'Non-engagement payment',
externalId: 'task-1',
details: [
{
status: PaymentStatus.PAID,
totalAmount: 500,
billingAccount: '80001064',
},
],
paymentStatus: {} as any,
attributes: {},
createdAt: new Date('2026-04-08T01:02:03.000Z'),
updatedAt: new Date('2026-04-08T04:05:06.000Z'),
releaseDate: new Date('2026-04-23T07:08:09.000Z'),
},
],
pagination: {
totalItems: 2,
totalPages: 1,
pageSize: 1000,
currentPage: 1,
},
},
})
.mockResolvedValueOnce({
data: {
winnings: [],
pagination: {
totalItems: 0,
totalPages: 0,
pageSize: 1000,
currentPage: 2,
},
},
});
tcMembersService.getHandlesByUserIds.mockResolvedValue({
'1001': 'winner-one',
'1002': 'winner-two',
'9001': 'creator-handle',
});

const output = await controller.exportWinnings(
{ type: WinningsType.PAYMENT } as WinningRequestDto,
{ id: 'admin-user', roles: ['Payment Admin'] },
);

expect(accessControlService.applyFilters).toHaveBeenCalledWith(
'admin-user',
['Payment Admin'],
{
type: WinningsType.PAYMENT,
limit: undefined,
offset: undefined,
},
);
expect(winningsRepo.searchWinnings).toHaveBeenNthCalledWith(
1,
{
type: WinningsType.PAYMENT,
limit: 1000,
offset: 0,
},
{
includeCount: false,
includePayoutStatus: false,
latestPaymentOnly: true,
},
);
expect(tcMembersService.getHandlesByUserIds).toHaveBeenCalledWith([
'1001',
'1002',
'9001',
]);

const [headerRow, engagementRow, nonEngagementRow] = output
.trim()
.split(/\r?\n/)
.map((line) => line.split(','));

expect(headerRow).toEqual([
'Winnings ID',
'Winner ID',
'Handle',
'Origin',
'Category',
'Title',
'Description',
'External ID',
'Status',
'Total Amount',
'Date Paid',
'Created At',
'Updated At',
'Release Date',
'Payment Creator',
'Billing Account',
]);
expect(engagementRow[14]).toBe('creator-handle');
expect(nonEngagementRow[14]).toBe('');
});
});
Loading
Loading