@@ -387,3 +387,323 @@ def test_api_organization_contracts_signature_link_cumulative_filters(self):
387387 response .json (),
388388 {"detail" : "Some contracts are not available for this organization." },
389389 )
390+
391+ def test_api_organization_contracts_from_batch_order_signature_link_without_owner (
392+ self ,
393+ ):
394+ """
395+ Authenticated users which is not an organization owner should not be able
396+ to sign contracts in bulk.
397+ """
398+ organization_roles_not_owner = [
399+ role [0 ]
400+ for role in OrganizationAccess .ROLE_CHOICES
401+ if role [0 ] != enums .OWNER
402+ ]
403+
404+ for organization_role in organization_roles_not_owner :
405+ with self .subTest (role = organization_role ):
406+ user = factories .UserFactory ()
407+ organization = factories .BatchOrderFactory (
408+ state = enums .BATCH_ORDER_STATE_TO_SIGN
409+ ).organization
410+ factories .UserOrganizationAccessFactory (
411+ user = user ,
412+ organization = organization ,
413+ role = organization_role ,
414+ )
415+
416+ token = self .generate_token_from_user (user )
417+
418+ response = self .client .get (
419+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/" ,
420+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
421+ )
422+
423+ self .assertContains (
424+ response ,
425+ '{"detail":"You do not have permission to perform this action."}' ,
426+ status_code = HTTPStatus .FORBIDDEN ,
427+ )
428+
429+ def test_api_organization_contracts_from_batch_order_signature_link_success (self ):
430+ """
431+ Authenticated users with the owner role should be able to sign contracts in bulk.
432+ Contracts where the batch order is canceled should not be returned.
433+ """
434+ user = factories .UserFactory ()
435+ batch_order = factories .BatchOrderFactory (state = enums .BATCH_ORDER_STATE_TO_SIGN )
436+ offering = batch_order .offering
437+ organization = batch_order .organization
438+ factories .UserOrganizationAccessFactory (
439+ user = user ,
440+ organization = organization ,
441+ role = enums .OWNER ,
442+ )
443+
444+ # Create one batch order that is canceled
445+ batch_order_2 = factories .BatchOrderFactory (
446+ organization = organization ,
447+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
448+ )
449+ batch_order_2 .flow .cancel ()
450+ # Create simple orders that should not be retrieved
451+ factories .OrderGeneratorFactory .create_batch (
452+ 3 ,
453+ organization = organization ,
454+ product = offering .product ,
455+ course = offering .course ,
456+ )
457+
458+ token = self .generate_token_from_user (user )
459+
460+ response = self .client .get (
461+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
462+ "?from_batch_order=true" ,
463+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
464+ )
465+
466+ content = response .json ()
467+
468+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
469+ self .assertIn (
470+ "https://dummysignaturebackend.fr/?reference=" ,
471+ content ["invitation_link" ],
472+ )
473+ # We should not find the batch_order_2 contract since it's canceled.
474+ self .assertCountEqual (
475+ content ["contract_ids" ],
476+ [str (batch_order .contract .id )],
477+ )
478+
479+ def test_api_organization_contracts_from_batch_order_signature_link_with_offering_id (
480+ self ,
481+ ):
482+ """
483+ Authenticated user with the owner role should be able to sign the contracts in
484+ bulk by passing offering ids.
485+ """
486+ user = factories .UserFactory ()
487+ [organization , other_organization ] = factories .OrganizationFactory .create_batch (
488+ 2
489+ )
490+ factories .UserOrganizationAccessFactory (
491+ user = user ,
492+ organization = organization ,
493+ role = enums .OWNER ,
494+ )
495+ offering_1 = factories .OfferingFactory (
496+ organizations = [organization , other_organization ],
497+ product__contract_definition_batch_order = factories .ContractDefinitionFactory (),
498+ product__quote_definition = factories .QuoteDefinitionFactory (),
499+ product__price = 0 ,
500+ )
501+ offering_2 = factories .OfferingFactory (
502+ organizations = [organization ],
503+ product__contract_definition_batch_order = factories .ContractDefinitionFactory (),
504+ product__quote_definition = factories .QuoteDefinitionFactory (),
505+ product__price = 0 ,
506+ )
507+ batch_order_1 = factories .BatchOrderFactory (
508+ offering = offering_1 ,
509+ organization = organization ,
510+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
511+ )
512+ factories .BatchOrderFactory (
513+ offering = offering_1 ,
514+ organization = other_organization ,
515+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
516+ )
517+ batch_order_2 = factories .BatchOrderFactory (
518+ offering = offering_2 ,
519+ organization = organization ,
520+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
521+ )
522+
523+ token = self .generate_token_from_user (user )
524+
525+ response = self .client .get (
526+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
527+ f"?from_batch_order=true&offering_ids={ offering_1 .id } " ,
528+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
529+ )
530+
531+ content = response .json ()
532+
533+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
534+ self .assertIn (
535+ "https://dummysignaturebackend.fr/?reference=" ,
536+ content ["invitation_link" ],
537+ )
538+ # We should only find batch_order_1.contract in the response
539+ self .assertCountEqual (
540+ content ["contract_ids" ],
541+ [str (batch_order_1 .contract .id )],
542+ )
543+
544+ response = self .client .get (
545+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
546+ f"?from_batch_order=true&offering_ids={ offering_1 .id } &offering_ids={ offering_2 .id } " ,
547+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
548+ )
549+
550+ content = response .json ()
551+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
552+ self .assertIn (
553+ "https://dummysignaturebackend.fr/?reference=" ,
554+ content ["invitation_link" ],
555+ )
556+ # We should only find both contracts related to organization's batch orders.
557+ self .assertCountEqual (
558+ content ["contract_ids" ],
559+ [str (batch_order_1 .contract .id ), str (batch_order_2 .contract .id )],
560+ )
561+
562+ def test_api_organization_contracts_from_batch_order_signature_link_with_contract_ids (
563+ self ,
564+ ):
565+ """
566+ Authenticated user with the owner role should be able to sign the contracts in
567+ bulk by passing contract ids.
568+ """
569+ user = factories .UserFactory ()
570+ organization = factories .OrganizationFactory ()
571+ factories .UserOrganizationAccessFactory (
572+ user = user ,
573+ organization = organization ,
574+ role = enums .OWNER ,
575+ )
576+ batch_order_1 = factories .BatchOrderFactory (
577+ organization = organization , state = enums .BATCH_ORDER_STATE_TO_SIGN
578+ )
579+ batch_order_2 = factories .BatchOrderFactory (
580+ organization = organization , state = enums .BATCH_ORDER_STATE_TO_SIGN
581+ )
582+
583+ token = self .generate_token_from_user (user )
584+
585+ response = self .client .get (
586+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
587+ f"?from_batch_order=true&contract_ids={ batch_order_1 .contract .id } " ,
588+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
589+ )
590+
591+ content = response .json ()
592+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
593+ self .assertIn (
594+ "https://dummysignaturebackend.fr/?reference=" ,
595+ content ["invitation_link" ],
596+ )
597+ self .assertCountEqual (
598+ content ["contract_ids" ],
599+ [str (batch_order_1 .contract .id )],
600+ )
601+
602+ response = self .client .get (
603+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
604+ f"?from_batch_order=true&contract_ids={ batch_order_1 .contract .id } "
605+ f"&contract_ids={ batch_order_2 .contract .id } " ,
606+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
607+ )
608+
609+ content = response .json ()
610+
611+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
612+ self .assertIn (
613+ "https://dummysignaturebackend.fr/?reference=" ,
614+ content ["invitation_link" ],
615+ )
616+ self .assertCountEqual (
617+ content ["contract_ids" ],
618+ [str (batch_order_1 .contract .id ), str (batch_order_2 .contract .id )],
619+ )
620+
621+ def test_api_organization_contracts_from_batch_order_signature_link_cumulative_filters (
622+ self ,
623+ ):
624+ """
625+ When filter by both a list of offering ids and a list of contract ids,
626+ those filter should be combined.
627+ """
628+ user = factories .UserFactory ()
629+ organization = factories .OrganizationFactory ()
630+ factories .UserOrganizationAccessFactory (
631+ user = user ,
632+ organization = organization ,
633+ role = enums .OWNER ,
634+ )
635+ offering_1 = factories .OfferingFactory (
636+ organizations = [organization ],
637+ product__contract_definition_batch_order = factories .ContractDefinitionFactory (),
638+ product__quote_definition = factories .QuoteDefinitionFactory (),
639+ product__price = 0 ,
640+ )
641+ offering_2 = factories .OfferingFactory (
642+ organizations = [organization ],
643+ product__contract_definition_batch_order = factories .ContractDefinitionFactory (),
644+ product__quote_definition = factories .QuoteDefinitionFactory (),
645+ product__price = 0 ,
646+ )
647+ batch_order_1 = factories .BatchOrderFactory (
648+ offering = offering_1 ,
649+ organization = organization ,
650+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
651+ )
652+ batch_order_2 = factories .BatchOrderFactory (
653+ offering = offering_2 ,
654+ organization = organization ,
655+ state = enums .BATCH_ORDER_STATE_TO_SIGN ,
656+ )
657+
658+ token = self .generate_token_from_user (user )
659+
660+ response = self .client .get (
661+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
662+ f"?from_batch_order=true&contract_ids={ batch_order_1 .contract .id } "
663+ f"&offering_ids={ offering_1 .id } " ,
664+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
665+ )
666+
667+ content = response .json ()
668+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
669+ self .assertIn (
670+ "https://dummysignaturebackend.fr/?reference=" ,
671+ content ["invitation_link" ],
672+ )
673+ self .assertCountEqual (
674+ content ["contract_ids" ],
675+ [str (batch_order_1 .contract .id )],
676+ )
677+
678+ response = self .client .get (
679+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
680+ f"?from_batch_order=true&contract_ids={ batch_order_2 .contract .id } "
681+ f"&offering_ids={ offering_2 .id } " ,
682+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
683+ )
684+
685+ content = response .json ()
686+ self .assertStatusCodeEqual (response , HTTPStatus .OK )
687+ self .assertIn (
688+ "https://dummysignaturebackend.fr/?reference=" ,
689+ content ["invitation_link" ],
690+ )
691+ self .assertCountEqual (
692+ content ["contract_ids" ],
693+ [str (batch_order_2 .contract .id )],
694+ )
695+
696+ # Requesting a wrong pair of offering and contract available
697+ response = self .client .get (
698+ f"/api/v1.0/organizations/{ organization .id } /contracts-signature-link/"
699+ f"?from_batch_order=true&contract_ids={ batch_order_1 .contract .id } "
700+ f"&offering_ids={ offering_2 .id } " ,
701+ HTTP_AUTHORIZATION = f"Bearer { token } " ,
702+ )
703+
704+ content = response .json ()
705+ self .assertStatusCodeEqual (response , HTTPStatus .BAD_REQUEST )
706+ self .assertEqual (
707+ response .json (),
708+ {"detail" : "Some contracts are not available for this organization." },
709+ )
0 commit comments