@@ -17,7 +17,11 @@ import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto';
1717import { WinningUpdateRequestDto } from './dto/winnings.dto' ;
1818import { Logger } from 'src/shared/global' ;
1919import { BillingAccountsService } from 'src/shared/topcoder/billing-accounts.service' ;
20- import { TopcoderEngagementsService } from 'src/shared/topcoder/engagements.service' ;
20+ import {
21+ TopcoderEngagementAssignment ,
22+ TopcoderEngagementDetails ,
23+ TopcoderEngagementsService ,
24+ } from 'src/shared/topcoder/engagements.service' ;
2125import { WinningPaymentDetailsDto } from './dto/payment-details.dto' ;
2226
2327/**
@@ -100,11 +104,133 @@ export class AdminService {
100104 private getWinningAssignmentId (
101105 winning : Awaited < ReturnType < AdminService [ 'getWinningById' ] > > ,
102106 ) : string | undefined {
103- return (
104- this . getStringAttribute ( winning ?. attributes ?? null , 'assignmentId' ) ??
105- winning ?. external_id ??
106- undefined
107- ) ;
107+ if (
108+ ! winning ?. attributes ||
109+ typeof winning . attributes !== 'object' ||
110+ Array . isArray ( winning . attributes )
111+ ) {
112+ return undefined ;
113+ }
114+
115+ const assignmentId = ( winning . attributes as Record < string , unknown > )
116+ . assignmentId ;
117+
118+ if ( typeof assignmentId === 'string' ) {
119+ const normalizedAssignmentId = assignmentId . trim ( ) ;
120+ return normalizedAssignmentId || undefined ;
121+ }
122+
123+ if ( typeof assignmentId === 'number' && Number . isFinite ( assignmentId ) ) {
124+ return String ( assignmentId ) ;
125+ }
126+
127+ return undefined ;
128+ }
129+
130+ /**
131+ * Finds the engagement assignment that best matches the current winning.
132+ *
133+ * @param assignments assignments returned by the engagements API.
134+ * @param winnerId Topcoder member identifier stored on the winning.
135+ * @param assignmentId optional assignment identifier captured on the winning.
136+ * @returns the matched assignment, or the only assignment when the engagement
137+ * has a single assignee.
138+ * @throws This helper does not throw.
139+ */
140+ private findMatchingEngagementAssignment (
141+ assignments : TopcoderEngagementAssignment [ ] | null | undefined ,
142+ winnerId : string ,
143+ assignmentId ?: string ,
144+ ) : TopcoderEngagementAssignment | undefined {
145+ if ( ! Array . isArray ( assignments ) || assignments . length === 0 ) {
146+ return undefined ;
147+ }
148+
149+ if ( assignmentId ) {
150+ const assignmentMatch = assignments . find ( ( item ) => {
151+ if ( item . id === undefined || item . id === null ) {
152+ return false ;
153+ }
154+
155+ return String ( item . id ) . trim ( ) === assignmentId ;
156+ } ) ;
157+
158+ if ( assignmentMatch ) {
159+ return assignmentMatch ;
160+ }
161+ }
162+
163+ const winnerMatch = assignments . find ( ( item ) => {
164+ if ( item . memberId === undefined || item . memberId === null ) {
165+ return false ;
166+ }
167+
168+ return String ( item . memberId ) . trim ( ) === winnerId ;
169+ } ) ;
170+
171+ if ( winnerMatch ) {
172+ return winnerMatch ;
173+ }
174+
175+ return assignments . length === 1 ? assignments [ 0 ] : undefined ;
176+ }
177+
178+ /**
179+ * Builds wallet-admin engagement details from an engagement record.
180+ *
181+ * @param engagement engagement payload returned by the engagements API.
182+ * @param assignment matched assignment for the winning, when available.
183+ * @param assignmentId optional assignment identifier captured on the winning.
184+ * @returns engagement details shaped for the payment details response.
185+ * @throws This helper does not throw.
186+ */
187+ private buildEngagementDetailsFromEngagement (
188+ engagement : TopcoderEngagementDetails ,
189+ assignment ?: TopcoderEngagementAssignment ,
190+ assignmentId ?: string ,
191+ ) : WinningPaymentDetailsDto [ 'engagementDetails' ] {
192+ const durationMonths =
193+ assignment ?. durationMonths !== undefined &&
194+ assignment . durationMonths !== null
195+ ? Number ( assignment . durationMonths )
196+ : undefined ;
197+ const standardHoursPerWeek =
198+ assignment ?. standardHoursPerWeek !== undefined &&
199+ assignment . standardHoursPerWeek !== null
200+ ? Number ( assignment . standardHoursPerWeek )
201+ : undefined ;
202+ const projectId = engagement . projectId ?? engagement . project ?. id ;
203+ const projectName =
204+ ( engagement . projectName ?? engagement . project ?. name ) ?. trim ( ) ?? undefined ;
205+ const engagementId =
206+ engagement . id !== undefined && engagement . id !== null
207+ ? String ( engagement . id ) . trim ( ) || undefined
208+ : undefined ;
209+
210+ return {
211+ assignmentId :
212+ assignment ?. id !== undefined && assignment . id !== null
213+ ? String ( assignment . id ) . trim ( ) || assignmentId
214+ : assignmentId ,
215+ engagementId,
216+ projectId :
217+ projectId !== undefined && projectId !== null
218+ ? String ( projectId ) . trim ( ) || undefined
219+ : undefined ,
220+ projectName,
221+ engagementTitle : engagement . title ?. trim ( ) ?? undefined ,
222+ billingStartDate : assignment ?. startDate
223+ ? new Date ( assignment . startDate )
224+ : undefined ,
225+ durationMonths : Number . isFinite ( durationMonths )
226+ ? durationMonths
227+ : undefined ,
228+ ratePerHour : assignment ?. ratePerHour ?. trim ( ) ?? undefined ,
229+ standardHoursPerWeek : Number . isFinite ( standardHoursPerWeek )
230+ ? standardHoursPerWeek
231+ : undefined ,
232+ otherRemarks : assignment ?. otherRemarks ?. trim ( ) ?? undefined ,
233+ } ;
108234 }
109235
110236 private getPaymentsByWinningsId ( winningsId : string , paymentId ?: string ) {
@@ -572,36 +698,70 @@ export class AdminService {
572698 } ;
573699
574700 const assignmentId = this . getWinningAssignmentId ( winning ) ;
701+ const externalId =
702+ typeof winning . external_id === 'string'
703+ ? winning . external_id . trim ( ) || undefined
704+ : undefined ;
705+ const assignmentLookupId = assignmentId ?? externalId ;
575706 const isEngagementPayment = winning . category === 'ENGAGEMENT_PAYMENT' ;
576707
577- if ( ! isEngagementPayment || ! assignmentId ) {
708+ if ( ! isEngagementPayment ) {
578709 return result ;
579710 }
580711
581- try {
582- const assignmentContext =
583- await this . topcoderEngagementsService . getAssignmentContextById (
584- assignmentId ,
712+ if ( assignmentLookupId ) {
713+ try {
714+ const assignmentContext =
715+ await this . topcoderEngagementsService . getAssignmentContextById (
716+ assignmentLookupId ,
717+ ) ;
718+
719+ result . data . engagementDetails = {
720+ assignmentId : assignmentContext . assignmentId ,
721+ engagementId : assignmentContext . engagementId ,
722+ projectId : assignmentContext . projectId ,
723+ projectName : assignmentContext . projectName ?? undefined ,
724+ engagementTitle : assignmentContext . engagementTitle ,
725+ billingStartDate : assignmentContext . startDate
726+ ? new Date ( assignmentContext . startDate )
727+ : undefined ,
728+ durationMonths : assignmentContext . durationMonths ?? undefined ,
729+ ratePerHour : assignmentContext . ratePerHour ?? undefined ,
730+ standardHoursPerWeek :
731+ assignmentContext . standardHoursPerWeek ?? undefined ,
732+ otherRemarks : assignmentContext . otherRemarks ?? undefined ,
733+ } ;
734+
735+ return result ;
736+ } catch ( error ) {
737+ this . logger . warn (
738+ `Failed to enrich winning ${ winningsId } with assignment context` ,
739+ error instanceof Error ? error . message : error ,
585740 ) ;
741+ }
742+ }
586743
587- result . data . engagementDetails = {
588- assignmentId : assignmentContext . assignmentId ,
589- engagementId : assignmentContext . engagementId ,
590- projectId : assignmentContext . projectId ,
591- projectName : assignmentContext . projectName ?? undefined ,
592- engagementTitle : assignmentContext . engagementTitle ,
593- billingStartDate : assignmentContext . startDate
594- ? new Date ( assignmentContext . startDate )
595- : undefined ,
596- durationMonths : assignmentContext . durationMonths ?? undefined ,
597- ratePerHour : assignmentContext . ratePerHour ?? undefined ,
598- standardHoursPerWeek :
599- assignmentContext . standardHoursPerWeek ?? undefined ,
600- otherRemarks : assignmentContext . otherRemarks ?? undefined ,
601- } ;
744+ if ( ! externalId ) {
745+ return result ;
746+ }
747+
748+ try {
749+ const engagement =
750+ await this . topcoderEngagementsService . getEngagementById ( externalId ) ;
751+ const assignment = this . findMatchingEngagementAssignment (
752+ engagement . assignments ,
753+ winning . winner_id ,
754+ assignmentId ,
755+ ) ;
756+
757+ result . data . engagementDetails = this . buildEngagementDetailsFromEngagement (
758+ engagement ,
759+ assignment ,
760+ assignmentId ,
761+ ) ;
602762 } catch ( error ) {
603763 this . logger . warn (
604- `Failed to enrich winning ${ winningsId } with engagement context ` ,
764+ `Failed to enrich winning ${ winningsId } with engagement details ` ,
605765 error instanceof Error ? error . message : error ,
606766 ) ;
607767 }
0 commit comments