@@ -775,3 +775,62 @@ def validate(self, procurement_tracker_step: ProcurementTrackerStep, context: Va
775775 raise ValidationError (
776776 {"reviewer_notes" : "Reviewer notes are required when declining an approval request." }
777777 )
778+
779+
780+ class AwardApprovalResponseAuthorizationRule (ValidationRule ):
781+ """
782+ Validates that the user responding to an award approval request is authorized.
783+ Only BUDGET_TEAM or SYSTEM_OWNER may respond to AWARD step approvals.
784+ """
785+
786+ @property
787+ def name (self ) -> str :
788+ return "AWARD Approval Response Authorization"
789+
790+ def validate (self , procurement_tracker_step : ProcurementTrackerStep , context : ValidationContext ) -> None :
791+ if procurement_tracker_step .step_type != ProcurementTrackerStepType .AWARD :
792+ return
793+ if "approval_status" not in context .updated_fields :
794+ return
795+
796+ user_id = context .user .id
797+ user = context .db_session .get (User , user_id )
798+ if user :
799+ user_role_names = {role .name for role in user .roles }
800+ if "BUDGET_TEAM" in user_role_names or "SYSTEM_OWNER" in user_role_names :
801+ return
802+
803+ raise AuthorizationError (
804+ f"User { user_id } is not authorized to respond to award approval requests. "
805+ "Only BUDGET_TEAM or SYSTEM_OWNER members may approve award requests." ,
806+ "ProcurementTrackerStep" ,
807+ )
808+
809+
810+ class AwardApprovalResponseValidationRule (ValidationRule ):
811+ """
812+ Validates that award approval responses are valid:
813+ - Approval must have been requested first
814+ - Cannot respond if already responded (APPROVED/DECLINED)
815+ """
816+
817+ @property
818+ def name (self ) -> str :
819+ return "AWARD Approval Response Validation"
820+
821+ def validate (self , procurement_tracker_step : ProcurementTrackerStep , context : ValidationContext ) -> None :
822+ if procurement_tracker_step .step_type != ProcurementTrackerStepType .AWARD :
823+ return
824+ if "approval_status" not in context .updated_fields :
825+ return
826+
827+ if not procurement_tracker_step .award_approval_requested :
828+ raise ValidationError (
829+ {"approval_status" : "Cannot respond to an award approval request that has not been submitted." }
830+ )
831+
832+ current_status = procurement_tracker_step .award_approval_status
833+ if current_status in ["APPROVED" , "DECLINED" ]:
834+ raise ValidationError (
835+ {"approval_status" : f"This award approval request has already been { current_status .lower ()} ." }
836+ )
0 commit comments