Skip to content

Commit 2570f5d

Browse files
committed
Remove budget approver for engagements
1 parent 4f6576a commit 2570f5d

5 files changed

Lines changed: 5 additions & 427 deletions

File tree

src/api/admin/admin.service.spec.ts

Lines changed: 0 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ describe('AdminService', () => {
8787
getHandlesByUserIds: jest.Mock;
8888
};
8989
let topcoderChallengesService: {
90-
findChallengeByProjectAndTitle: jest.Mock;
91-
findChallengeIdFromProjectPhases: jest.Mock;
9290
getChallengeById: jest.Mock;
9391
getProjectById: jest.Mock;
9492
};
@@ -138,8 +136,6 @@ describe('AdminService', () => {
138136
}),
139137
};
140138
topcoderChallengesService = {
141-
findChallengeByProjectAndTitle: jest.fn().mockResolvedValue(undefined),
142-
findChallengeIdFromProjectPhases: jest.fn().mockResolvedValue(undefined),
143139
getChallengeById: jest.fn().mockResolvedValue(undefined),
144140
getProjectById: jest.fn().mockResolvedValue(undefined),
145141
};
@@ -155,178 +151,6 @@ describe('AdminService', () => {
155151
);
156152
});
157153

158-
it('returns engagement budgetApproverHandle from challenge approvalApprovedBy', async () => {
159-
prisma.winnings.findFirst.mockResolvedValue({
160-
winning_id: 'winning-1',
161-
category: 'ENGAGEMENT_PAYMENT',
162-
created_by: '654321',
163-
external_id: 'assignment-1',
164-
attributes: {},
165-
});
166-
topcoderEngagementsService.getAssignmentContextById.mockResolvedValue({
167-
assignmentId: 'assignment-1',
168-
challengeId: 'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
169-
engagementId: 'engagement-1',
170-
engagementTitle: 'Software Developer I',
171-
projectId: '100575',
172-
projectName: 'Ai Reviewer Wf testing',
173-
ratePerHour: '15.5',
174-
standardHoursPerWeek: 12,
175-
startDate: '2026-04-30T12:00:00.000Z',
176-
durationMonths: 1,
177-
});
178-
topcoderChallengesService.getChallengeById.mockResolvedValue({
179-
id: 'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
180-
name: 'Software Developer I',
181-
projectId: 100575,
182-
approvalApprovedBy: 'kartik',
183-
});
184-
185-
const result = await service.getWinningPaymentDetails(
186-
'winning-1',
187-
'123456',
188-
['Payment Admin'],
189-
);
190-
191-
expect(topcoderChallengesService.getChallengeById).toHaveBeenCalledWith(
192-
'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
193-
);
194-
expect(
195-
topcoderChallengesService.findChallengeIdFromProjectPhases,
196-
).not.toHaveBeenCalled();
197-
expect(result.data?.engagementDetails?.budgetApproverHandle).toBe('kartik');
198-
});
199-
200-
it('returns engagement budgetApproverHandle via project phase challengeGuid', async () => {
201-
prisma.winnings.findFirst.mockResolvedValue({
202-
winning_id: 'winning-1',
203-
category: 'ENGAGEMENT_PAYMENT',
204-
created_by: '654321',
205-
external_id: 'assignment-1',
206-
attributes: {},
207-
});
208-
topcoderEngagementsService.getAssignmentContextById.mockResolvedValue({
209-
assignmentId: 'assignment-1',
210-
engagementId: 'engagement-1',
211-
engagementTitle: 'Software Developer I',
212-
projectId: '100575',
213-
ratePerHour: '15.5',
214-
standardHoursPerWeek: 12,
215-
startDate: '2026-04-30T12:00:00.000Z',
216-
durationMonths: 1,
217-
});
218-
topcoderChallengesService.findChallengeIdFromProjectPhases.mockResolvedValue(
219-
'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
220-
);
221-
topcoderChallengesService.getChallengeById.mockResolvedValue({
222-
id: 'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
223-
name: 'Software Developer I',
224-
projectId: 100575,
225-
approvalApprovedBy: 'kartik',
226-
});
227-
228-
const result = await service.getWinningPaymentDetails(
229-
'winning-1',
230-
'123456',
231-
['Payment Admin'],
232-
);
233-
234-
expect(
235-
topcoderChallengesService.findChallengeIdFromProjectPhases,
236-
).toHaveBeenCalledWith('100575', 'Software Developer I');
237-
expect(topcoderChallengesService.getChallengeById).toHaveBeenCalledWith(
238-
'da8b0e8b-f56a-419b-9e4e-b8afe06375e2',
239-
);
240-
expect(result.data?.engagementDetails?.budgetApproverHandle).toBe('kartik');
241-
});
242-
243-
it('returns engagement budgetApproverHandle via project and title search', async () => {
244-
prisma.winnings.findFirst.mockResolvedValue({
245-
winning_id: 'winning-1',
246-
category: 'ENGAGEMENT_PAYMENT',
247-
created_by: '654321',
248-
external_id: 'assignment-1',
249-
attributes: {},
250-
});
251-
topcoderEngagementsService.getAssignmentContextById.mockResolvedValue({
252-
assignmentId: 'assignment-1',
253-
engagementId: 'engagement-1',
254-
engagementTitle: 'Software Developer I',
255-
projectId: '100575',
256-
projectName: 'Ai Reviewer Wf testing',
257-
ratePerHour: '15.5',
258-
standardHoursPerWeek: 12,
259-
startDate: '2026-04-30T12:00:00.000Z',
260-
durationMonths: 1,
261-
});
262-
topcoderChallengesService.findChallengeByProjectAndTitle.mockResolvedValue({
263-
id: 'challenge-uuid-1',
264-
name: 'Software Developer I',
265-
projectId: 100575,
266-
approvalApprovedBy: 'kartik',
267-
});
268-
topcoderChallengesService.getChallengeById.mockResolvedValue({
269-
id: 'challenge-uuid-1',
270-
name: 'Software Developer I',
271-
projectId: 100575,
272-
approvalApprovedBy: 'kartik',
273-
});
274-
275-
const result = await service.getWinningPaymentDetails(
276-
'winning-1',
277-
'123456',
278-
['Payment Admin'],
279-
);
280-
281-
expect(
282-
topcoderChallengesService.findChallengeByProjectAndTitle,
283-
).toHaveBeenCalledWith('100575', 'Software Developer I');
284-
expect(topcoderChallengesService.getChallengeById).toHaveBeenCalledWith(
285-
'challenge-uuid-1',
286-
);
287-
expect(result.data?.engagementDetails?.budgetApproverHandle).toBe('kartik');
288-
});
289-
290-
it('returns engagement budgetApproverHandle from winnings attributes challengeId', async () => {
291-
prisma.winnings.findFirst.mockResolvedValue({
292-
winning_id: 'winning-1',
293-
category: 'ENGAGEMENT_PAYMENT',
294-
created_by: '654321',
295-
external_id: 'assignment-1',
296-
attributes: {
297-
challengeId: 'challenge-uuid-1',
298-
},
299-
});
300-
topcoderEngagementsService.getAssignmentContextById.mockResolvedValue({
301-
assignmentId: 'assignment-1',
302-
engagementId: 'engagement-1',
303-
engagementTitle: 'May 19 pvt eng',
304-
projectId: '100575',
305-
projectName: 'Ai Reviewer Wf testing',
306-
ratePerHour: '2.99',
307-
standardHoursPerWeek: 11,
308-
startDate: '2026-05-23T12:00:00.000Z',
309-
durationMonths: 2,
310-
});
311-
topcoderChallengesService.getChallengeById.mockResolvedValue({
312-
id: 'challenge-uuid-1',
313-
name: 'Engagement challenge',
314-
projectId: 100575,
315-
approvalApprovedBy: 'kartik',
316-
});
317-
318-
const result = await service.getWinningPaymentDetails(
319-
'winning-1',
320-
'123456',
321-
['Payment Admin'],
322-
);
323-
324-
expect(topcoderChallengesService.getChallengeById).toHaveBeenCalledWith(
325-
'challenge-uuid-1',
326-
);
327-
expect(result.data?.engagementDetails?.budgetApproverHandle).toBe('kartik');
328-
});
329-
330154
it('returns paymentApproverHandle for engagement payments from audit trail', async () => {
331155
prisma.winnings.findFirst.mockResolvedValue({
332156
winning_id: 'winning-1',

src/api/admin/admin.service.ts

Lines changed: 5 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -127,44 +127,6 @@ export class AdminService {
127127
return Number.isFinite(parsedValue) ? parsedValue : undefined;
128128
}
129129

130-
private getWinningChallengeId(
131-
winning: Awaited<ReturnType<AdminService['getWinningById']>>,
132-
assignmentContext?: { challengeId?: string | null },
133-
): string | undefined {
134-
const contextChallengeId = assignmentContext?.challengeId;
135-
136-
if (contextChallengeId !== undefined && contextChallengeId !== null) {
137-
const normalizedContextChallengeId = String(contextChallengeId).trim();
138-
139-
if (normalizedContextChallengeId) {
140-
return normalizedContextChallengeId;
141-
}
142-
}
143-
144-
if (
145-
!winning?.attributes ||
146-
typeof winning.attributes !== 'object' ||
147-
Array.isArray(winning.attributes)
148-
) {
149-
return undefined;
150-
}
151-
152-
const attributes = winning.attributes as Record<string, unknown>;
153-
const challengeId = attributes.challengeId ?? attributes.challengeGuid;
154-
155-
if (typeof challengeId === 'string') {
156-
const normalizedChallengeId = challengeId.trim();
157-
158-
return normalizedChallengeId || undefined;
159-
}
160-
161-
if (typeof challengeId === 'number' && Number.isFinite(challengeId)) {
162-
return String(challengeId);
163-
}
164-
165-
return undefined;
166-
}
167-
168130
private getWinningAssignmentId(
169131
winning: Awaited<ReturnType<AdminService['getWinningById']>>,
170132
): string | undefined {
@@ -1214,68 +1176,10 @@ export class AdminService {
12141176
return result;
12151177
}
12161178

1217-
/**
1218-
* Resolves the engagement budget approver from the linked challenge's
1219-
* `approvalApprovedBy` field (`GET /v6/challenges/{challengeId}`).
1220-
*/
1221-
private async resolveEngagementBudgetApproverHandle(
1222-
winning: Awaited<ReturnType<AdminService['getWinningById']>>,
1223-
engagementLookup?: {
1224-
challengeId?: string | null;
1225-
engagementTitle?: string;
1226-
projectId?: string;
1227-
},
1228-
): Promise<string | undefined> {
1229-
let challengeId = this.getWinningChallengeId(winning, engagementLookup);
1230-
1231-
if (
1232-
!challengeId &&
1233-
engagementLookup?.projectId &&
1234-
engagementLookup?.engagementTitle
1235-
) {
1236-
challengeId =
1237-
await this.topcoderChallengesService.findChallengeIdFromProjectPhases(
1238-
engagementLookup.projectId,
1239-
engagementLookup.engagementTitle,
1240-
);
1241-
}
1242-
1243-
if (
1244-
!challengeId &&
1245-
engagementLookup?.projectId &&
1246-
engagementLookup?.engagementTitle
1247-
) {
1248-
const challenge =
1249-
await this.topcoderChallengesService.findChallengeByProjectAndTitle(
1250-
engagementLookup.projectId,
1251-
engagementLookup.engagementTitle,
1252-
);
1253-
challengeId = challenge?.id;
1254-
}
1255-
1256-
if (!challengeId) {
1257-
return undefined;
1258-
}
1259-
1260-
try {
1261-
const challenge =
1262-
await this.topcoderChallengesService.getChallengeById(challengeId);
1263-
1264-
if (!challenge?.approvalApprovedBy) {
1265-
return undefined;
1266-
}
1267-
1268-
return this.getPaymentCreatorHandle(challenge.approvalApprovedBy);
1269-
} catch {
1270-
return undefined;
1271-
}
1272-
}
1273-
12741179
/**
12751180
* Resolves the payment approver handle from the audit entry that moved the
12761181
* winning from `ON_HOLD_ADMIN` to `OWED`.
12771182
*/
1278-
12791183
private async resolvePaymentApproverHandleFromAudit(
12801184
winningsId: string,
12811185
): Promise<string | undefined> {
@@ -1401,12 +1305,6 @@ export class AdminService {
14011305
await this.topcoderEngagementsService.getAssignmentContextById(
14021306
assignmentLookupId,
14031307
);
1404-
const budgetApproverHandle =
1405-
await this.resolveEngagementBudgetApproverHandle(
1406-
winning,
1407-
assignmentContext,
1408-
);
1409-
14101308
result.data.engagementDetails = {
14111309
assignmentId: assignmentContext.assignmentId,
14121310
engagementId: assignmentContext.engagementId,
@@ -1422,7 +1320,6 @@ export class AdminService {
14221320
assignmentContext.standardHoursPerWeek ?? undefined,
14231321
otherRemarks: assignmentContext.otherRemarks ?? undefined,
14241322
paymentApproverHandle,
1425-
budgetApproverHandle,
14261323
};
14271324

14281325
return result;
@@ -1447,21 +1344,13 @@ export class AdminService {
14471344
assignmentId,
14481345
);
14491346

1450-
const builtEngagementDetails = this.buildEngagementDetailsFromEngagement(
1451-
engagement,
1452-
assignment,
1453-
assignmentId,
1454-
);
1455-
const budgetApproverHandle =
1456-
await this.resolveEngagementBudgetApproverHandle(winning, {
1457-
engagementTitle: builtEngagementDetails.engagementTitle,
1458-
projectId: builtEngagementDetails.projectId,
1459-
});
1460-
14611347
result.data.engagementDetails = {
1462-
...builtEngagementDetails,
1348+
...this.buildEngagementDetailsFromEngagement(
1349+
engagement,
1350+
assignment,
1351+
assignmentId,
1352+
),
14631353
paymentApproverHandle,
1464-
budgetApproverHandle,
14651354
};
14661355
} catch (error) {
14671356
this.logger.warn(

src/api/admin/dto/payment-details.dto.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ export class PaymentEngagementDetailsDto {
6767
example: 'payment_approver_handle',
6868
})
6969
paymentApproverHandle?: string;
70-
71-
@ApiPropertyOptional({
72-
description:
73-
'The Topcoder handle of the user who approved the engagement budget (from challenge `approvalApprovedBy`)',
74-
example: 'budget_approver_handle',
75-
})
76-
budgetApproverHandle?: string;
7770
}
7871

7972
export class PaymentWorkLogDto {

0 commit comments

Comments
 (0)