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 handleConfirmPurchaseOrder = async (id: string) => {
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: { quote_id: id },
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={() => handleConfirmPurchaseOrder(quote.id)}
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "3.2.2-dev36",
3
+ "version": "3.2.2-dev37",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {