richie-education 3.2.2-dev36 → 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.
|
@@ -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
|
};
|