@@ -563,13 +563,20 @@ function getApprovalFlowBillingAccountId(challenge, data?: any, projectBillingAc
563563/**
564564 * Determines whether the challenge approval flow should be bypassed.
565565 *
566- * Challenges billed to configured Topgear billing accounts are auto-approved
567- * because they should not enter the manual budget approval flow.
566+ * Fun challenges and challenges billed to configured Topgear billing accounts
567+ * are auto-approved because they should not enter the manual budget approval flow.
568568 *
569569 * @param {string|number|null|undefined } billingAccountId Billing-account identifier.
570+ * @param {boolean } [funChallenge=false] Effective Fun challenge flag from the create or update.
570571 * @returns {boolean } `true` when challenge approval should be skipped.
572+ * @throws This function does not throw.
573+ * @remarks Used by challenge create, update, and launch validation to apply one approval policy.
571574 */
572- function shouldSkipChallengeApprovalFlow ( billingAccountId ) {
575+ function shouldSkipChallengeApprovalFlow ( billingAccountId , funChallenge = false ) {
576+ if ( funChallenge === true ) {
577+ return true ;
578+ }
579+
573580 const normalizedBillingAccountId = normalizeOptionalString ( billingAccountId ) ;
574581
575582 if ( ! normalizedBillingAccountId ) {
@@ -587,10 +594,13 @@ function shouldSkipChallengeApprovalFlow(billingAccountId) {
587594 *
588595 * @param {Object } target Challenge create or update payload to mutate.
589596 * @param {string|number|null|undefined } billingAccountId Billing-account identifier.
597+ * @param {boolean } [funChallenge=false] Effective Fun challenge flag from the create or update.
590598 * @returns {boolean } `true` when approval fields were forced to approved.
599+ * @throws This function does not intentionally throw; callers provide a mutable challenge payload.
600+ * @remarks Used before normal approval validation so bypassed challenges persist as approved.
591601 */
592- function applyChallengeApprovalFlowBypass ( target , billingAccountId ) {
593- if ( ! shouldSkipChallengeApprovalFlow ( billingAccountId ) ) {
602+ function applyChallengeApprovalFlowBypass ( target , billingAccountId , funChallenge = false ) {
603+ if ( ! shouldSkipChallengeApprovalFlow ( billingAccountId , funChallenge ) ) {
594604 return false ;
595605 }
596606
@@ -606,11 +616,18 @@ function applyChallengeApprovalFlowBypass(target, billingAccountId) {
606616 *
607617 * @param {string|null|undefined } approvalStatus Effective approval status.
608618 * @param {string|number|null|undefined } billingAccountId Billing-account identifier.
619+ * @param {boolean } [funChallenge=false] Effective Fun challenge flag from the create or update.
609620 * @returns {boolean } `true` when launch should be blocked by approval state.
621+ * @throws This function does not throw.
622+ * @remarks Used when a challenge update transitions its status to Active.
610623 */
611- function shouldBlockChallengeLaunchForApproval ( approvalStatus , billingAccountId ) {
624+ function shouldBlockChallengeLaunchForApproval (
625+ approvalStatus ,
626+ billingAccountId ,
627+ funChallenge = false ,
628+ ) {
612629 return (
613- ! shouldSkipChallengeApprovalFlow ( billingAccountId ) &&
630+ ! shouldSkipChallengeApprovalFlow ( billingAccountId , funChallenge ) &&
614631 normalizeApprovalStatus ( approvalStatus ) !== CHALLENGE_APPROVAL_STATUS . APPROVED
615632 ) ;
616633}
@@ -2530,7 +2547,8 @@ searchChallenges.schema = {
25302547
25312548/**
25322549 * Create challenge.
2533- * Challenges billed to configured Topgear accounts skip manual budget approval and are auto-approved.
2550+ * Fun challenges and challenges billed to configured Topgear accounts skip manual budget approval
2551+ * and are auto-approved.
25342552 * @param {Object } currentUser the user who perform operation
25352553 * @param {Object } challenge the challenge to create; omitted `is_test_challenge` metadata defaults
25362554 * to the exact string `false`
@@ -2645,6 +2663,7 @@ async function createChallenge(currentUser, challenge, userToken) {
26452663 const skipsChallengeApprovalFlow = applyChallengeApprovalFlowBypass (
26462664 challenge ,
26472665 approvalBillingAccountId ,
2666+ challenge . funChallenge === true ,
26482667 ) ;
26492668
26502669 if ( ! skipsChallengeApprovalFlow ) {
@@ -3654,7 +3673,8 @@ function prepareTaskCompletionData(challenge, challengeResources, data) {
36543673 * Update challenge.
36553674 * When a challenge transitions to completed task status or a cancelled status,
36563675 * payment generation is requested after the database update commits.
3657- * Challenges billed to configured Topgear accounts skip manual budget approval and remain approved.
3676+ * Fun challenges and challenges billed to configured Topgear accounts skip manual budget approval
3677+ * and remain approved.
36583678 * Updates that start in or transition to a completed/cancelled status may not change the effective
36593679 * `is_test_challenge` metadata value.
36603680 * @param {Object } currentUser the user who perform operation
@@ -3743,6 +3763,9 @@ async function updateChallenge(currentUser, challengeId, data, options: any = {}
37433763 }
37443764
37453765 data = preserveBillingMarkupForCopilotUpdate ( currentUser , data , challenge ) ;
3766+ const effectiveFunChallenge = _ . isBoolean ( data . funChallenge )
3767+ ? data . funChallenge
3768+ : challenge . funChallenge === true ;
37463769 const rawApprovalRejectionReason = _ . toString ( _ . get ( data , "approvalRejectionReason" , "" ) ) ;
37473770
37483771 // Remove fields from data that are not allowed to be updated and that match the existing challenge
@@ -3762,6 +3785,7 @@ async function updateChallenge(currentUser, challengeId, data, options: any = {}
37623785 const skipsChallengeApprovalFlow = applyChallengeApprovalFlowBypass (
37633786 data ,
37643787 approvalBillingAccountId ,
3788+ effectiveFunChallenge ,
37653789 ) ;
37663790
37673791 if ( ! skipsChallengeApprovalFlow ) {
@@ -3844,7 +3868,11 @@ async function updateChallenge(currentUser, challengeId, data, options: any = {}
38443868
38453869 if (
38463870 isStatusChangingToActive &&
3847- shouldBlockChallengeLaunchForApproval ( resolvedApprovalStatus , approvalBillingAccountId )
3871+ shouldBlockChallengeLaunchForApproval (
3872+ resolvedApprovalStatus ,
3873+ approvalBillingAccountId ,
3874+ effectiveFunChallenge ,
3875+ )
38483876 ) {
38493877 throw new errors . BadRequestError (
38503878 "Challenge launch is blocked until budget approval is Approved." ,
0 commit comments