richie-education 3.2.2-dev30 → 3.2.2-dev37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/components/PaymentInterfaces/types.ts +1 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +39 -15
- package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +21 -12
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +12 -1
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +154 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +142 -3
- package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +58 -1
- package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +69 -7
- package/js/types/Joanie.ts +12 -0
- package/js/utils/test/factories/joanie.ts +12 -0
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ export enum SubscriptionErrorMessageId {
|
|
|
10
10
|
ERROR_FULL_PRODUCT = 'errorFullProduct',
|
|
11
11
|
ERROR_WITHDRAWAL_RIGHT = 'errorWithdrawalRight',
|
|
12
12
|
ERROR_BATCH_ORDER_FORM_INVALID = 'batchOrderFormInvalid',
|
|
13
|
+
ERROR_BATCH_ORDER_MAX_ORDERS = 'errorBatchOrderMaxOrders',
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export enum PaymentProviders {
|
|
@@ -86,6 +86,8 @@ export const SaleTunnelInformationGroup = () => {
|
|
|
86
86
|
);
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
const EMAIL_REGEX = /^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$/;
|
|
90
|
+
|
|
89
91
|
export const validationSchema = Yup.object().shape({
|
|
90
92
|
offering_id: Yup.string().required(),
|
|
91
93
|
company_name: Yup.string().required(),
|
|
@@ -98,23 +100,25 @@ export const validationSchema = Yup.object().shape({
|
|
|
98
100
|
administrative_lastname: Yup.string().required(),
|
|
99
101
|
administrative_firstname: Yup.string().required(),
|
|
100
102
|
administrative_profession: Yup.string().required(),
|
|
101
|
-
administrative_email: Yup.string().
|
|
103
|
+
administrative_email: Yup.string().matches(EMAIL_REGEX).required(),
|
|
102
104
|
administrative_telephone: Yup.string().required(),
|
|
103
105
|
signatory_lastname: Yup.string().required(),
|
|
104
106
|
signatory_firstname: Yup.string().required(),
|
|
105
107
|
signatory_profession: Yup.string().required(),
|
|
106
|
-
signatory_email: Yup.string().
|
|
108
|
+
signatory_email: Yup.string().matches(EMAIL_REGEX).required(),
|
|
107
109
|
signatory_telephone: Yup.string().required(),
|
|
108
|
-
billing_address: Yup.object()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
billing_address: Yup.object()
|
|
111
|
+
.optional()
|
|
112
|
+
.shape({
|
|
113
|
+
company_name: Yup.string().optional(),
|
|
114
|
+
identification_number: Yup.string().optional(),
|
|
115
|
+
contact_name: Yup.string().optional(),
|
|
116
|
+
contact_email: Yup.string().matches(EMAIL_REGEX).optional(),
|
|
117
|
+
address: Yup.string().optional(),
|
|
118
|
+
postcode: Yup.string().optional(),
|
|
119
|
+
city: Yup.string().optional(),
|
|
120
|
+
country: Yup.string().optional(),
|
|
121
|
+
}),
|
|
118
122
|
nb_seats: Yup.number().required().min(1),
|
|
119
123
|
payment_method: Yup.mixed<PaymentMethod>().oneOf(Object.values(PaymentMethod)).required(),
|
|
120
124
|
funding_entity: Yup.string().optional(),
|
|
@@ -198,9 +202,29 @@ const BatchOrderForm = () => {
|
|
|
198
202
|
}, [values, batchOrder, setBatchOrder]);
|
|
199
203
|
|
|
200
204
|
useEffect(() => {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
const validateStep = async () => {
|
|
206
|
+
if (activeStep === 0 && batchOrder?.billing_address?.contact_email) {
|
|
207
|
+
const isEmailValid = EMAIL_REGEX.test(batchOrder.billing_address.contact_email);
|
|
208
|
+
if (!isEmailValid) {
|
|
209
|
+
setIsCurrentStepValid(false);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const fieldsToValidate = requiredFieldsByStep[activeStep];
|
|
214
|
+
const validationPromises = fieldsToValidate.map(async (field) => {
|
|
215
|
+
try {
|
|
216
|
+
const fieldSchema = Yup.reach(validationSchema, field) as Yup.Schema;
|
|
217
|
+
await fieldSchema.validate(batchOrder?.[field]);
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
const results = await Promise.all(validationPromises);
|
|
224
|
+
const isStepValid = results.every((isValid) => isValid);
|
|
225
|
+
setIsCurrentStepValid(isStepValid);
|
|
226
|
+
};
|
|
227
|
+
validateStep();
|
|
204
228
|
}, [activeStep, batchOrder]);
|
|
205
229
|
|
|
206
230
|
return (
|
|
@@ -208,7 +208,7 @@ export const StepContent = ({
|
|
|
208
208
|
const { items: organizations } = useOfferingOrganizations({ id: offering?.id });
|
|
209
209
|
const orgOptions = organizations.map((organization) => ({
|
|
210
210
|
label: organization.title,
|
|
211
|
-
value: organization.
|
|
211
|
+
value: organization.id,
|
|
212
212
|
}));
|
|
213
213
|
const [otherBillingAddress, setOtherBillingAddress] = useState(false);
|
|
214
214
|
|
|
@@ -275,7 +275,19 @@ export const StepContent = ({
|
|
|
275
275
|
/>
|
|
276
276
|
<Checkbox
|
|
277
277
|
label={intl.formatMessage(messages.checkBilling)}
|
|
278
|
-
onChange={() =>
|
|
278
|
+
onChange={() => {
|
|
279
|
+
setOtherBillingAddress(!otherBillingAddress);
|
|
280
|
+
form.resetField('billing_address');
|
|
281
|
+
form.resetField('billing_address.contact_name');
|
|
282
|
+
form.resetField('billing_address.contact_email');
|
|
283
|
+
form.resetField('billing_address.company_name');
|
|
284
|
+
form.resetField('billing_address.identification_number');
|
|
285
|
+
form.resetField('billing_address.address');
|
|
286
|
+
form.resetField('billing_address.postcode');
|
|
287
|
+
form.resetField('billing_address.city');
|
|
288
|
+
form.resetField('billing_address.country');
|
|
289
|
+
form.clearErrors();
|
|
290
|
+
}}
|
|
279
291
|
checked={otherBillingAddress}
|
|
280
292
|
/>
|
|
281
293
|
</div>
|
|
@@ -291,6 +303,13 @@ export const StepContent = ({
|
|
|
291
303
|
className="field"
|
|
292
304
|
{...register('billing_address.contact_email')}
|
|
293
305
|
label={intl.formatMessage(messages.contactEmail)}
|
|
306
|
+
state={formState.errors.billing_address?.contact_email?.message ? 'error' : 'default'}
|
|
307
|
+
text={
|
|
308
|
+
getLocalizedCunninghamErrorProp(
|
|
309
|
+
intl,
|
|
310
|
+
formState.errors.billing_address?.contact_email?.message,
|
|
311
|
+
).text
|
|
312
|
+
}
|
|
294
313
|
/>
|
|
295
314
|
<Input
|
|
296
315
|
className="field"
|
|
@@ -497,21 +516,11 @@ export const StepContent = ({
|
|
|
497
516
|
<Input
|
|
498
517
|
{...register('funding_entity')}
|
|
499
518
|
label={intl.formatMessage(messages.fundingEntityName)}
|
|
500
|
-
required
|
|
501
|
-
state={formState.errors.funding_entity?.message ? 'error' : 'default'}
|
|
502
|
-
text={
|
|
503
|
-
getLocalizedCunninghamErrorProp(intl, formState.errors.funding_entity?.message).text
|
|
504
|
-
}
|
|
505
519
|
/>
|
|
506
520
|
<Input
|
|
507
521
|
{...register('funding_amount')}
|
|
508
522
|
type="number"
|
|
509
523
|
label={intl.formatMessage(messages.fundingEntityAmount)}
|
|
510
|
-
required
|
|
511
|
-
state={formState.errors.funding_amount?.message ? 'error' : 'default'}
|
|
512
|
-
text={
|
|
513
|
-
getLocalizedCunninghamErrorProp(intl, formState.errors.funding_amount?.message).text
|
|
514
|
-
}
|
|
515
524
|
/>
|
|
516
525
|
</div>
|
|
517
526
|
<FormattedMessage {...messages.recommandation} />
|
|
@@ -67,6 +67,13 @@ const messages = defineMessages({
|
|
|
67
67
|
defaultMessage: 'Some required fields are missing in the form.',
|
|
68
68
|
description: 'Some required fields are missing in the form.',
|
|
69
69
|
},
|
|
70
|
+
errorBatchOrderMaxOrders: {
|
|
71
|
+
id: 'components.SubscriptionButton.errorBatchOrderMaxOrders',
|
|
72
|
+
defaultMessage:
|
|
73
|
+
'Unable to create the order: the maximum number of available seats for this offering has been reached. Please contact support for more information.',
|
|
74
|
+
description:
|
|
75
|
+
'Error message shown when batch order creation fails because maximum number of orders is reached by an active offering rule.',
|
|
76
|
+
},
|
|
70
77
|
});
|
|
71
78
|
|
|
72
79
|
enum ComponentStates {
|
|
@@ -172,7 +179,11 @@ const SubscriptionButton = ({ buildOrderPayload }: Props) => {
|
|
|
172
179
|
return;
|
|
173
180
|
}
|
|
174
181
|
batchOrderMethods.create(batchOrder, {
|
|
175
|
-
onError: async () => {
|
|
182
|
+
onError: async (createBatchOrderError: HttpError) => {
|
|
183
|
+
if (createBatchOrderError.code === 422) {
|
|
184
|
+
handleError(SubscriptionErrorMessageId.ERROR_BATCH_ORDER_MAX_ORDERS);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
176
187
|
handleError();
|
|
177
188
|
},
|
|
178
189
|
onSuccess: async (createdBatchOrder: any) => {
|
|
@@ -353,4 +353,158 @@ describe('SaleTunnel', () => {
|
|
|
353
353
|
|
|
354
354
|
screen.getByText('Subscription confirmed!');
|
|
355
355
|
}, 10000);
|
|
356
|
+
|
|
357
|
+
it('should display the appropriate error message when there are not enough seats available', async () => {
|
|
358
|
+
const course = PacedCourseFactory().one();
|
|
359
|
+
const product = ProductFactory().one();
|
|
360
|
+
const offering = OfferingFactory({ course, product, is_withdrawable: false }).one();
|
|
361
|
+
const paymentPlan = PaymentPlanFactory().one();
|
|
362
|
+
const offeringOrganization = OfferingBatchOrderFactory({
|
|
363
|
+
product: { id: product.id, title: product.title },
|
|
364
|
+
}).one();
|
|
365
|
+
|
|
366
|
+
fetchMock.get(
|
|
367
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/`,
|
|
368
|
+
offering,
|
|
369
|
+
);
|
|
370
|
+
fetchMock.get(
|
|
371
|
+
`https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${product.id}/payment-plan/`,
|
|
372
|
+
paymentPlan,
|
|
373
|
+
);
|
|
374
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/enrollments/`, []);
|
|
375
|
+
fetchMock.get(
|
|
376
|
+
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify({
|
|
377
|
+
product_id: product.id,
|
|
378
|
+
course_code: course.code,
|
|
379
|
+
state: NOT_CANCELED_ORDER_STATES,
|
|
380
|
+
})}`,
|
|
381
|
+
[],
|
|
382
|
+
);
|
|
383
|
+
fetchMock.get(
|
|
384
|
+
`https://joanie.endpoint/api/v1.0/offerings/${offering.id}/get-organizations/`,
|
|
385
|
+
offeringOrganization,
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
render(<CourseProductItem productId={product.id} course={course} />, {
|
|
389
|
+
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Verify product info
|
|
393
|
+
await screen.findByRole('heading', { level: 3, name: product.title });
|
|
394
|
+
await screen.findByText(formatPrice(product.price_currency, product.price));
|
|
395
|
+
expect(screen.queryByText('Purchased')).not.toBeInTheDocument();
|
|
396
|
+
|
|
397
|
+
const user = userEvent.setup();
|
|
398
|
+
const buyButton = screen.getByRole('button', { name: product.call_to_action });
|
|
399
|
+
|
|
400
|
+
expect(screen.queryByTestId('generic-sale-tunnel-payment-step')).not.toBeInTheDocument();
|
|
401
|
+
await user.click(buyButton);
|
|
402
|
+
await screen.findByTestId('generic-sale-tunnel-payment-step');
|
|
403
|
+
|
|
404
|
+
// Verify learning path
|
|
405
|
+
await screen.findByText('Your learning path');
|
|
406
|
+
const targetCourses = await screen.findAllByTestId('product-target-course');
|
|
407
|
+
expect(targetCourses).toHaveLength(product.target_courses.length);
|
|
408
|
+
targetCourses.forEach((targetCourse, index) => {
|
|
409
|
+
const courseItem = product.target_courses[index];
|
|
410
|
+
const courseDetail = within(targetCourse).getByTestId(
|
|
411
|
+
`target-course-detail-${courseItem.code}`,
|
|
412
|
+
);
|
|
413
|
+
const summary = courseDetail.querySelector('summary')!;
|
|
414
|
+
expect(summary).toHaveTextContent(courseItem.title);
|
|
415
|
+
|
|
416
|
+
const courseRuns = targetCourse.querySelectorAll(
|
|
417
|
+
'.product-detail-row__course-run-dates__item',
|
|
418
|
+
);
|
|
419
|
+
const openedCourseRuns = courseItem.course_runs.filter(
|
|
420
|
+
(cr: CourseRun) => cr.state.priority <= Priority.FUTURE_NOT_YET_OPEN,
|
|
421
|
+
);
|
|
422
|
+
expect(courseRuns).toHaveLength(openedCourseRuns.length);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Select group buy form
|
|
426
|
+
await screen.findByText('Purchase type');
|
|
427
|
+
const formTypeSelect = screen.getByRole('combobox', { name: 'Purchase type' });
|
|
428
|
+
const menu: HTMLDivElement = screen.getByRole('listbox', { name: 'Purchase type' });
|
|
429
|
+
expectMenuToBeClosed(menu);
|
|
430
|
+
await user.click(formTypeSelect);
|
|
431
|
+
expectMenuToBeOpen(menu);
|
|
432
|
+
await user.click(screen.getByText('Group purchase (B2B)'));
|
|
433
|
+
|
|
434
|
+
// Company step
|
|
435
|
+
const $companyName = await screen.findByRole('textbox', { name: 'Company name' });
|
|
436
|
+
const $idNumber = screen.getByRole('textbox', { name: /Identification number/ });
|
|
437
|
+
const $address = screen.getByRole('textbox', { name: 'Address' });
|
|
438
|
+
const $postCode = screen.getByRole('textbox', { name: 'Post code' });
|
|
439
|
+
const $city = screen.getByRole('textbox', { name: 'City' });
|
|
440
|
+
const $country = screen.getByRole('combobox', { name: 'Country' });
|
|
441
|
+
|
|
442
|
+
await user.type($companyName, 'GIP-FUN');
|
|
443
|
+
await user.type($idNumber, '789 242 229 01694');
|
|
444
|
+
await user.type($address, '61 Bis Rue de la Glaciere');
|
|
445
|
+
await user.type($postCode, '75013');
|
|
446
|
+
await user.type($city, 'Paris');
|
|
447
|
+
|
|
448
|
+
const countryMenu: HTMLDivElement = screen.getByRole('listbox', { name: 'Country' });
|
|
449
|
+
await user.click($country);
|
|
450
|
+
expectMenuToBeOpen(countryMenu);
|
|
451
|
+
await user.click(screen.getByText('France'));
|
|
452
|
+
|
|
453
|
+
expect($companyName).toHaveValue('GIP-FUN');
|
|
454
|
+
const visibleValue = $country.querySelector('.c__select__inner__value span');
|
|
455
|
+
expect(visibleValue!.textContent).toBe('France');
|
|
456
|
+
|
|
457
|
+
// Follow-up step
|
|
458
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
459
|
+
const $lastName = await screen.findByRole('textbox', { name: 'Last name' });
|
|
460
|
+
const $firstName = screen.getByRole('textbox', { name: 'First name' });
|
|
461
|
+
const $role = screen.getByRole('textbox', { name: 'Role' });
|
|
462
|
+
const $email = screen.getByRole('textbox', { name: 'Email' });
|
|
463
|
+
const $phone = screen.getByRole('textbox', { name: 'Phone' });
|
|
464
|
+
|
|
465
|
+
await user.type($lastName, 'Doe');
|
|
466
|
+
await user.type($firstName, 'John');
|
|
467
|
+
await user.type($role, 'HR');
|
|
468
|
+
await user.type($email, 'john.doe@fun-mooc.com');
|
|
469
|
+
await user.type($phone, '+338203920103');
|
|
470
|
+
|
|
471
|
+
expect($lastName).toHaveValue('Doe');
|
|
472
|
+
expect($email).toHaveValue('john.doe@fun-mooc.com');
|
|
473
|
+
|
|
474
|
+
// Signatory step
|
|
475
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
476
|
+
const $signatoryLastName = await screen.findByRole('textbox', { name: 'Last name' });
|
|
477
|
+
const $signatoryFirstName = screen.getByRole('textbox', { name: 'First name' });
|
|
478
|
+
const $signatoryRole = screen.getByRole('textbox', { name: 'Role' });
|
|
479
|
+
const $signatoryEmail = screen.getByRole('textbox', { name: 'Email' });
|
|
480
|
+
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone' });
|
|
481
|
+
|
|
482
|
+
await user.type($signatoryLastName, 'Doe');
|
|
483
|
+
await user.type($signatoryFirstName, 'John');
|
|
484
|
+
await user.type($signatoryRole, 'CEO');
|
|
485
|
+
await user.type($signatoryEmail, 'john.doe@fun-mooc.com');
|
|
486
|
+
await user.type($signatoryPhone, '+338203920103');
|
|
487
|
+
|
|
488
|
+
// Participants step
|
|
489
|
+
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
490
|
+
const $nbParticipants = await screen.findByLabelText('How many participants ?');
|
|
491
|
+
await user.type($nbParticipants, '13');
|
|
492
|
+
expect($nbParticipants).toHaveValue(13);
|
|
493
|
+
|
|
494
|
+
fetchMock.post('https://joanie.endpoint/api/v1.0/batch-orders/', {
|
|
495
|
+
status: 422,
|
|
496
|
+
body: {
|
|
497
|
+
__all__: ['Maximum number of orders reached for product Credential Product'],
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const $subscribeButton = screen.getByRole('button', {
|
|
502
|
+
name: `Subscribe`,
|
|
503
|
+
}) as HTMLButtonElement;
|
|
504
|
+
await user.click($subscribeButton);
|
|
505
|
+
|
|
506
|
+
await screen.findByText(
|
|
507
|
+
'Unable to create the order: the maximum number of available seats for this offering has been reached. Please contact support for more information.',
|
|
508
|
+
);
|
|
509
|
+
}, 30000);
|
|
356
510
|
});
|
|
@@ -2,7 +2,7 @@ import fetchMock from 'fetch-mock';
|
|
|
2
2
|
import { cleanup, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent, { UserEvent } from '@testing-library/user-event';
|
|
4
4
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
5
|
-
import { OrganizationQuoteFactory } from 'utils/test/factories/joanie';
|
|
5
|
+
import { OrganizationFactory, OrganizationQuoteFactory } from 'utils/test/factories/joanie';
|
|
6
6
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
7
7
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
8
8
|
import { render } from 'utils/test/render';
|
|
@@ -35,6 +35,17 @@ describe('full process for the organization quotes dashboard', () => {
|
|
|
35
35
|
it('should works with the full process workflow for any payment methods', async () => {
|
|
36
36
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
37
37
|
|
|
38
|
+
const organization = OrganizationFactory({
|
|
39
|
+
abilities: {
|
|
40
|
+
can_submit_for_signature_batch_order: true,
|
|
41
|
+
confirm_bank_transfer: true,
|
|
42
|
+
confirm_quote: true,
|
|
43
|
+
download_quote: true,
|
|
44
|
+
sign_contracts: true,
|
|
45
|
+
},
|
|
46
|
+
}).one();
|
|
47
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', organization);
|
|
48
|
+
|
|
38
49
|
const quoteQuoted = OrganizationQuoteFactory({
|
|
39
50
|
batch_order: {
|
|
40
51
|
state: BatchOrderState.QUOTED,
|
|
@@ -129,7 +140,6 @@ describe('full process for the organization quotes dashboard', () => {
|
|
|
129
140
|
await user.click(toggle);
|
|
130
141
|
expect(card).toHaveClass('dashboard-card--opened');
|
|
131
142
|
|
|
132
|
-
// Download quote
|
|
133
143
|
const downloadQuoteButton = await screen.findByRole('button', {
|
|
134
144
|
name: /Download quote/i,
|
|
135
145
|
});
|
|
@@ -147,7 +157,7 @@ describe('full process for the organization quotes dashboard', () => {
|
|
|
147
157
|
|
|
148
158
|
// Second step : to sign quote
|
|
149
159
|
const sendForSignatureButton = await screen.findByRole('button', {
|
|
150
|
-
name: /send
|
|
160
|
+
name: /send contract for signature/i,
|
|
151
161
|
});
|
|
152
162
|
expect(sendForSignatureButton).toBeVisible();
|
|
153
163
|
await user.click(sendForSignatureButton);
|
|
@@ -191,4 +201,133 @@ describe('full process for the organization quotes dashboard', () => {
|
|
|
191
201
|
expect(processPaymentButton).toBeVisible();
|
|
192
202
|
expect(processPaymentButton).toBeDisabled();
|
|
193
203
|
});
|
|
204
|
+
|
|
205
|
+
it('should work with purchase order payment method workflow', async () => {
|
|
206
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
207
|
+
const organization = OrganizationFactory({
|
|
208
|
+
abilities: {
|
|
209
|
+
can_submit_for_signature_batch_order: true,
|
|
210
|
+
confirm_bank_transfer: true,
|
|
211
|
+
confirm_quote: true,
|
|
212
|
+
download_quote: true,
|
|
213
|
+
sign_contracts: true,
|
|
214
|
+
},
|
|
215
|
+
}).one();
|
|
216
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', organization);
|
|
217
|
+
|
|
218
|
+
const quoteQuoted = OrganizationQuoteFactory({
|
|
219
|
+
batch_order: {
|
|
220
|
+
state: BatchOrderState.QUOTED,
|
|
221
|
+
payment_method: PaymentMethod.PURCHASE_ORDER,
|
|
222
|
+
available_actions: { next_action: 'confirm_quote' },
|
|
223
|
+
},
|
|
224
|
+
organization_signed_on: undefined,
|
|
225
|
+
}).one();
|
|
226
|
+
|
|
227
|
+
const quotePurchaseOrder = OrganizationQuoteFactory({
|
|
228
|
+
id: quoteQuoted.id,
|
|
229
|
+
batch_order: {
|
|
230
|
+
state: BatchOrderState.QUOTED,
|
|
231
|
+
payment_method: PaymentMethod.PURCHASE_ORDER,
|
|
232
|
+
available_actions: { next_action: 'confirm_purchase_order' },
|
|
233
|
+
},
|
|
234
|
+
}).one();
|
|
235
|
+
|
|
236
|
+
const quoteSendForSign = OrganizationQuoteFactory({
|
|
237
|
+
id: quoteQuoted.id,
|
|
238
|
+
batch_order: {
|
|
239
|
+
state: BatchOrderState.TO_SIGN,
|
|
240
|
+
payment_method: PaymentMethod.PURCHASE_ORDER,
|
|
241
|
+
contract_submitted: false,
|
|
242
|
+
available_actions: { next_action: 'submit_for_signature' },
|
|
243
|
+
},
|
|
244
|
+
}).one();
|
|
245
|
+
|
|
246
|
+
const quoteCompleted = OrganizationQuoteFactory({
|
|
247
|
+
id: quoteQuoted.id,
|
|
248
|
+
batch_order: {
|
|
249
|
+
state: BatchOrderState.COMPLETED,
|
|
250
|
+
payment_method: PaymentMethod.PURCHASE_ORDER,
|
|
251
|
+
},
|
|
252
|
+
}).one();
|
|
253
|
+
|
|
254
|
+
const quotesResponses = [
|
|
255
|
+
{ results: [quoteQuoted], count: 1, previous: null, next: null },
|
|
256
|
+
{ results: [quotePurchaseOrder], count: 1, previous: null, next: null },
|
|
257
|
+
{ results: [quoteSendForSign], count: 1, previous: null, next: null },
|
|
258
|
+
{ results: [quoteCompleted], count: 1, previous: null, next: null },
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
fetchMock.get(
|
|
262
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10`,
|
|
263
|
+
() => quotesResponses.shift(),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
fetchMock.patch(`https://joanie.endpoint/api/v1.0/organizations/1/confirm-quote/`, 200);
|
|
267
|
+
fetchMock.patch(
|
|
268
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/confirm-purchase-order/`,
|
|
269
|
+
200,
|
|
270
|
+
);
|
|
271
|
+
fetchMock.post(
|
|
272
|
+
`https://joanie.endpoint/api/v1.0/organizations/1/submit-for-signature-batch-order/`,
|
|
273
|
+
200,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
render(<TeacherDashboardOrganizationQuotes />, {
|
|
277
|
+
routerOptions: {
|
|
278
|
+
path: '/organizations/:organizationId/quotes',
|
|
279
|
+
initialEntries: ['/organizations/1/quotes'],
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await expectNoSpinner();
|
|
284
|
+
|
|
285
|
+
// First step: confirm quote
|
|
286
|
+
const confirmQuoteButton = await screen.findByRole('button', { name: /confirm quote/i });
|
|
287
|
+
expect(confirmQuoteButton).toBeVisible();
|
|
288
|
+
await user.click(confirmQuoteButton);
|
|
289
|
+
await screen.findByText(/total amount/i);
|
|
290
|
+
await user.type(screen.getByLabelText(/total amount/i), '1000');
|
|
291
|
+
await user.click(screen.getByRole('button', { name: /confirm quote/i }));
|
|
292
|
+
|
|
293
|
+
// Second step: confirm purchase order with reference via modal
|
|
294
|
+
const confirmPurchaseOrderButton = await screen.findByRole('button', {
|
|
295
|
+
name: /confirm receipt of purchase order/i,
|
|
296
|
+
});
|
|
297
|
+
expect(confirmPurchaseOrderButton).toBeVisible();
|
|
298
|
+
await user.click(confirmPurchaseOrderButton);
|
|
299
|
+
|
|
300
|
+
// Modal should open with purchase order reference input
|
|
301
|
+
const modalTitle = await screen.findByText(/confirm purchase order/i);
|
|
302
|
+
expect(modalTitle).toBeInTheDocument();
|
|
303
|
+
|
|
304
|
+
const referenceInput = screen.getByLabelText(/purchase order reference/i);
|
|
305
|
+
expect(referenceInput).toBeInTheDocument();
|
|
306
|
+
|
|
307
|
+
// Type reference and confirm
|
|
308
|
+
await user.type(referenceInput, 'this-is-a-reference');
|
|
309
|
+
await user.click(screen.getByRole('button', { name: /confirm receipt of purchase order/i }));
|
|
310
|
+
|
|
311
|
+
// Third step: send for signature
|
|
312
|
+
const sendForSignatureButton = await screen.findByRole('button', {
|
|
313
|
+
name: /send contract for signature/i,
|
|
314
|
+
});
|
|
315
|
+
expect(sendForSignatureButton).toBeVisible();
|
|
316
|
+
await user.click(sendForSignatureButton);
|
|
317
|
+
|
|
318
|
+
// Verify completed state
|
|
319
|
+
cleanup();
|
|
320
|
+
render(<TeacherDashboardOrganizationQuotes />, {
|
|
321
|
+
routerOptions: {
|
|
322
|
+
path: '/organizations/:organizationId/quotes',
|
|
323
|
+
initialEntries: ['/organizations/1/quotes'],
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
await expectNoSpinner();
|
|
327
|
+
|
|
328
|
+
// No action button should be visible for completed quote
|
|
329
|
+
expect(
|
|
330
|
+
screen.queryByRole('button', { name: /confirm receipt of purchase order/i }),
|
|
331
|
+
).not.toBeInTheDocument();
|
|
332
|
+
});
|
|
194
333
|
});
|
|
@@ -2,11 +2,12 @@ import fetchMock from 'fetch-mock';
|
|
|
2
2
|
import { screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import userEvent, { UserEvent } from '@testing-library/user-event';
|
|
4
4
|
import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
|
|
5
|
-
import { OrganizationQuoteFactory } from 'utils/test/factories/joanie';
|
|
5
|
+
import { OrganizationFactory, OrganizationQuoteFactory } from 'utils/test/factories/joanie';
|
|
6
6
|
import { expectNoSpinner } from 'utils/test/expectSpinner';
|
|
7
7
|
import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
|
|
8
8
|
import { render } from 'utils/test/render';
|
|
9
9
|
import { expectBannerInfo, expectBannerError } from 'utils/test/expectBanner';
|
|
10
|
+
import { BatchOrderState } from 'types/Joanie';
|
|
10
11
|
import TeacherDashboardOrganizationQuotes from '.';
|
|
11
12
|
|
|
12
13
|
let user: UserEvent;
|
|
@@ -29,6 +30,9 @@ describe('pages/TeacherDashboardOrganizationQuotes', () => {
|
|
|
29
30
|
it('should render a list of quotes for an organization', async () => {
|
|
30
31
|
const quoteList = OrganizationQuoteFactory().many(1);
|
|
31
32
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
33
|
+
|
|
34
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', []);
|
|
35
|
+
|
|
32
36
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10`, {
|
|
33
37
|
results: quoteList,
|
|
34
38
|
count: 0,
|
|
@@ -59,6 +63,9 @@ describe('pages/TeacherDashboardOrganizationQuotes', () => {
|
|
|
59
63
|
|
|
60
64
|
it('should render an empty list of quotes for an organization', async () => {
|
|
61
65
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
66
|
+
|
|
67
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', []);
|
|
68
|
+
|
|
62
69
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10`, {
|
|
63
70
|
results: [],
|
|
64
71
|
count: 0,
|
|
@@ -81,6 +88,8 @@ describe('pages/TeacherDashboardOrganizationQuotes', () => {
|
|
|
81
88
|
const quoteList = OrganizationQuoteFactory().many(30);
|
|
82
89
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
83
90
|
|
|
91
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', []);
|
|
92
|
+
|
|
84
93
|
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10`, {
|
|
85
94
|
results: quoteList.slice(0, 10),
|
|
86
95
|
count: 30,
|
|
@@ -126,6 +135,9 @@ describe('pages/TeacherDashboardOrganizationQuotes', () => {
|
|
|
126
135
|
|
|
127
136
|
it('should display an error when API fails', async () => {
|
|
128
137
|
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/', []);
|
|
138
|
+
|
|
139
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', []);
|
|
140
|
+
|
|
129
141
|
fetchMock.get(
|
|
130
142
|
'https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10',
|
|
131
143
|
500,
|
|
@@ -141,4 +153,49 @@ describe('pages/TeacherDashboardOrganizationQuotes', () => {
|
|
|
141
153
|
await expectNoSpinner();
|
|
142
154
|
await expectBannerError('An error occurred while fetching resources. Please retry later.');
|
|
143
155
|
});
|
|
156
|
+
|
|
157
|
+
it('should render disabled buttons when the user is not allowed', async () => {
|
|
158
|
+
const quoteQuoted = OrganizationQuoteFactory({
|
|
159
|
+
batch_order: {
|
|
160
|
+
state: BatchOrderState.QUOTED,
|
|
161
|
+
available_actions: { next_action: 'confirm_quote' },
|
|
162
|
+
},
|
|
163
|
+
}).one();
|
|
164
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/`, []);
|
|
165
|
+
|
|
166
|
+
const organization = OrganizationFactory({
|
|
167
|
+
abilities: {
|
|
168
|
+
can_submit_for_signature_batch_order: false,
|
|
169
|
+
confirm_bank_transfer: false,
|
|
170
|
+
confirm_quote: false,
|
|
171
|
+
download_quote: true,
|
|
172
|
+
sign_contracts: false,
|
|
173
|
+
},
|
|
174
|
+
}).one();
|
|
175
|
+
fetchMock.get('https://joanie.endpoint/api/v1.0/organizations/1/', organization);
|
|
176
|
+
|
|
177
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/organizations/1/quotes/?page=1&page_size=10`, {
|
|
178
|
+
results: [quoteQuoted],
|
|
179
|
+
count: 1,
|
|
180
|
+
previous: null,
|
|
181
|
+
next: null,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
render(<TeacherDashboardOrganizationQuotes />, {
|
|
185
|
+
routerOptions: {
|
|
186
|
+
path: '/organizations/:organizationId/quotes',
|
|
187
|
+
initialEntries: ['/organizations/1/quotes'],
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await expectNoSpinner();
|
|
192
|
+
|
|
193
|
+
const downloadQuoteButton = await screen.findByRole('button', { name: /Download quote/i });
|
|
194
|
+
expect(downloadQuoteButton).toBeVisible();
|
|
195
|
+
expect(downloadQuoteButton).not.toBeDisabled();
|
|
196
|
+
|
|
197
|
+
const confirmQuoteButton = await screen.findByRole('button', { name: /Confirm quote/i });
|
|
198
|
+
expect(confirmQuoteButton).toBeVisible();
|
|
199
|
+
expect(confirmQuoteButton).toBeDisabled();
|
|
200
|
+
});
|
|
144
201
|
});
|
|
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
|
|
4
4
|
import { useParams } from 'react-router';
|
|
5
5
|
import Banner, { BannerType } from 'components/Banner';
|
|
6
6
|
import { useOrganizationsQuotes } from 'hooks/useOrganizationQuotes';
|
|
7
|
+
import { useOrganization } from 'hooks/useOrganizations';
|
|
7
8
|
import { TeacherDashboardContractsParams } from 'pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters';
|
|
8
9
|
import { BatchOrderState, OrganizationQuote } from 'types/Joanie';
|
|
9
10
|
import { PaymentMethod } from 'components/PaymentInterfaces/types';
|
|
@@ -65,15 +66,25 @@ const messages = defineMessages({
|
|
|
65
66
|
id: 'components.OrganizationQuotesTable.confirmPurchaseOrder',
|
|
66
67
|
description: 'Label for confirming receipt of a purchase order',
|
|
67
68
|
},
|
|
69
|
+
purchaseOrderModalTitle: {
|
|
70
|
+
defaultMessage: 'Confirm purchase order',
|
|
71
|
+
id: 'components.OrganizationQuotesTable.purchaseOrderModalTitle',
|
|
72
|
+
description: 'Title of the confirm purchase order modal',
|
|
73
|
+
},
|
|
74
|
+
purchaseOrderReferenceLabel: {
|
|
75
|
+
defaultMessage: 'Purchase order reference',
|
|
76
|
+
id: 'components.OrganizationQuotesTable.purchaseOrderReferenceLabel',
|
|
77
|
+
description: 'Label for the purchase order reference input',
|
|
78
|
+
},
|
|
68
79
|
confirmBank: {
|
|
69
80
|
defaultMessage: 'Confirm bank transfer',
|
|
70
81
|
id: 'components.OrganizationQuotesTable.confirmBank',
|
|
71
82
|
description: 'Label for confirming a bank transfer',
|
|
72
83
|
},
|
|
73
84
|
sendForSignature: {
|
|
74
|
-
defaultMessage: 'Send
|
|
85
|
+
defaultMessage: 'Send contract for signature',
|
|
75
86
|
id: 'components.OrganizationQuotesTable.sendForSignature',
|
|
76
|
-
description: 'Action label to send a
|
|
87
|
+
description: 'Action label to send a contract for signature',
|
|
77
88
|
},
|
|
78
89
|
waitingSignature: {
|
|
79
90
|
defaultMessage: 'Waiting signature',
|
|
@@ -215,6 +226,13 @@ const messages = defineMessages({
|
|
|
215
226
|
const TeacherDashboardOrganizationQuotes = () => {
|
|
216
227
|
const intl = useIntl();
|
|
217
228
|
const { organizationId: routeOrganizationId } = useParams<TeacherDashboardContractsParams>();
|
|
229
|
+
const {
|
|
230
|
+
item: organization,
|
|
231
|
+
states: { isPending: isOrganizationPending },
|
|
232
|
+
} = useOrganization(routeOrganizationId);
|
|
233
|
+
|
|
234
|
+
const abilities = organization?.abilities;
|
|
235
|
+
|
|
218
236
|
const pagination = usePagination({ itemsPerPage: 10 });
|
|
219
237
|
|
|
220
238
|
const {
|
|
@@ -241,6 +259,11 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
241
259
|
const [amount, setAmount] = useState('');
|
|
242
260
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
243
261
|
|
|
262
|
+
const [selectedPurchaseOrderQuote, setSelectedPurchaseOrderQuote] =
|
|
263
|
+
useState<OrganizationQuote | null>(null);
|
|
264
|
+
const [purchaseOrderReference, setPurchaseOrderReference] = useState('');
|
|
265
|
+
const [isPurchaseOrderModalOpen, setIsPurchaseOrderModalOpen] = useState(false);
|
|
266
|
+
|
|
244
267
|
useEffect(() => {
|
|
245
268
|
if (meta?.pagination?.count) {
|
|
246
269
|
pagination.setItemsCount(meta.pagination.count);
|
|
@@ -249,7 +272,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
249
272
|
|
|
250
273
|
if (error) return <Banner message={error} type={BannerType.ERROR} rounded />;
|
|
251
274
|
|
|
252
|
-
if (isPending)
|
|
275
|
+
if (isPending || isOrganizationPending)
|
|
253
276
|
return (
|
|
254
277
|
<Spinner size="large">
|
|
255
278
|
<span id="loading-contract-data">
|
|
@@ -286,11 +309,27 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
286
309
|
await invalidate();
|
|
287
310
|
};
|
|
288
311
|
|
|
289
|
-
const
|
|
312
|
+
const handleOpenPurchaseOrderModal = (quote: OrganizationQuote) => {
|
|
313
|
+
setSelectedPurchaseOrderQuote(quote);
|
|
314
|
+
setIsPurchaseOrderModalOpen(true);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const handleCancelPurchaseOrder = () => {
|
|
318
|
+
setIsPurchaseOrderModalOpen(false);
|
|
319
|
+
setPurchaseOrderReference('');
|
|
320
|
+
setSelectedPurchaseOrderQuote(null);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const handleConfirmPurchaseOrder = async () => {
|
|
324
|
+
if (!selectedPurchaseOrderQuote) return;
|
|
290
325
|
await confirmPurchaseOrder({
|
|
291
326
|
organization_id: routeOrganizationId,
|
|
292
|
-
payload: {
|
|
327
|
+
payload: {
|
|
328
|
+
quote_id: selectedPurchaseOrderQuote.id,
|
|
329
|
+
purchase_order_reference: purchaseOrderReference,
|
|
330
|
+
},
|
|
293
331
|
});
|
|
332
|
+
handleCancelPurchaseOrder();
|
|
294
333
|
await invalidate();
|
|
295
334
|
};
|
|
296
335
|
|
|
@@ -336,6 +375,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
336
375
|
className="mr-2"
|
|
337
376
|
onClick={() => handleDownloadQuote(quote.id)}
|
|
338
377
|
icon={<span className="material-icons">download</span>}
|
|
378
|
+
disabled={!abilities?.download_quote}
|
|
339
379
|
>
|
|
340
380
|
{intl.formatMessage(messages.downloadQuote)}
|
|
341
381
|
</Button>
|
|
@@ -343,6 +383,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
343
383
|
size="small"
|
|
344
384
|
onClick={() => handleOpenConfirm(quote.id)}
|
|
345
385
|
icon={<span className="material-icons">check_circle</span>}
|
|
386
|
+
disabled={!abilities?.confirm_quote}
|
|
346
387
|
>
|
|
347
388
|
{intl.formatMessage(messages.confirmQuote)}
|
|
348
389
|
</Button>
|
|
@@ -353,7 +394,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
353
394
|
<Button
|
|
354
395
|
size="small"
|
|
355
396
|
className="ml-2"
|
|
356
|
-
onClick={() =>
|
|
397
|
+
onClick={() => handleOpenPurchaseOrderModal(quote)}
|
|
357
398
|
icon={<span className="material-icons">description</span>}
|
|
358
399
|
>
|
|
359
400
|
{intl.formatMessage(messages.confirmPurchaseOrder)}
|
|
@@ -365,6 +406,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
365
406
|
size="small"
|
|
366
407
|
onClick={() => handleConfirmBankTransfer(quote.batch_order.id)}
|
|
367
408
|
icon={<span className="material-icons">account_balance</span>}
|
|
409
|
+
disabled={!abilities?.confirm_bank_transfer}
|
|
368
410
|
>
|
|
369
411
|
{intl.formatMessage(messages.confirmBank)}
|
|
370
412
|
</Button>
|
|
@@ -373,7 +415,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
373
415
|
const submitForSignatureButton = (
|
|
374
416
|
<Button
|
|
375
417
|
size="small"
|
|
376
|
-
disabled={batchOrder.contract_submitted}
|
|
418
|
+
disabled={batchOrder.contract_submitted || !abilities?.can_submit_for_signature_batch_order}
|
|
377
419
|
onClick={() =>
|
|
378
420
|
!batchOrder.contract_submitted && handleSubmitForSignature(quote.batch_order.id)
|
|
379
421
|
}
|
|
@@ -514,6 +556,26 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
514
556
|
/>
|
|
515
557
|
</div>
|
|
516
558
|
</Modal>
|
|
559
|
+
<Modal
|
|
560
|
+
isOpen={isPurchaseOrderModalOpen}
|
|
561
|
+
onClose={handleCancelPurchaseOrder}
|
|
562
|
+
title={intl.formatMessage(messages.purchaseOrderModalTitle)}
|
|
563
|
+
size={ModalSize.MEDIUM}
|
|
564
|
+
actions={
|
|
565
|
+
<Button size="small" onClick={handleConfirmPurchaseOrder}>
|
|
566
|
+
{intl.formatMessage(messages.confirmPurchaseOrder)}
|
|
567
|
+
</Button>
|
|
568
|
+
}
|
|
569
|
+
>
|
|
570
|
+
<div className="dashboard__quote__modal">
|
|
571
|
+
<Input
|
|
572
|
+
type="text"
|
|
573
|
+
label={intl.formatMessage(messages.purchaseOrderReferenceLabel)}
|
|
574
|
+
onChange={(e) => setPurchaseOrderReference(e.target.value)}
|
|
575
|
+
value={purchaseOrderReference}
|
|
576
|
+
/>
|
|
577
|
+
</div>
|
|
578
|
+
</Modal>
|
|
517
579
|
</div>
|
|
518
580
|
);
|
|
519
581
|
};
|
package/js/types/Joanie.ts
CHANGED
|
@@ -35,6 +35,18 @@ export interface Organization {
|
|
|
35
35
|
contact_phone: Nullable<string>;
|
|
36
36
|
dpo_email: Nullable<string>;
|
|
37
37
|
address?: Address;
|
|
38
|
+
abilities: {
|
|
39
|
+
can_submit_for_signature_batch_order: boolean;
|
|
40
|
+
confirm_bank_transfer: boolean;
|
|
41
|
+
confirm_quote: boolean;
|
|
42
|
+
delete: boolean;
|
|
43
|
+
download_quote: boolean;
|
|
44
|
+
get: boolean;
|
|
45
|
+
manage_accesses: boolean;
|
|
46
|
+
patch: boolean;
|
|
47
|
+
put: boolean;
|
|
48
|
+
sign_contracts: boolean;
|
|
49
|
+
};
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
export interface OrganizationResourceQuery extends ResourcesQuery {
|
|
@@ -171,6 +171,18 @@ export const OrganizationFactory = factory((): Organization => {
|
|
|
171
171
|
dpo_email: faker.internet.email(),
|
|
172
172
|
contact_phone: faker.phone.number(),
|
|
173
173
|
address: AddressFactory().one(),
|
|
174
|
+
abilities: {
|
|
175
|
+
can_submit_for_signature_batch_order: faker.datatype.boolean(),
|
|
176
|
+
confirm_bank_transfer: faker.datatype.boolean(),
|
|
177
|
+
confirm_quote: faker.datatype.boolean(),
|
|
178
|
+
delete: faker.datatype.boolean(),
|
|
179
|
+
download_quote: faker.datatype.boolean(),
|
|
180
|
+
get: faker.datatype.boolean(),
|
|
181
|
+
manage_accesses: faker.datatype.boolean(),
|
|
182
|
+
patch: faker.datatype.boolean(),
|
|
183
|
+
put: faker.datatype.boolean(),
|
|
184
|
+
sign_contracts: faker.datatype.boolean(),
|
|
185
|
+
},
|
|
174
186
|
};
|
|
175
187
|
});
|
|
176
188
|
|