1- import { includes , isEmpty , sortBy , find , camelCase , groupBy } from 'lodash' ;
1+ import { includes , isEmpty , find , camelCase , groupBy , orderBy } from 'lodash' ;
22import { Injectable } from '@nestjs/common' ;
33import { ENV_CONFIG } from 'src/config' ;
44import { Logger } from 'src/shared/global' ;
5- import { Challenge , ChallengeResource , ResourceRole } from './models' ;
5+ import {
6+ Challenge ,
7+ ChallengeResource ,
8+ ChallengeSubmission ,
9+ ResourceRole ,
10+ } from './models' ;
611import { BillingAccountsService } from 'src/shared/topcoder/billing-accounts.service' ;
712import { TopcoderM2MService } from 'src/shared/topcoder/topcoder-m2m.service' ;
813import { ChallengeStatuses } from 'src/dto/challenge.dto' ;
@@ -40,7 +45,6 @@ export class ChallengesService {
4045
4146 try {
4247 const challenge = await this . m2MService . m2mFetch < Challenge > ( requestUrl ) ;
43- this . logger . log ( JSON . stringify ( challenge , null , 2 ) ) ;
4448 return challenge ;
4549 } catch ( e ) {
4650 this . logger . error (
@@ -50,6 +54,24 @@ export class ChallengesService {
5054 }
5155 }
5256
57+ async getChallengeSubmissionsCount ( challengeId : string ) {
58+ const requestUrl = `${ TC_API_BASE } /submissions?challengeId=${ challengeId } &perPage=9999` ;
59+
60+ try {
61+ const submissions =
62+ await this . m2MService . m2mFetch < ChallengeSubmission [ ] > ( requestUrl ) ;
63+ const uniqueSubmissions = Object . fromEntries (
64+ submissions . map ( ( s ) => [ s . memberId , s ] ) ,
65+ ) ;
66+ return Object . keys ( uniqueSubmissions ) . length ;
67+ } catch ( e ) {
68+ this . logger . error (
69+ `Challenge submissions couldn't be fetched for challenge ${ challengeId } !` ,
70+ e ,
71+ ) ;
72+ }
73+ }
74+
5375 async getChallengeResources ( challengeId : string ) {
5476 try {
5577 const resources = await this . m2MService . m2mFetch < ChallengeResource [ ] > (
@@ -97,36 +119,43 @@ export class ChallengesService {
97119 } [ ] ;
98120
99121 const { prizeSets, winners, reviewers } = challenge ;
122+ const isCancelledFailedReview =
123+ challenge . status . toLowerCase ( ) ===
124+ ChallengeStatuses . CancelledFailedReview . toLowerCase ( ) ;
100125
101126 // generate placement payments
102- const placementPrizes = sortBy (
127+ const placementPrizes = orderBy (
103128 find ( prizeSets , { type : 'PLACEMENT' } ) ?. prizes ,
104129 'value' ,
130+ 'desc' ,
105131 ) ;
106- if ( placementPrizes . length < winners . length ) {
107- throw new Error (
108- 'Task has incorrect number of placement prizes! There are more winners than prizes!' ,
109- ) ;
110- }
111132
112- winners . forEach ( ( winner ) => {
113- payments . push ( {
114- handle : winner . handle ,
115- amount : placementPrizes [ winner . placement - 1 ] . value ,
116- userId : winner . userId . toString ( ) ,
117- type : challenge . task . isTask
118- ? WinningsCategory . TASK_PAYMENT
119- : WinningsCategory . CONTEST_PAYMENT ,
120- description :
121- challenge . type === 'Task'
122- ? challenge . name
123- : `${ challenge . name } - ${ placeToOrdinal ( winner . placement ) } Place` ,
133+ if ( ! isCancelledFailedReview ) {
134+ if ( placementPrizes . length < winners . length ) {
135+ throw new Error (
136+ 'Task has incorrect number of placement prizes! There are more winners than prizes!' ,
137+ ) ;
138+ }
139+
140+ winners . forEach ( ( winner ) => {
141+ payments . push ( {
142+ handle : winner . handle ,
143+ amount : placementPrizes [ winner . placement - 1 ] . value ,
144+ userId : winner . userId . toString ( ) ,
145+ type : challenge . task . isTask
146+ ? WinningsCategory . TASK_PAYMENT
147+ : WinningsCategory . CONTEST_PAYMENT ,
148+ description :
149+ challenge . type === 'Task'
150+ ? challenge . name
151+ : `${ challenge . name } - ${ placeToOrdinal ( winner . placement ) } Place` ,
152+ } ) ;
124153 } ) ;
125- } ) ;
154+ }
126155
127156 // generate copilot payments
128157 const copilotPrizes = find ( prizeSets , { type : 'COPILOT' } ) ?. prizes ?? [ ] ;
129- if ( copilotPrizes . length ) {
158+ if ( copilotPrizes . length && ! isCancelledFailedReview ) {
130159 const copilots = challengeResources . copilot ;
131160
132161 if ( ! copilots ?. length ) {
@@ -144,19 +173,22 @@ export class ChallengesService {
144173 }
145174
146175 // generate reviewer payments
147- const firstPlacePrize = placementPrizes [ 0 ] . value ;
176+ const firstPlacePrize = placementPrizes ?. [ 0 ] ? .value ?? 0 ;
148177 const challengeReviewer = find ( reviewers , { isMemberReview : true } ) ;
178+ const numOfSubmissions =
179+ ( await this . getChallengeSubmissionsCount ( challenge . id ) ) ?? 1 ;
149180
150181 if ( challengeReviewer && challengeResources . reviewer ) {
151182 challengeResources . reviewer ?. forEach ( ( reviewer ) => {
152183 payments . push ( {
153184 handle : reviewer . memberHandle ,
154185 userId : reviewer . memberId . toString ( ) ,
155186 amount : Math . round (
156- ( challengeReviewer . basePayment ?? 0 ) +
157- ( challengeReviewer . incrementalPayment ?? 0 ) *
158- challenge . numOfSubmissions *
159- firstPlacePrize ,
187+ ( challengeReviewer . fixedAmount ?? 0 ) +
188+ ( challengeReviewer . baseCoefficient ?? 0 ) * firstPlacePrize +
189+ ( challengeReviewer . incrementalCoefficient ?? 0 ) *
190+ firstPlacePrize *
191+ numOfSubmissions ,
160192 ) ,
161193 type : WinningsCategory . REVIEW_BOARD_PAYMENT ,
162194 } ) ;
@@ -202,18 +234,18 @@ export class ChallengesService {
202234 throw new Error ( 'Challenge not found!' ) ;
203235 }
204236
205- if (
206- challenge . status . toLowerCase ( ) !==
207- ChallengeStatuses . Completed . toLowerCase ( )
208- ) {
209- throw new Error ( "Challenge isn't completed yet!" ) ;
237+ const allowedStatuses = [
238+ ChallengeStatuses . Completed . toLowerCase ( ) ,
239+ ChallengeStatuses . CancelledFailedReview . toLowerCase ( ) ,
240+ ] ;
241+
242+ if ( ! allowedStatuses . includes ( challenge . status . toLowerCase ( ) ) ) {
243+ throw new Error ( "Challenge isn't in a payable status!" ) ;
210244 }
211245
212246 const existingPayments = (
213247 await this . winningsRepo . searchWinnings ( {
214248 externalIds : [ challengeId ] ,
215- limit : 1 ,
216- offset : 0 ,
217249 } as WinningRequestDto )
218250 ) ?. data ?. winnings ;
219251 if ( existingPayments ?. length > 0 ) {
@@ -244,9 +276,16 @@ export class ChallengesService {
244276 }
245277
246278 await Promise . all (
247- payments . map ( ( p ) =>
248- this . winningsService . createWinningWithPayments ( p , userId ) ,
249- ) ,
279+ payments . map ( async ( p ) => {
280+ try {
281+ await this . winningsService . createWinningWithPayments ( p , userId ) ;
282+ } catch ( e ) {
283+ this . logger . log (
284+ `Failed to create winnings payment for user ${ p . winnerId } !` ,
285+ e ,
286+ ) ;
287+ }
288+ } ) ,
250289 ) ;
251290
252291 this . logger . log ( 'Task Completed. locking consumed budget' , baValidation ) ;
0 commit comments