@@ -1822,6 +1822,8 @@ async function updateChallenge(currentUser, challengeId, data) {
18221822 }
18231823 enrichChallengeForResponse ( challenge ) ;
18241824 prismaHelper . convertModelToResponse ( challenge ) ;
1825+ const originalChallengePhases = _ . cloneDeep ( challenge . phases || [ ] ) ;
1826+ const auditUserId = _ . toString ( currentUser . userId ) ;
18251827 const existingPrizeType = challengeHelper . validatePrizeSetsAndGetPrizeType ( challenge . prizeSets ) ;
18261828
18271829 // No conversion needed - values are already in dollars in the database
@@ -2108,6 +2110,7 @@ async function updateChallenge(currentUser, challengeId, data) {
21082110 }
21092111
21102112 let phasesUpdated = false ;
2113+ let phasesForUpdate = null ;
21112114 if (
21122115 ( ( data . phases && data . phases . length > 0 ) ||
21132116 isChallengeBeingActivated ||
@@ -2140,10 +2143,12 @@ async function updateChallenge(currentUser, challengeId, data) {
21402143 }
21412144 phasesUpdated = true ;
21422145 data . phases = newPhases ;
2146+ phasesForUpdate = _ . cloneDeep ( newPhases ) ;
21432147 }
21442148 if ( isChallengeBeingCancelled && challenge . phases && challenge . phases . length > 0 ) {
21452149 data . phases = phaseHelper . handlePhasesAfterCancelling ( challenge . phases ) ;
21462150 phasesUpdated = true ;
2151+ phasesForUpdate = _ . cloneDeep ( data . phases ) ;
21472152 }
21482153 const phasesForDates = phasesUpdated ? data . phases : challenge . phases ;
21492154
@@ -2264,6 +2269,9 @@ async function updateChallenge(currentUser, challengeId, data) {
22642269 delete data . phases ;
22652270 }
22662271 }
2272+ if ( _ . isNil ( data . phases ) ) {
2273+ phasesForUpdate = null ;
2274+ }
22672275
22682276 // Normalize and validate reviewers' phase references before converting to Prisma input
22692277 if ( ! _ . isNil ( data . reviewers ) ) {
@@ -2318,6 +2326,9 @@ async function updateChallenge(currentUser, challengeId, data) {
23182326 updateData . updatedBy = _ . toString ( currentUser . userId ) ;
23192327 // reset createdBy
23202328 delete updateData . createdBy ;
2329+ if ( ! _ . isNil ( updateData . phases ) ) {
2330+ delete updateData . phases ;
2331+ }
23212332
23222333 const newPrizeType = challengeHelper . validatePrizeSetsAndGetPrizeType ( updateData . prizeSets ) ;
23232334 if ( newPrizeType != null && existingPrizeType != null && newPrizeType !== existingPrizeType ) {
@@ -2326,6 +2337,15 @@ async function updateChallenge(currentUser, challengeId, data) {
23262337 ) ;
23272338 }
23282339 const updatedChallenge = await prisma . $transaction ( async ( tx ) => {
2340+ if ( Array . isArray ( phasesForUpdate ) ) {
2341+ await syncChallengePhases (
2342+ tx ,
2343+ challengeId ,
2344+ phasesForUpdate ,
2345+ auditUserId ,
2346+ originalChallengePhases
2347+ ) ;
2348+ }
23292349 // drop nested data if updated
23302350 if ( ! _ . isNil ( updateData . legacyRecord ) ) {
23312351 await tx . challengeLegacy . deleteMany ( { where : { challengeId } } ) ;
@@ -2345,9 +2365,6 @@ async function updateChallenge(currentUser, challengeId, data) {
23452365 if ( ! _ . isNil ( updateData . metadata ) ) {
23462366 await tx . challengeMetadata . deleteMany ( { where : { challengeId } } ) ;
23472367 }
2348- if ( ! _ . isNil ( updateData . phases ) ) {
2349- await tx . challengePhase . deleteMany ( { where : { challengeId } } ) ;
2350- }
23512368 if ( ! _ . isNil ( updateData . prizeSets ) ) {
23522369 await tx . challengePrizeSet . deleteMany ( { where : { challengeId } } ) ;
23532370 }
@@ -2778,6 +2795,117 @@ function sanitizeChallenge(challenge) {
27782795 return sanitized ;
27792796}
27802797
2798+ async function syncChallengePhases ( tx , challengeId , updatedPhases , auditUserId , originalPhases = [ ] ) {
2799+ if ( ! Array . isArray ( updatedPhases ) ) {
2800+ return ;
2801+ }
2802+
2803+ const originalById = new Map ( ( originalPhases || [ ] ) . map ( ( phase ) => [ phase . id , phase ] ) ) ;
2804+ const originalByPhaseId = new Map ( ) ;
2805+ for ( const original of originalPhases || [ ] ) {
2806+ if ( ! _ . isNil ( original . phaseId ) ) {
2807+ originalByPhaseId . set ( original . phaseId , original ) ;
2808+ }
2809+ }
2810+ const retainedIds = new Set ( ) ;
2811+
2812+ for ( const phase of updatedPhases ) {
2813+ if ( ! phase ) continue ;
2814+
2815+ let recordId = ! _ . isNil ( phase . id ) ? phase . id : undefined ;
2816+ let phaseDefinitionId = ! _ . isNil ( phase . phaseId ) ? phase . phaseId : undefined ;
2817+
2818+ if ( ! phaseDefinitionId && recordId && originalById . has ( recordId ) ) {
2819+ phaseDefinitionId = originalById . get ( recordId ) . phaseId ;
2820+ }
2821+ if ( ! recordId && phaseDefinitionId && originalByPhaseId . has ( phaseDefinitionId ) ) {
2822+ recordId = originalByPhaseId . get ( phaseDefinitionId ) . id ;
2823+ }
2824+ if ( ! phaseDefinitionId && recordId && originalById . has ( recordId ) ) {
2825+ phaseDefinitionId = originalById . get ( recordId ) . phaseId ;
2826+ }
2827+ if ( ! phaseDefinitionId ) {
2828+ throw new BadRequestError ( "Cannot update challenge phases without phaseId" ) ;
2829+ }
2830+ if ( ! recordId ) {
2831+ recordId = uuid ( ) ;
2832+ }
2833+
2834+ const existing = originalById . get ( recordId ) ;
2835+ retainedIds . add ( recordId ) ;
2836+
2837+ const scalarKeys = [
2838+ "name" ,
2839+ "description" ,
2840+ "isOpen" ,
2841+ "duration" ,
2842+ "scheduledStartDate" ,
2843+ "scheduledEndDate" ,
2844+ "actualStartDate" ,
2845+ "actualEndDate" ,
2846+ "challengeSource" ,
2847+ ] ;
2848+ const phaseData = { } ;
2849+ for ( const key of scalarKeys ) {
2850+ if ( ! _ . isUndefined ( phase [ key ] ) ) {
2851+ phaseData [ key ] = phase [ key ] ;
2852+ }
2853+ }
2854+ phaseData . predecessor = _ . isNil ( phase . predecessor ) ? null : phase . predecessor ;
2855+
2856+ phaseData . phaseId = phaseDefinitionId ;
2857+
2858+ if ( existing ) {
2859+ await tx . challengePhase . update ( {
2860+ where : { id : recordId } ,
2861+ data : {
2862+ ...phaseData ,
2863+ updatedBy : auditUserId ,
2864+ } ,
2865+ } ) ;
2866+ await tx . challengePhaseConstraint . deleteMany ( { where : { challengePhaseId : recordId } } ) ;
2867+ } else {
2868+ await tx . challengePhase . create ( {
2869+ data : {
2870+ id : recordId ,
2871+ challengeId,
2872+ ...phaseData ,
2873+ createdBy : auditUserId ,
2874+ updatedBy : auditUserId ,
2875+ } ,
2876+ } ) ;
2877+ }
2878+
2879+ if ( Array . isArray ( phase . constraints ) && phase . constraints . length > 0 ) {
2880+ for ( const constraint of phase . constraints ) {
2881+ if ( _ . isNil ( constraint . name ) || _ . isNil ( constraint . value ) ) {
2882+ continue ;
2883+ }
2884+
2885+ const constraintData = {
2886+ challengePhaseId : recordId ,
2887+ name : constraint . name ,
2888+ value : constraint . value ,
2889+ createdBy : auditUserId ,
2890+ updatedBy : auditUserId ,
2891+ } ;
2892+
2893+ if ( ! _ . isNil ( constraint . id ) ) {
2894+ constraintData . id = constraint . id ;
2895+ }
2896+
2897+ await tx . challengePhaseConstraint . create ( { data : constraintData } ) ;
2898+ }
2899+ }
2900+ }
2901+
2902+ for ( const phase of originalPhases || [ ] ) {
2903+ if ( ! retainedIds . has ( phase . id ) ) {
2904+ await tx . challengePhase . delete ( { where : { id : phase . id } } ) ;
2905+ }
2906+ }
2907+ }
2908+
27812909function sanitizeData ( data , challenge ) {
27822910 for ( const key in data ) {
27832911 if ( key === "phases" ) continue ;
0 commit comments