Skip to content

Commit 1627131

Browse files
authored
Merge pull request #4164 from Northeastern-Electric-Racing/#3934-Enforce-Start-Date
added check to enforce start date
2 parents f211c35 + e05c1fd commit 1627131

12 files changed

Lines changed: 96 additions & 44 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -496,14 +496,14 @@ export default class ReimbursementRequestsController {
496496
const editedVendor = await ReimbursementRequestService.editVendor(
497497
name,
498498
vendorId,
499-
username,
500-
password,
501-
discountCode,
502499
taxExempt,
503500
twoFactorContacts,
504-
notes,
505501
req.currentUser,
506-
req.organization
502+
req.organization,
503+
username,
504+
password,
505+
discountCode,
506+
notes
507507
);
508508
res.status(200).json(editedVendor);
509509
} catch (error: unknown) {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ reimbursementRequestsRouter.get('/reimbursements', ReimbursementRequestControlle
9696
reimbursementRequestsRouter.post(
9797
'/:vendorId/vendors/edit',
9898
nonEmptyString(body('name')),
99-
nonEmptyString(body('username')).optional(),
100-
nonEmptyString(body('password')).optional(),
101-
nonEmptyString(body('discountCode')).optional(),
99+
nonEmptyString(body('username')).optional({ checkFalsy: true }),
100+
nonEmptyString(body('password')).optional({ checkFalsy: true }),
101+
nonEmptyString(body('discountCode')).optional({ checkFalsy: true }),
102102
body('taxExempt').isBoolean(),
103103
body('twoFactorContacts').isArray(),
104104
nonEmptyString(body('twoFactorContacts.*')),
105-
nonEmptyString(body('notes')).optional(),
105+
nonEmptyString(body('notes')).optional({ checkFalsy: true }),
106106
validateInputs,
107107
ReimbursementRequestController.editVendor
108108
);

src/backend/src/routes/tasks.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ tasksRouter.post(
4242
tasksRouter.post(
4343
'/:taskId/edit',
4444
nonEmptyString(body('title')),
45-
nonEmptyString(body('notes')),
45+
body('notes').isString(),
4646
isOptionalDateOnly(body('deadline')),
4747
isOptionalDateOnly(body('startDate')),
4848
isTaskPriority(body('priority')),

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,14 +1510,14 @@ export default class ReimbursementRequestService {
15101510
static async editVendor(
15111511
name: string,
15121512
vendorId: string,
1513-
username: string,
1514-
password: string,
1515-
discountCode: string,
15161513
taxExempt: boolean,
15171514
twoFactorContacts: string[],
1518-
notes: string,
15191515
submitter: User,
1520-
organization: Organization
1516+
organization: Organization,
1517+
username?: string,
1518+
password?: string,
1519+
discountCode?: string,
1520+
notes?: string
15211521
): Promise<Vendor> {
15221522
const existingVendor = await prisma.vendor.findUnique({
15231523
where: { vendorId, dateDeleted: null },

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export default class TasksService {
104104
if (!isUnderWordCount(title, 15)) throw new HttpException(400, 'Title must be less than 15 words');
105105
if (!isUnderWordCount(notes, 250)) throw new HttpException(400, 'Notes must be less than 250 words');
106106

107+
if (startDate && deadline && startDate > deadline) {
108+
throw new HttpException(400, 'Start date must be before or on the same day as the deadline');
109+
}
110+
107111
if (status === 'IN_PROGRESS' && (!deadline || assignees.length === 0)) {
108112
throw new HttpException(400, 'Tasks in progress must have a dealine and assignees');
109113
}
@@ -175,6 +179,13 @@ export default class TasksService {
175179
if (!isUnderWordCount(title, 15)) throw new HttpException(400, 'Title must be less than 15 words');
176180
if (!isUnderWordCount(notes, 250)) throw new HttpException(400, 'Notes must be less than 250 words');
177181

182+
const effectiveStartDate = startDate ?? originalTask.startDate ?? undefined;
183+
const effectiveDeadline = deadline ?? originalTask.deadline ?? undefined;
184+
185+
if (effectiveStartDate && effectiveDeadline && effectiveStartDate > effectiveDeadline) {
186+
throw new HttpException(400, 'Start date must be before or on the same day as the deadline');
187+
}
188+
178189
// if wbsNum passed, error if there's a problem with the wbs element
179190
if (wbsNum) {
180191
const newWbsElement = await prisma.wBS_Element.findUnique({

src/backend/tests/unit/reimbursement-requests.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,14 +359,14 @@ describe('Reimbursement Requests', () => {
359359
const editedVendor = await ReimbursementRequestService.editVendor(
360360
'Google',
361361
createdVendor.vendorId,
362-
'ner@gmail.com',
363-
'racecar',
364-
'DISCOUNT',
365362
false,
366363
[],
367-
'no notes',
368364
createdUser,
369-
org
365+
org,
366+
'ner@gmail.com',
367+
'racecar',
368+
'DISCOUNT',
369+
'no notes'
370370
);
371371
expect(editedVendor.name).toEqual('Google');
372372
expect(editedVendor.username).toEqual('ner@gmail.com');

src/backend/tests/unmocked/reimbursement-requests.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -713,14 +713,14 @@ describe('Reimbursement Requests', () => {
713713
const updatedVendor = await ReimbursementRequestService.editVendor(
714714
'Updated Vendor Name',
715715
vendor.vendorId,
716-
'',
717-
'',
718-
'',
719716
false,
720717
[],
721-
'Updated notes',
722718
regularMember,
723-
org
719+
org,
720+
undefined,
721+
undefined,
722+
undefined,
723+
'Updated notes'
724724
);
725725

726726
expect(updatedVendor).not.toBeNull();
@@ -733,14 +733,14 @@ describe('Reimbursement Requests', () => {
733733
ReimbursementRequestService.editVendor(
734734
'Updated Name',
735735
createdVendor.vendorId,
736-
'',
737-
'',
738-
'',
739736
false,
740737
[],
741-
'notes',
742738
anotherMember,
743-
org
739+
org,
740+
undefined,
741+
undefined,
742+
undefined,
743+
'notes'
744744
)
745745
).rejects.toThrow(new AccessDeniedException('You are not a member of the finance team!'));
746746
});
@@ -749,14 +749,14 @@ describe('Reimbursement Requests', () => {
749749
const updatedVendor = await ReimbursementRequestService.editVendor(
750750
'Finance Updated Vendor',
751751
createdVendor.vendorId,
752-
'',
753-
'',
754-
'',
755752
false,
756753
[],
757-
'Finance notes',
758754
financeMember,
759-
org
755+
org,
756+
undefined,
757+
undefined,
758+
undefined,
759+
'Finance notes'
760760
);
761761

762762
expect(updatedVendor).not.toBeNull();

src/frontend/src/pages/CalendarPage/Components/CalendarCreateTaskModal.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ const schema = yup.object().shape({
2121
status: yup.mixed<TaskStatus>().oneOf(Object.values(TaskStatus)).required(),
2222
assignees: yup.array().of(yup.string().required()).required(),
2323
startDate: yup.date().optional(),
24-
deadline: yup.date().optional(),
24+
deadline: yup
25+
.date()
26+
.optional()
27+
.test('deadline-after-start', 'Deadline must be on or after the start date', function (deadline) {
28+
const { startDate } = this.parent;
29+
if (!startDate || !deadline) return true;
30+
return deadline >= startDate;
31+
}),
2532
notes: yup.string().optional()
2633
});
2734

@@ -51,6 +58,7 @@ const CalendarCreateTaskModal: React.FC<CalendarCreateTaskModalProps> = ({ open,
5158
const {
5259
handleSubmit,
5360
control,
61+
watch,
5462
formState: { errors },
5563
reset
5664
} = useForm<CreateTaskFormInput>({
@@ -67,6 +75,8 @@ const CalendarCreateTaskModal: React.FC<CalendarCreateTaskModalProps> = ({ open,
6775
}
6876
});
6977

78+
const startDate = watch('startDate');
79+
7080
if (usersError) return <ErrorPage error={usersErr} />;
7181
if (projectsError) return <ErrorPage error={projectsErr} />;
7282
if (usersLoading || !users || projectsLoading || !projects) return <LoadingIndicator />;
@@ -249,10 +259,12 @@ const CalendarCreateTaskModal: React.FC<CalendarCreateTaskModalProps> = ({ open,
249259
format="MM-dd-yyyy"
250260
onChange={(event) => onChange(event ?? undefined)}
251261
value={value ?? null}
252-
slotProps={{ textField: { autoComplete: 'off' } }}
262+
minDate={startDate ?? undefined}
263+
slotProps={{ textField: { autoComplete: 'off', error: !!errors.deadline } }}
253264
/>
254265
)}
255266
/>
267+
{errors.deadline && <FormHelperText error>{errors.deadline.message}</FormHelperText>}
256268
</FormControl>
257269
</Grid>
258270
<Grid item xs={12}>

src/frontend/src/pages/CalendarPage/TaskClickPopup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export const TaskClickContent: React.FC<TaskClickContentProps> = ({ task, onClos
6464
notes: data.notes,
6565
priority: data.priority,
6666
startDate: data.startDate,
67-
deadline: data.deadline
67+
deadline: data.deadline,
68+
wbsNum: task.wbsNum
6869
});
6970
await editAssignees({
7071
taskId: task.taskId,

src/frontend/src/pages/GanttPage/ProjectGanttChart/AddGanttTaskModal.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ const schema = yup.object().shape({
1818
assignees: yup.array().of(yup.string()).min(0, 'At least 0 assignees are required'),
1919
notes: yup.string(),
2020
startDate: yup.date().nullable(),
21-
deadline: yup.date().nullable(),
21+
deadline: yup
22+
.date()
23+
.nullable()
24+
.test('deadline-after-start', 'Deadline must be on or after the start date', function (deadline) {
25+
const { startDate } = this.parent;
26+
if (!startDate || !deadline) return true;
27+
return deadline >= startDate;
28+
}),
2229
wpWbsNum: yup.mixed<WbsNumber>().optional()
2330
});
2431

@@ -49,6 +56,7 @@ const AddGanttTaskModal: React.FC<AddGanttTaskModalProps> = ({ showModal, handle
4956
handleSubmit,
5057
control,
5158
reset,
59+
watch,
5260
formState: { errors }
5361
} = useForm({
5462
resolver: yupResolver(schema),
@@ -64,6 +72,8 @@ const AddGanttTaskModal: React.FC<AddGanttTaskModalProps> = ({ showModal, handle
6472
}
6573
});
6674

75+
const startDate = watch('startDate');
76+
6777
if (usersIsError) return <ErrorPage message={usersError?.message} />;
6878
if (!users || usersIsLoading) return <LoadingIndicator />;
6979

@@ -214,10 +224,12 @@ const AddGanttTaskModal: React.FC<AddGanttTaskModalProps> = ({ showModal, handle
214224
onChange={(event) => onChange(event ?? undefined)}
215225
className={'padding: 10'}
216226
value={value}
227+
minDate={startDate ?? undefined}
217228
slotProps={{ textField: { autoComplete: 'off', error: !!errors.deadline } }}
218229
/>
219230
)}
220231
/>
232+
{errors.deadline && <FormHelperText error>{errors.deadline.message}</FormHelperText>}
221233
</FormControl>
222234
</Grid>
223235
<Grid item xs={12} md={12}>

0 commit comments

Comments
 (0)