Skip to content

Commit 8d7e5f8

Browse files
committed
Merge branch 'develop' into infrastructure
2 parents eefa8a5 + 4802aa0 commit 8d7e5f8

52 files changed

Lines changed: 1160 additions & 772 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/backend/src/controllers/projects.controllers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,13 +437,14 @@ export default class ProjectsController {
437437
static async editLinkType(req: Request, res: Response, next: NextFunction) {
438438
try {
439439
const { linkTypeName } = req.params;
440-
const { iconName, required } = req.body;
440+
const { name: newName, iconName, required } = req.body;
441441
const linkTypeUpdated = await ProjectsService.editLinkType(
442442
linkTypeName,
443443
iconName,
444444
required,
445445
req.currentUser,
446-
req.organization
446+
req.organization,
447+
newName
447448
);
448449
res.status(200).json(linkTypeUpdated);
449450
} catch (error: unknown) {

src/backend/src/controllers/reimbursement-requests.controllers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,25 @@ export default class ReimbursementRequestsController {
497497
}
498498
}
499499

500+
static async setVendorTaxExemptStatus(req: Request, res: Response, next: NextFunction) {
501+
try {
502+
const { vendorId } = req.params;
503+
504+
const { taxExempt } = req.body;
505+
506+
const updatedVendor = await ReimbursementRequestService.setVendorTaxExemptStatus(
507+
vendorId,
508+
taxExempt,
509+
req.currentUser,
510+
req.organization
511+
);
512+
513+
res.status(200).json(updatedVendor);
514+
} catch (error) {
515+
next(error);
516+
}
517+
}
518+
500519
static async deleteVendor(req: Request, res: Response, next: NextFunction) {
501520
try {
502521
const { vendorId } = req.params;

src/backend/src/routes/projects.routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ projectRouter.post(
2929
);
3030
projectRouter.post(
3131
'/link-types/:linkTypeName/edit',
32+
nonEmptyString(body('name').optional()),
3233
nonEmptyString(body('iconName')),
3334
body('required').isBoolean(),
3435
validateInputs,

src/backend/src/routes/reimbursement-requests.routes.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ reimbursementRequestsRouter.post(
101101
ReimbursementRequestController.editVendor
102102
);
103103

104+
reimbursementRequestsRouter.post(
105+
'/vendors/:vendorId/setTaxExemptStatus',
106+
ReimbursementRequestController.setVendorTaxExemptStatus
107+
);
108+
104109
reimbursementRequestsRouter.post('/:vendorId/vendors/delete', ReimbursementRequestController.deleteVendor);
105110

106111
reimbursementRequestsRouter.post(
@@ -167,7 +172,7 @@ reimbursementRequestsRouter.post(
167172
body('taxExempt').optional().isBoolean(),
168173
body('twoFactorContacts').optional().isArray(),
169174
nonEmptyString(body('twoFactorContacts.*')),
170-
nonEmptyString(body('notes')).optional(),
175+
body('notes').optional().isString(),
171176
validateInputs,
172177
ReimbursementRequestController.createVendor
173178
);

src/backend/src/services/change-requests.services.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export default class ChangeRequestsService {
178178
dateReviewed: null
179179
},
180180
{
181-
NOT: { scopeChangeRequest: null }
181+
changes: { none: {} }
182182
}
183183
];
184184

@@ -666,9 +666,7 @@ export default class ChangeRequestsService {
666666
include: {
667667
changeRequests: {
668668
where: {
669-
dateDeleted: {
670-
not: null
671-
}
669+
dateDeleted: null
672670
},
673671
include: {
674672
changes: true
@@ -1143,6 +1141,8 @@ export default class ChangeRequestsService {
11431141
}
11441142
}
11451143

1144+
const isCreatingNewProject = projectProposedChanges && projectNumber === 0;
1145+
11461146
const changes = await prisma.wbs_Proposed_Changes.create({
11471147
data: {
11481148
scopeChangeRequest: {
@@ -1151,7 +1151,7 @@ export default class ChangeRequestsService {
11511151
}
11521152
},
11531153
name,
1154-
status: WBS_Element_Status.ACTIVE,
1154+
status: isCreatingNewProject ? WBS_Element_Status.INACTIVE : wbsElement.status,
11551155
links: {
11561156
create: validationResult.links.map((linkInfo) => ({
11571157
url: linkInfo.url,
@@ -1234,11 +1234,13 @@ export default class ChangeRequestsService {
12341234
managerId
12351235
);
12361236

1237+
const isCreatingNewWorkPackage = workPackageProposedChanges && workPackageNumber === 0;
1238+
12371239
const changes = await prisma.wbs_Proposed_Changes.create({
12381240
data: {
12391241
scopeChangeRequest: { connect: { scopeCrId: createdCR.scopeChangeRequest!.scopeCrId } },
12401242
name,
1241-
status: WBS_Element_Status.INACTIVE,
1243+
status: isCreatingNewWorkPackage ? WBS_Element_Status.INACTIVE : wbsElement.status,
12421244
proposedDescriptionBulletChanges: {
12431245
create: validationResult.descriptionBullets.map((bullet) => ({
12441246
detail: bullet.detail,

src/backend/src/services/finance.services.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,15 +1096,20 @@ export default class FinanceServices {
10961096

10971097
if (!tier) throw new NotFoundException('Sponsor Tier', sponsorTierId);
10981098

1099-
const existingSponsor = await prisma.sponsor.findFirst({
1100-
where: {
1101-
name,
1102-
organizationId: organization.organizationId
1103-
}
1104-
});
1099+
if (name !== oldSponsor.name) {
1100+
const existingSponsor = await prisma.sponsor.findFirst({
1101+
where: {
1102+
name: {
1103+
equals: name,
1104+
mode: 'insensitive'
1105+
},
1106+
organizationId: organization.organizationId
1107+
}
1108+
});
11051109

1106-
if (existingSponsor) {
1107-
throw new HttpException(400, `A sponsor with the name "${name}" already exists.`);
1110+
if (existingSponsor) {
1111+
throw new HttpException(400, `A sponsor with the name "${name}" already exists.`);
1112+
}
11081113
}
11091114

11101115
const updatedSponsor = await prisma.sponsor.update({

src/backend/src/services/projects.services.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,8 @@ export default class ProjectsService {
621621
iconName: string,
622622
required: boolean,
623623
submitter: User,
624-
organization: Organization
624+
organization: Organization,
625+
newName?: string
625626
): Promise<LinkType> {
626627
if (!(await userHasPermission(submitter.userId, organization.organizationId, isAdmin)))
627628
throw new AccessDeniedException('Only an admin can update the linkType');
@@ -638,11 +639,25 @@ export default class ProjectsService {
638639

639640
if (!linkType) throw new NotFoundException('Link Type', linkName);
640641

642+
// If attempting to rename, ensure new name does not conflict with an existing LinkType
643+
if (newName && newName !== linkName) {
644+
const existingWithNewName = await prisma.link_Type.findUnique({
645+
where: {
646+
uniqueLinkType: {
647+
name: newName,
648+
organizationId: organization.organizationId
649+
}
650+
}
651+
});
652+
653+
if (existingWithNewName) throw new HttpException(400, 'LinkType with that name already exists in this organization.');
654+
}
655+
641656
// update the LinkType
642657
const linkTypeUpdated = await prisma.link_Type.update({
643658
where: { id: linkType.id },
644659
data: {
645-
name: linkName,
660+
name: newName && newName ? newName : linkName,
646661
iconName,
647662
required
648663
}

src/backend/src/services/reimbursement-requests.services.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,6 +1544,42 @@ export default class ReimbursementRequestService {
15441544
return vendorTransformer(vendor);
15451545
}
15461546

1547+
static async setVendorTaxExemptStatus(
1548+
vendorId: string,
1549+
taxExempt: boolean,
1550+
submitter: User,
1551+
organization: Organization
1552+
): Promise<Vendor> {
1553+
const existingVendor = await prisma.vendor.findUnique({
1554+
where: { vendorId, dateDeleted: null },
1555+
include: { twoFactorContacts: { select: { userId: true } } }
1556+
});
1557+
1558+
if (!existingVendor) {
1559+
throw new NotFoundException('Vendor', vendorId);
1560+
}
1561+
1562+
if (existingVendor.organizationId !== organization.organizationId) {
1563+
throw new InvalidOrganizationException('Vendor');
1564+
}
1565+
1566+
const isUserAuthorized =
1567+
existingVendor.addedByUserId === submitter.userId ||
1568+
(await isUserOnFinanceTeam(submitter, organization.organizationId)) ||
1569+
(await userHasPermission(submitter.userId, organization.organizationId, isHead));
1570+
if (!isUserAuthorized) {
1571+
throw new AccessDeniedException(`You are not a member of the finance team!`);
1572+
}
1573+
1574+
const updatedVendor = await prisma.vendor.update({
1575+
where: { vendorId },
1576+
data: { taxExempt },
1577+
...getVendorQueryArgs(organization.organizationId)
1578+
});
1579+
1580+
return vendorTransformer(updatedVendor);
1581+
}
1582+
15471583
/**
15481584
* Deletes the vendor
15491585
*

src/backend/src/services/tasks.services.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,11 @@ export default class TasksService {
266266

267267
// this checks the current users permissions
268268
const isLead = wbsElement.leadId === currentUser.userId || wbsElement.managerId === currentUser.userId;
269-
if (!(await userHasPermission(currentUser.userId, organization.organizationId, isAdmin)) && !isLead) {
270-
throw new AccessDeniedException('Only admin, app-admins, project leads, and project managers can delete tasks');
269+
const isCreator = task.createdByUserId === currentUser.userId;
270+
if (!(await userHasPermission(currentUser.userId, organization.organizationId, isAdmin)) && !isLead && !isCreator) {
271+
throw new AccessDeniedException(
272+
'Only admin, app-admins, project leads, project managers, and the task creator can delete tasks'
273+
);
271274
}
272275

273276
const deletedTask = await prisma.task.update({

0 commit comments

Comments
 (0)