richie-education 3.2.2-dev36 → 3.2.2-dev38
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/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +3 -3
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +1 -1
- package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +15 -15
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -2
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +12 -12
- package/js/components/SaleTunnel/index.spec.tsx +2 -2
- package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +129 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +54 -3
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ const messages = defineMessages({
|
|
|
21
21
|
description: {
|
|
22
22
|
id: 'components.SaleTunnel.Information.description',
|
|
23
23
|
description: 'Helper text explaining that the information will be used for billing',
|
|
24
|
-
defaultMessage: '
|
|
24
|
+
defaultMessage: 'This information will be used for billing',
|
|
25
25
|
},
|
|
26
26
|
purchaseTypeTitle: {
|
|
27
27
|
id: 'components.SaleTunnel.Information.purchaseTypeTitle',
|
|
@@ -36,12 +36,12 @@ const messages = defineMessages({
|
|
|
36
36
|
purchaseTypeOptionSingle: {
|
|
37
37
|
id: 'components.SaleTunnel.Information.purchaseTypeOptionSingle',
|
|
38
38
|
description: 'Option label for selecting a single purchase (B2C)',
|
|
39
|
-
defaultMessage: '
|
|
39
|
+
defaultMessage: 'I am purchasing as an individual',
|
|
40
40
|
},
|
|
41
41
|
purchaseTypeOptionGroup: {
|
|
42
42
|
id: 'components.SaleTunnel.Information.purchaseTypeOptionGroup',
|
|
43
43
|
description: 'Option label for selecting a group purchase (B2B)',
|
|
44
|
-
defaultMessage: '
|
|
44
|
+
defaultMessage: 'I am purchasing on behalf of an organization',
|
|
45
45
|
},
|
|
46
46
|
stepCompany: {
|
|
47
47
|
id: 'components.SaleTunnel.BatchOrderForm.stepCompany',
|
|
@@ -21,7 +21,7 @@ const messages = defineMessages({
|
|
|
21
21
|
description: {
|
|
22
22
|
id: 'components.SaleTunnel.Information.description',
|
|
23
23
|
description: 'Description of the information section',
|
|
24
|
-
defaultMessage: '
|
|
24
|
+
defaultMessage: 'This information will be used for billing',
|
|
25
25
|
},
|
|
26
26
|
paymentSchedule: {
|
|
27
27
|
id: 'components.SaleTunnel.Information.paymentSchedule',
|
|
@@ -38,7 +38,7 @@ const messages = defineMessages({
|
|
|
38
38
|
id: 'components.SaleTunnel.BatchOrderForm.stepAdminTitle',
|
|
39
39
|
description:
|
|
40
40
|
'Title of the section with details about the person responsible for admin follow-up',
|
|
41
|
-
defaultMessage: '
|
|
41
|
+
defaultMessage: 'Administrative contact',
|
|
42
42
|
},
|
|
43
43
|
stepSignatoryTitle: {
|
|
44
44
|
id: 'components.SaleTunnel.BatchOrderForm.stepSignatoryTitle',
|
|
@@ -49,17 +49,17 @@ const messages = defineMessages({
|
|
|
49
49
|
stepBillingTitle: {
|
|
50
50
|
id: 'components.SaleTunnel.BatchOrderForm.stepBillingTitle',
|
|
51
51
|
description: 'Title of the section with billing details',
|
|
52
|
-
defaultMessage: 'Billing
|
|
52
|
+
defaultMessage: 'Billing details',
|
|
53
53
|
},
|
|
54
54
|
stepParticipantsTitle: {
|
|
55
55
|
id: 'components.SaleTunnel.BatchOrderForm.stepParticipantsTitle',
|
|
56
56
|
description: 'Title of the section to enter the number of registrations/participants',
|
|
57
|
-
defaultMessage: '
|
|
57
|
+
defaultMessage: 'Number of registrations',
|
|
58
58
|
},
|
|
59
59
|
stepFinancingTitle: {
|
|
60
60
|
id: 'components.SaleTunnel.BatchOrderForm.stepFinancingTitle',
|
|
61
61
|
description: 'Title of the section to select the payment plan of the course',
|
|
62
|
-
defaultMessage: '
|
|
62
|
+
defaultMessage: 'Ordering method',
|
|
63
63
|
},
|
|
64
64
|
companyName: {
|
|
65
65
|
id: 'batchOrder.companyName',
|
|
@@ -70,7 +70,7 @@ const messages = defineMessages({
|
|
|
70
70
|
id: 'batchOrder.identificationNumber',
|
|
71
71
|
description:
|
|
72
72
|
'Label for the field asking the company identification number (eg. SIRET in France)',
|
|
73
|
-
defaultMessage: '
|
|
73
|
+
defaultMessage: 'Registration number (SIRET for French companies)',
|
|
74
74
|
},
|
|
75
75
|
vatNumber: {
|
|
76
76
|
id: 'batchOrder.vatNumber',
|
|
@@ -85,7 +85,7 @@ const messages = defineMessages({
|
|
|
85
85
|
postCode: {
|
|
86
86
|
id: 'batchOrder.postCode',
|
|
87
87
|
description: 'Label for the field asking the postal code',
|
|
88
|
-
defaultMessage: '
|
|
88
|
+
defaultMessage: 'Postal code',
|
|
89
89
|
},
|
|
90
90
|
city: {
|
|
91
91
|
id: 'batchOrder.city',
|
|
@@ -120,42 +120,42 @@ const messages = defineMessages({
|
|
|
120
120
|
phone: {
|
|
121
121
|
id: 'batchOrder.phone',
|
|
122
122
|
description: 'Label for the field asking the phone number of the contact person',
|
|
123
|
-
defaultMessage: 'Phone',
|
|
123
|
+
defaultMessage: 'Phone number',
|
|
124
124
|
},
|
|
125
125
|
checkBilling: {
|
|
126
126
|
id: 'components.SaleTunnel.BatchOrderForm.checkBilling',
|
|
127
127
|
description: 'Checkbox label to indicate using alternative billing information',
|
|
128
|
-
defaultMessage: 'Use
|
|
128
|
+
defaultMessage: 'Use different billing information',
|
|
129
129
|
},
|
|
130
130
|
contactName: {
|
|
131
131
|
id: 'batchOrder.contactName',
|
|
132
132
|
description: 'Label for the field asking the billing contact name',
|
|
133
|
-
defaultMessage: '
|
|
133
|
+
defaultMessage: 'Contact name',
|
|
134
134
|
},
|
|
135
135
|
contactEmail: {
|
|
136
136
|
id: 'batchOrder.contactEmail',
|
|
137
137
|
description: 'Label for the field asking the billing contact email',
|
|
138
|
-
defaultMessage: '
|
|
138
|
+
defaultMessage: 'Contact email',
|
|
139
139
|
},
|
|
140
140
|
nbSeats: {
|
|
141
141
|
id: 'batchOrder.nbSeats',
|
|
142
142
|
description: 'Label for the field asking the number of participants/seats',
|
|
143
|
-
defaultMessage: '
|
|
143
|
+
defaultMessage: 'Number of participants to register',
|
|
144
144
|
},
|
|
145
145
|
cardPayment: {
|
|
146
146
|
id: 'batchOrder.cardPayment',
|
|
147
147
|
description: 'Option label for selecting credit card payment',
|
|
148
|
-
defaultMessage: '
|
|
148
|
+
defaultMessage: 'Credit card',
|
|
149
149
|
},
|
|
150
150
|
bankTransfer: {
|
|
151
151
|
id: 'batchOrder.bankTransfer',
|
|
152
152
|
description: 'Option label for selecting bank transfer payment',
|
|
153
|
-
defaultMessage: '
|
|
153
|
+
defaultMessage: 'Bank transfer',
|
|
154
154
|
},
|
|
155
155
|
purchaseOrder: {
|
|
156
156
|
id: 'batchOrder.purchaseOrder',
|
|
157
157
|
description: 'Option label for selecting payment via purchase order',
|
|
158
|
-
defaultMessage: '
|
|
158
|
+
defaultMessage: 'Purchase order',
|
|
159
159
|
},
|
|
160
160
|
withoutOrderForm: {
|
|
161
161
|
id: 'batchOrder.withoutOrderForm',
|
|
@@ -191,7 +191,7 @@ const messages = defineMessages({
|
|
|
191
191
|
participatingOrganisations: {
|
|
192
192
|
id: 'batchOrder.participatingOrganisations',
|
|
193
193
|
description: 'Label for the field listing other participating organisations',
|
|
194
|
-
defaultMessage: 'Participating
|
|
194
|
+
defaultMessage: 'Participating organizations',
|
|
195
195
|
},
|
|
196
196
|
});
|
|
197
197
|
|
|
@@ -19,12 +19,12 @@ const messages = defineMessages({
|
|
|
19
19
|
purchaseTypeOptionSingle: {
|
|
20
20
|
id: 'components.SaleTunnel.Information.purchaseTypeOptionSingle',
|
|
21
21
|
description: 'Label for B2C option',
|
|
22
|
-
defaultMessage: '
|
|
22
|
+
defaultMessage: 'I am purchasing as an individual',
|
|
23
23
|
},
|
|
24
24
|
purchaseTypeOptionGroup: {
|
|
25
25
|
id: 'components.SaleTunnel.Information.purchaseTypeOptionGroup',
|
|
26
26
|
description: 'Label for B2C option',
|
|
27
|
-
defaultMessage: '
|
|
27
|
+
defaultMessage: 'I am purchasing on behalf of an organization',
|
|
28
28
|
},
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -152,13 +152,13 @@ describe('SaleTunnel', () => {
|
|
|
152
152
|
expectMenuToBeClosed(menu);
|
|
153
153
|
await user.click(formTypeSelect);
|
|
154
154
|
expectMenuToBeOpen(menu);
|
|
155
|
-
await user.click(screen.getByText('
|
|
155
|
+
await user.click(screen.getByText('I am purchasing on behalf of an organization'));
|
|
156
156
|
|
|
157
157
|
// Company step
|
|
158
158
|
const $companyName = await screen.findByRole('textbox', { name: 'Company name' });
|
|
159
|
-
const $idNumber = screen.getByRole('textbox', { name: /
|
|
159
|
+
const $idNumber = screen.getByRole('textbox', { name: /Registration number/ });
|
|
160
160
|
const $address = screen.getByRole('textbox', { name: 'Address' });
|
|
161
|
-
const $postCode = screen.getByRole('textbox', { name: '
|
|
161
|
+
const $postCode = screen.getByRole('textbox', { name: 'Postal code' });
|
|
162
162
|
const $city = screen.getByRole('textbox', { name: 'City' });
|
|
163
163
|
const $country = screen.getByRole('combobox', { name: 'Country' });
|
|
164
164
|
|
|
@@ -183,7 +183,7 @@ describe('SaleTunnel', () => {
|
|
|
183
183
|
const $firstName = screen.getByRole('textbox', { name: 'First name' });
|
|
184
184
|
const $role = screen.getByRole('textbox', { name: 'Role' });
|
|
185
185
|
const $email = screen.getByRole('textbox', { name: 'Email' });
|
|
186
|
-
const $phone = screen.getByRole('textbox', { name: 'Phone' });
|
|
186
|
+
const $phone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
187
187
|
|
|
188
188
|
await user.type($lastName, 'Doe');
|
|
189
189
|
await user.type($firstName, 'John');
|
|
@@ -200,7 +200,7 @@ describe('SaleTunnel', () => {
|
|
|
200
200
|
const $signatoryFirstName = screen.getByRole('textbox', { name: 'First name' });
|
|
201
201
|
const $signatoryRole = screen.getByRole('textbox', { name: 'Role' });
|
|
202
202
|
const $signatoryEmail = screen.getByRole('textbox', { name: 'Email' });
|
|
203
|
-
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone' });
|
|
203
|
+
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
204
204
|
|
|
205
205
|
await user.type($signatoryLastName, 'Doe');
|
|
206
206
|
await user.type($signatoryFirstName, 'John');
|
|
@@ -210,7 +210,7 @@ describe('SaleTunnel', () => {
|
|
|
210
210
|
|
|
211
211
|
// Participants step
|
|
212
212
|
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
213
|
-
const $nbParticipants = await screen.findByLabelText('
|
|
213
|
+
const $nbParticipants = await screen.findByLabelText('Number of participants to register');
|
|
214
214
|
await user.type($nbParticipants, '13');
|
|
215
215
|
expect($nbParticipants).toHaveValue(13);
|
|
216
216
|
|
|
@@ -429,13 +429,13 @@ describe('SaleTunnel', () => {
|
|
|
429
429
|
expectMenuToBeClosed(menu);
|
|
430
430
|
await user.click(formTypeSelect);
|
|
431
431
|
expectMenuToBeOpen(menu);
|
|
432
|
-
await user.click(screen.getByText('
|
|
432
|
+
await user.click(screen.getByText('I am purchasing on behalf of an organization'));
|
|
433
433
|
|
|
434
434
|
// Company step
|
|
435
435
|
const $companyName = await screen.findByRole('textbox', { name: 'Company name' });
|
|
436
|
-
const $idNumber = screen.getByRole('textbox', { name: /
|
|
436
|
+
const $idNumber = screen.getByRole('textbox', { name: /Registration number/ });
|
|
437
437
|
const $address = screen.getByRole('textbox', { name: 'Address' });
|
|
438
|
-
const $postCode = screen.getByRole('textbox', { name: '
|
|
438
|
+
const $postCode = screen.getByRole('textbox', { name: 'Postal code' });
|
|
439
439
|
const $city = screen.getByRole('textbox', { name: 'City' });
|
|
440
440
|
const $country = screen.getByRole('combobox', { name: 'Country' });
|
|
441
441
|
|
|
@@ -460,7 +460,7 @@ describe('SaleTunnel', () => {
|
|
|
460
460
|
const $firstName = screen.getByRole('textbox', { name: 'First name' });
|
|
461
461
|
const $role = screen.getByRole('textbox', { name: 'Role' });
|
|
462
462
|
const $email = screen.getByRole('textbox', { name: 'Email' });
|
|
463
|
-
const $phone = screen.getByRole('textbox', { name: 'Phone' });
|
|
463
|
+
const $phone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
464
464
|
|
|
465
465
|
await user.type($lastName, 'Doe');
|
|
466
466
|
await user.type($firstName, 'John');
|
|
@@ -477,7 +477,7 @@ describe('SaleTunnel', () => {
|
|
|
477
477
|
const $signatoryFirstName = screen.getByRole('textbox', { name: 'First name' });
|
|
478
478
|
const $signatoryRole = screen.getByRole('textbox', { name: 'Role' });
|
|
479
479
|
const $signatoryEmail = screen.getByRole('textbox', { name: 'Email' });
|
|
480
|
-
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone' });
|
|
480
|
+
const $signatoryPhone = screen.getByRole('textbox', { name: 'Phone number' });
|
|
481
481
|
|
|
482
482
|
await user.type($signatoryLastName, 'Doe');
|
|
483
483
|
await user.type($signatoryFirstName, 'John');
|
|
@@ -487,7 +487,7 @@ describe('SaleTunnel', () => {
|
|
|
487
487
|
|
|
488
488
|
// Participants step
|
|
489
489
|
await user.click(screen.getByRole('button', { name: 'Next' }));
|
|
490
|
-
const $nbParticipants = await screen.findByLabelText('
|
|
490
|
+
const $nbParticipants = await screen.findByLabelText('Number of participants to register');
|
|
491
491
|
await user.type($nbParticipants, '13');
|
|
492
492
|
expect($nbParticipants).toHaveValue(13);
|
|
493
493
|
|
|
@@ -772,7 +772,7 @@ describe.each([
|
|
|
772
772
|
queryOptions: { client: createTestQueryClient({ user: richieUser }) },
|
|
773
773
|
});
|
|
774
774
|
expect(
|
|
775
|
-
await screen.queryByText('
|
|
775
|
+
await screen.queryByText('This information will be used for billing'),
|
|
776
776
|
).toBeInTheDocument();
|
|
777
777
|
|
|
778
778
|
await user.type(screen.getByLabelText('Voucher code'), 'DISCOUNT100');
|
|
@@ -781,7 +781,7 @@ describe.each([
|
|
|
781
781
|
expect(await screen.findByText('Discount applied')).toBeInTheDocument();
|
|
782
782
|
await waitFor(async () =>
|
|
783
783
|
expect(
|
|
784
|
-
await screen.queryByText('
|
|
784
|
+
await screen.queryByText('This information will be used for billing'),
|
|
785
785
|
).not.toBeInTheDocument(),
|
|
786
786
|
);
|
|
787
787
|
expect(await screen.queryByTestId('withdraw-right-checkbox')).not.toBeInTheDocument();
|
|
@@ -201,4 +201,133 @@ describe('full process for the organization quotes dashboard', () => {
|
|
|
201
201
|
expect(processPaymentButton).toBeVisible();
|
|
202
202
|
expect(processPaymentButton).toBeDisabled();
|
|
203
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
|
+
});
|
|
204
333
|
});
|
|
@@ -66,6 +66,16 @@ const messages = defineMessages({
|
|
|
66
66
|
id: 'components.OrganizationQuotesTable.confirmPurchaseOrder',
|
|
67
67
|
description: 'Label for confirming receipt of a purchase order',
|
|
68
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
|
+
},
|
|
69
79
|
confirmBank: {
|
|
70
80
|
defaultMessage: 'Confirm bank transfer',
|
|
71
81
|
id: 'components.OrganizationQuotesTable.confirmBank',
|
|
@@ -249,6 +259,11 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
249
259
|
const [amount, setAmount] = useState('');
|
|
250
260
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
251
261
|
|
|
262
|
+
const [selectedPurchaseOrderQuote, setSelectedPurchaseOrderQuote] =
|
|
263
|
+
useState<OrganizationQuote | null>(null);
|
|
264
|
+
const [purchaseOrderReference, setPurchaseOrderReference] = useState('');
|
|
265
|
+
const [isPurchaseOrderModalOpen, setIsPurchaseOrderModalOpen] = useState(false);
|
|
266
|
+
|
|
252
267
|
useEffect(() => {
|
|
253
268
|
if (meta?.pagination?.count) {
|
|
254
269
|
pagination.setItemsCount(meta.pagination.count);
|
|
@@ -294,11 +309,27 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
294
309
|
await invalidate();
|
|
295
310
|
};
|
|
296
311
|
|
|
297
|
-
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;
|
|
298
325
|
await confirmPurchaseOrder({
|
|
299
326
|
organization_id: routeOrganizationId,
|
|
300
|
-
payload: {
|
|
327
|
+
payload: {
|
|
328
|
+
quote_id: selectedPurchaseOrderQuote.id,
|
|
329
|
+
purchase_order_reference: purchaseOrderReference,
|
|
330
|
+
},
|
|
301
331
|
});
|
|
332
|
+
handleCancelPurchaseOrder();
|
|
302
333
|
await invalidate();
|
|
303
334
|
};
|
|
304
335
|
|
|
@@ -363,7 +394,7 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
363
394
|
<Button
|
|
364
395
|
size="small"
|
|
365
396
|
className="ml-2"
|
|
366
|
-
onClick={() =>
|
|
397
|
+
onClick={() => handleOpenPurchaseOrderModal(quote)}
|
|
367
398
|
icon={<span className="material-icons">description</span>}
|
|
368
399
|
>
|
|
369
400
|
{intl.formatMessage(messages.confirmPurchaseOrder)}
|
|
@@ -525,6 +556,26 @@ const TeacherDashboardOrganizationQuotes = () => {
|
|
|
525
556
|
/>
|
|
526
557
|
</div>
|
|
527
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>
|
|
528
579
|
</div>
|
|
529
580
|
);
|
|
530
581
|
};
|