@@ -1008,6 +1008,147 @@ export default class ChangeRequestsService {
10081008 return changeRequestTransformer ( createdChangeRequest ) ;
10091009 }
10101010
1011+ /**
1012+ * Validates and creates a leadership change request, auto-approved immediately.
1013+ * Updates the lead and/or manager of a project or work package without requiring review.
1014+ * @param submitter the user creating the cr
1015+ * @param carNumber the car number for the wbs element
1016+ * @param projectNumber the project number for the wbs element
1017+ * @param workPackageNumber the work package number for the wbs element
1018+ * @param leadId the id of the new lead
1019+ * @param managerId the id of the new manager
1020+ * @param organization the organization the user is currently in
1021+ * @returns the id of the created cr
1022+ */
1023+ static async createLeadershipChangeRequest (
1024+ submitter : User ,
1025+ carNumber : number ,
1026+ projectNumber : number ,
1027+ workPackageNumber : number ,
1028+ leadId : string | undefined ,
1029+ managerId : string | undefined ,
1030+ organization : Organization
1031+ ) : Promise < string > {
1032+ if ( await userHasPermission ( submitter . userId , organization . organizationId , isGuest ) )
1033+ throw new AccessDeniedGuestException ( 'create leadership change requests' ) ;
1034+
1035+ // verify wbs element exists
1036+ const wbsElement = await prisma . wBS_Element . findUnique ( {
1037+ where : {
1038+ wbsNumber : {
1039+ carNumber,
1040+ projectNumber,
1041+ workPackageNumber,
1042+ organizationId : organization . organizationId
1043+ }
1044+ } ,
1045+ select : {
1046+ wbsElementId : true ,
1047+ dateDeleted : true ,
1048+ organizationId : true ,
1049+ leadId : true ,
1050+ managerId : true
1051+ }
1052+ } ) ;
1053+
1054+ if ( ! wbsElement ) throw new NotFoundException ( 'WBS Element' , wbsPipe ( { carNumber, projectNumber, workPackageNumber } ) ) ;
1055+ if ( wbsElement . dateDeleted )
1056+ throw new DeletedException ( 'WBS Element' , wbsPipe ( { carNumber, projectNumber, workPackageNumber } ) ) ;
1057+ if ( wbsElement . organizationId !== organization . organizationId ) throw new InvalidOrganizationException ( 'WBS Element' ) ;
1058+
1059+ // avoid merge conflicts
1060+ await validateNoUnreviewedOpenCRs ( wbsElement . wbsElementId ) ;
1061+
1062+ const numChangeRequests = await prisma . change_Request . count ( {
1063+ where : { organizationId : organization . organizationId }
1064+ } ) ;
1065+
1066+ const createdCR = await prisma . change_Request . create ( {
1067+ data : {
1068+ submitter : { connect : { userId : submitter . userId } } ,
1069+ wbsElement : { connect : { wbsElementId : wbsElement . wbsElementId } } ,
1070+ type : CR_Type . LEADERSHIP ,
1071+ organization : { connect : { organizationId : organization . organizationId } } ,
1072+ identifier : numChangeRequests + 1 ,
1073+ leadershipChangeRequest : {
1074+ create : {
1075+ lead : leadId ? { connect : { userId : leadId } } : undefined ,
1076+ manager : managerId ? { connect : { userId : managerId } } : undefined
1077+ }
1078+ }
1079+ }
1080+ } ) ;
1081+
1082+ await ChangeRequestsService . applyLeadershipChangeRequest ( createdCR . crId , wbsElement , submitter , leadId , managerId ) ;
1083+
1084+ return createdCR . crId ;
1085+ }
1086+
1087+ /**
1088+ * Applies a leadership change request by updating the wbs element's lead/manager
1089+ * and auto-approving the change request.
1090+ */
1091+ private static async applyLeadershipChangeRequest (
1092+ crId : string ,
1093+ wbsElement : { wbsElementId : string ; leadId : string | null ; managerId : string | null } ,
1094+ submitter : User ,
1095+ leadId : string | undefined ,
1096+ managerId : string | undefined
1097+ ) : Promise < void > {
1098+ await prisma . $transaction ( async ( tx ) => {
1099+ await tx . change_Request . update ( {
1100+ where : { crId } ,
1101+ data : {
1102+ reviewer : { connect : { userId : submitter . userId } } ,
1103+ dateReviewed : new Date ( ) ,
1104+ accepted : true ,
1105+ reviewNotes : 'Auto-approved: leadership change only'
1106+ }
1107+ } ) ;
1108+
1109+ await tx . wBS_Element . update ( {
1110+ where : { wbsElementId : wbsElement . wbsElementId } ,
1111+ data : {
1112+ lead : leadId ? { connect : { userId : leadId } } : { disconnect : true } ,
1113+ manager : managerId ? { connect : { userId : managerId } } : { disconnect : true }
1114+ }
1115+ } ) ;
1116+
1117+ const changes : { changeRequestId : string ; implementerId : string ; wbsElementId : string ; detail : string } [ ] = [ ] ;
1118+
1119+ const oldLeadId = wbsElement . leadId ?? undefined ;
1120+ const oldManagerId = wbsElement . managerId ?? undefined ;
1121+
1122+ if ( leadId !== oldLeadId ) {
1123+ // only update if lead changed
1124+ const oldLead = await getUserFullName ( wbsElement . leadId ?? null ) ;
1125+ const newLead = await getUserFullName ( leadId ?? null ) ;
1126+ changes . push ( {
1127+ changeRequestId : crId ,
1128+ implementerId : submitter . userId ,
1129+ wbsElementId : wbsElement . wbsElementId ,
1130+ detail : buildChangeDetail ( 'lead' , oldLead , newLead )
1131+ } ) ;
1132+ }
1133+
1134+ if ( managerId !== oldManagerId ) {
1135+ // only update if manager changed
1136+ const oldManager = await getUserFullName ( wbsElement . managerId ?? null ) ;
1137+ const newManager = await getUserFullName ( managerId ?? null ) ;
1138+ changes . push ( {
1139+ changeRequestId : crId ,
1140+ implementerId : submitter . userId ,
1141+ wbsElementId : wbsElement . wbsElementId ,
1142+ detail : buildChangeDetail ( 'manager' , oldManager , newManager )
1143+ } ) ;
1144+ }
1145+
1146+ if ( changes . length > 0 ) {
1147+ await tx . change . createMany ( { data : changes } ) ;
1148+ }
1149+ } ) ;
1150+ }
1151+
10111152 /**
10121153 * Validates and creates a standard change request
10131154 * @param submitter The user creating the cr
0 commit comments