diff --git a/src/backend/joanie/client_urls.py b/src/backend/joanie/client_urls.py index 3d215c43e4..53ac6c8d7b 100644 --- a/src/backend/joanie/client_urls.py +++ b/src/backend/joanie/client_urls.py @@ -103,6 +103,11 @@ api_client.NestedOrganizationContractViewSet, basename="organization_contracts", ) +organization_related_router.register( + "agreements", + api_client.NestedOrganizationAgreementViewSet, + basename="organization_agreements", +) organization_related_router.register( "offerings", api_client.OfferingViewSet, diff --git a/src/backend/joanie/core/api/client/__init__.py b/src/backend/joanie/core/api/client/__init__.py index 28a9ff4217..34919ad262 100755 --- a/src/backend/joanie/core/api/client/__init__.py +++ b/src/backend/joanie/core/api/client/__init__.py @@ -1100,6 +1100,12 @@ def get_queryset(self): type=OpenApiTypes.UUID, many=True, ), + OpenApiParameter( + name="from_batch_order", + description="Retrieve contracts links for batch orders", + required=False, + type=OpenApiTypes.BOOL, + ), ], ) @action( @@ -1115,12 +1121,14 @@ def contracts_signature_link(self, request, *args, **kwargs): organization = self.get_object() contract_ids = request.query_params.getlist("contract_ids") offering_ids = request.query_params.getlist("offering_ids") + from_batch_order = request.query_params.get("from_batch_order", False) try: (signature_link, ids) = organization.contracts_signature_link( request.user, contract_ids=contract_ids, offering_ids=offering_ids, + from_batch_order=from_batch_order, ) except NoContractToSignError as error: return Response({"detail": f"{error}"}, status=HTTPStatus.BAD_REQUEST) @@ -1636,6 +1644,96 @@ def get_me(self, request): return Response(self.serializer_class(request.user, context=context).data) +class GenericAgreementViewSet( + mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet +): + """ + Agreement ViewSet that returns information on Contracts related to Batch Orders. + Only authenticated users that have access to organization can get information. + + GET /.*/agreements/ + GET /.*/agreements/ + """ + + lookup_field = "pk" + permission_classes = [permissions.IsAuthenticated] + serializer_class = serializers.AgreementBatchOrderSerializer + filterset_class = filters.AgreementFilter + ordering = ["-organization_signed_on", "-created_on"] + queryset = ( + models.Contract.objects.exclude( + batch_order__state=enums.BATCH_ORDER_STATE_CANCELED + ) + .select_related( + "definition", + "organization_signatory", + ) + .prefetch_related( + "batch_order__organization", + "batch_order__offering__course", + "batch_order__owner", + "batch_order__offering__product", + ) + ) + + +class NestedOrganizationAgreementViewSet(NestedGenericViewSet, GenericAgreementViewSet): + """ + Nested Organization and Agreements (contracts) related to batch orders inside organization + route. + + It allows to list & retrieve organization's agreements (contracts) if the user is + an administrator or an owner of the organization. + + GET /api/organizations//agreements/ + Return list of all organization's contracts + + GET /api/organizations//agreements// + Return an organization's contract if one matches the provided id + + You can use query params to filter by signature state when retrieving the list, or by + offering id. + """ + + lookup_fields = ["batch_order__organization__pk", "pk"] + lookup_url_kwargs = ["organization_id", "pk"] + + def _lookup_by_organization_code_or_pk(self): + """ + Override `lookup_fields` to lookup by organization code or pk according to + the `organization_id` kwarg is a valid UUID or not. + """ + try: + uuid.UUID(self.kwargs["organization_id"]) + except ValueError: + self.lookup_fields[0] = "batch_order__organization__code__iexact" + + def initial(self, request, *args, **kwargs): + """ + Runs anything that needs to occur prior to calling method handler. + """ + super().initial(request, *args, **kwargs) + self._lookup_by_organization_code_or_pk() + + def get_queryset(self): + """ + Customize the queryset to get only user's agreements. + """ + queryset = super().get_queryset() + + username = ( + self.request.auth["username"] + if self.request.auth + else self.request.user.username + ) + + additional_filter = { + "batch_order__organization__accesses__user__username": username, + } + + return queryset.filter(**additional_filter) + + class GenericContractViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet ): diff --git a/src/backend/joanie/core/filters/client/__init__.py b/src/backend/joanie/core/filters/client/__init__.py index 8e5c29f3fb..dd6abeee09 100755 --- a/src/backend/joanie/core/filters/client/__init__.py +++ b/src/backend/joanie/core/filters/client/__init__.py @@ -173,6 +173,60 @@ def filter_offering_id(self, queryset, _name, value): ) +class AgreementFilter(filters.FilterSet): + """ + AgreementFilter allows to filter this resource with signature state, organization id, + or offering id. + """ + + id = filters.AllValuesMultipleFilter() + signature_state = filters.ChoiceFilter( + method="filter_signature_state", + choices=enums.CONTRACT_SIGNATURE_STATE_FILTER_CHOICES, + ) + organization_id = filters.UUIDFilter(field_name="batch_order__organization__id") + offering_id = filters.UUIDFilter(method="filter_offering_id") + + class Meta: + model = models.Contract + fields: List[str] = ["id", "signature_state", "offering_id"] + + def filter_signature_state(self, queryset, _name, value): + """ + Filter Contracts by signature state + """ + is_unsigned = value == enums.CONTRACT_SIGNATURE_STATE_UNSIGNED + is_half_signed = value == enums.CONTRACT_SIGNATURE_STATE_HALF_SIGNED + + return queryset.filter( + student_signed_on__isnull=is_unsigned, + organization_signed_on__isnull=is_unsigned | is_half_signed, + ) + + def filter_offering_id(self, queryset, _name, value): + """ + Try to retrieve an offering from the given id and filter contracts accordingly. + """ + url_kwargs = self.request.parser_context.get("kwargs", {}) + queryset_filters = {"id": value} + + if organization_id := url_kwargs.get("organization_id"): + queryset_filters["organizations__in"] = [organization_id] + + try: + offering = models.CourseProductRelation.objects.get(**queryset_filters) + except models.CourseProductRelation.DoesNotExist: + return queryset.none() + + return queryset.filter( + batch_order__relation__course_id=offering.course_id, + batch_order__relation__product_id=offering.product_id, + batch_order__organization__in=offering.organizations.only("pk").values_list( + "pk", flat=True + ), + ) + + class NestedOrderCourseViewSetFilter(filters.FilterSet): """ OrderCourseFilter that allows to filter this resource with a product's 'id', an diff --git a/src/backend/joanie/core/models/contracts.py b/src/backend/joanie/core/models/contracts.py index 4f651928a5..c83b73d038 100644 --- a/src/backend/joanie/core/models/contracts.py +++ b/src/backend/joanie/core/models/contracts.py @@ -311,7 +311,11 @@ def get_abilities(self, user): can_sign = False if user.is_authenticated: - abilities = self.order.organization.get_abilities(user=user) + abilities = ( + self.order.organization.get_abilities(user=user) + if self.order + else self.batch_order.organization.get_abilities(user=user) + ) can_sign = abilities.get("sign_contracts", False) return { diff --git a/src/backend/joanie/core/models/courses.py b/src/backend/joanie/core/models/courses.py index 4640c12c9e..3eb05ffa1a 100644 --- a/src/backend/joanie/core/models/courses.py +++ b/src/backend/joanie/core/models/courses.py @@ -341,10 +341,23 @@ def signature_backend_references_to_sign(self, **kwargs): Return the list of references that should be signed by the organization. """ filters = Q() + from_batch_order = kwargs.get("from_batch_order") + if contract_ids := kwargs.get("contract_ids"): filters &= Q(id__in=contract_ids) - if relation_ids := kwargs.get("offering_ids"): - filters &= Q(order__product__offerings__id__in=relation_ids) + + if offering_ids := kwargs.get("offering_ids"): + if from_batch_order: + filters &= Q(batch_order__relation__id__in=offering_ids) + else: + filters &= Q(order__product__offerings__id__in=offering_ids) + + if from_batch_order: + filters &= Q(batch_order__organization=self) + exclude_filter = Q(batch_order__state=enums.BATCH_ORDER_STATE_CANCELED) + else: + filters &= Q(order__organization=self) + exclude_filter = Q(order__state=enums.ORDER_STATE_CANCELED) contracts_to_sign = list( Contract.objects.filter( @@ -352,9 +365,8 @@ def signature_backend_references_to_sign(self, **kwargs): signature_backend_reference__isnull=False, submitted_for_signature_on__isnull=False, student_signed_on__isnull=False, - order__organization=self, ) - .exclude(order__state=enums.ORDER_STATE_CANCELED) + .exclude(exclude_filter) .values_list("id", "signature_backend_reference") ) diff --git a/src/backend/joanie/core/serializers/client.py b/src/backend/joanie/core/serializers/client.py index 767c69f367..88ef468ae4 100644 --- a/src/backend/joanie/core/serializers/client.py +++ b/src/backend/joanie/core/serializers/client.py @@ -546,6 +546,20 @@ def get_total_currency(self, *args, **kwargs) -> str: return settings.DEFAULT_CURRENCY +class AgreementBatchOrderSerializer(AbilitiesModelSerializer): + """Small serializer for Contracts models related to Batch Orders (agreements)""" + + batch_order = BatchOrderLightSerializer(read_only=True) + + class Meta: + model = models.Contract + fields = [ + "id", + "batch_order", + ] + read_only_fields = fields + + class QuoteDefinitionSerializer(serializers.ModelSerializer): """Read only serializer for QuoteDefinition model.""" diff --git a/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.perf.yml b/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.perf.yml new file mode 100644 index 0000000000..bdd0b83c6b --- /dev/null +++ b/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.perf.yml @@ -0,0 +1,407 @@ +OrganizationAgreementApiTest.test_api_organizations_agreement_list_by_signature_state: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS # AND "joanie_contract"."student_signed_on" IS #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T7 ON ("joanie_contract"."organization_signatory_id" = T7."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS # AND "joanie_contract"."student_signed_on" IS #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreement_list_by_signature_state.2: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS # AND "joanie_contract"."student_signed_on" IS #)' +OrganizationAgreementApiTest.test_api_organizations_agreement_list_by_signature_state.3: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS # AND "joanie_contract"."student_signed_on" IS NOT NULL)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS # AND "joanie_contract"."student_signed_on" IS NOT NULL) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreement_list_by_signature_state.4: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS NOT NULL AND "joanie_contract"."student_signed_on" IS NOT NULL)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_contract"."organization_signed_on" IS NOT NULL AND "joanie_contract"."student_signed_on" IS NOT NULL) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreement_retrieve_with_accesses: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_by_offering: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT ... FROM "joanie_course_product_relation" INNER JOIN "joanie_course_product_relation_organizations" ON ("joanie_course_product_relation"."id" = "joanie_course_product_relation_organizations"."courseproductrelation_id") WHERE ("joanie_course_product_relation"."id" = #::uuid AND "joanie_course_product_relation_organizations"."organization_id" IN (#::uuid)) LIMIT #' +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_course_product_relation" ON ("joanie_batch_order"."relation_id" = "joanie_course_product_relation"."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_batch_order"."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_course_product_relation" ON ("joanie_batch_order"."relation_id" = "joanie_course_product_relation"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T10 ON ("joanie_contract"."organization_signatory_id" = T10."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND "joanie_batch_order"."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_by_offering.2: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT ... FROM "joanie_course_product_relation" INNER JOIN "joanie_course_product_relation_organizations" ON ("joanie_course_product_relation"."id" = "joanie_course_product_relation_organizations"."courseproductrelation_id") WHERE ("joanie_course_product_relation"."id" = #::uuid AND "joanie_course_product_relation_organizations"."organization_id" IN (#::uuid)) LIMIT #' +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_batch_order" T8 ON ("joanie_contract"."id" = T8."contract_id") INNER JOIN "joanie_course_product_relation" ON (T8."relation_id" = "joanie_course_product_relation"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND T8."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_batch_order" T8 ON ("joanie_contract"."id" = T8."contract_id") INNER JOIN "joanie_course_product_relation" ON (T8."relation_id" = "joanie_course_product_relation"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T14 ON ("joanie_contract"."organization_signatory_id" = T14."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND T8."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_by_offering.3: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT ... FROM "joanie_course_product_relation" INNER JOIN "joanie_course_product_relation_organizations" ON ("joanie_course_product_relation"."id" = "joanie_course_product_relation_organizations"."courseproductrelation_id") WHERE ("joanie_course_product_relation"."id" = #::uuid AND "joanie_course_product_relation_organizations"."organization_id" IN (#::uuid)) LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_filter: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_filter.2: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (...) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (...) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "easy_thumbnails_source" WHERE ("easy_thumbnails_source"."name" = # AND "easy_thumbnails_source"."storage_hash" = #) LIMIT #' +- db: 'UPDATE "easy_thumbnails_source" SET ... WHERE "easy_thumbnails_source"."id" = #' +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- db: 'SELECT ... FROM "easy_thumbnails_thumbnail" WHERE ("easy_thumbnails_thumbnail"."name" = # AND "easy_thumbnails_thumbnail"."source_id" = # AND "easy_thumbnails_thumbnail"."storage_hash" = #) LIMIT #' +- db: SAVEPOINT `#` +- db: INSERT INTO "easy_thumbnails_thumbnail" (...) VALUES (...) RETURNING "easy_thumbnails_thumbnail"."id" +- db: RELEASE SAVEPOINT `#` +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_filter.3: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT ... FROM "joanie_course_product_relation" INNER JOIN "joanie_course_product_relation_organizations" ON ("joanie_course_product_relation"."id" = "joanie_course_product_relation_organizations"."courseproductrelation_id") WHERE ("joanie_course_product_relation"."id" = #::uuid AND "joanie_course_product_relation_organizations"."organization_id" IN (#::uuid)) LIMIT #' +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_batch_order" T8 ON ("joanie_contract"."id" = T8."contract_id") INNER JOIN "joanie_course_product_relation" ON (T8."relation_id" = "joanie_course_product_relation"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND T8."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_batch_order" T8 ON ("joanie_contract"."id" = T8."contract_id") INNER JOIN "joanie_course_product_relation" ON (T8."relation_id" = "joanie_course_product_relation"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T14 ON ("joanie_contract"."organization_signatory_id" = T14."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = # AND T8."organization_id" IN (SELECT U0."id" FROM "joanie_organization" U0 INNER JOIN "joanie_course_product_relation_organizations" U1 ON (U0."id" = U1."organization_id") WHERE U1."courseproductrelation_id" = #::uuid) AND "joanie_course_product_relation"."course_id" = #::uuid AND "joanie_course_product_relation"."product_id" = #::uuid) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_filter_by_with_batch_order: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_filter_by_with_batch_order.2: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_list_with_accesses: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."batch_order_id" = "joanie_batch_order"."id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_organization_access" ON ("joanie_organization"."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T7 ON ("joanie_contract"."organization_signatory_id" = T7."id") WHERE (NOT ("joanie_batch_order"."state" = # AND "joanie_batch_order"."state" IS NOT NULL) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."id" IN (...) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (...) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (...) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (...) ORDER BY "joanie_product"."created_on" DESC +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_retrieve_combine_filter_signature_state_and_contract_id: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND "joanie_batch_order"."organization_id" = #::uuid AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' +OrganizationAgreementApiTest.test_api_organizations_agreements_retrieve_with_accesses: +- db: SELECT DISTINCT "joanie_contract"."id" FROM "joanie_contract" ORDER BY "joanie_contract"."id" ASC +- db: 'SELECT COUNT(*) AS "__count" FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND UPPER("joanie_organization"."code"::text) = UPPER(#) AND "joanie_user"."username" = #)' +- db: 'SELECT ... FROM "joanie_contract" INNER JOIN "joanie_batch_order" ON ("joanie_contract"."id" = "joanie_batch_order"."contract_id") INNER JOIN "joanie_organization" ON ("joanie_batch_order"."organization_id" = "joanie_organization"."id") INNER JOIN "joanie_batch_order" T4 ON ("joanie_contract"."id" = T4."contract_id") INNER JOIN "joanie_organization" T5 ON (T4."organization_id" = T5."id") INNER JOIN "joanie_organization_access" ON (T5."id" = "joanie_organization_access"."organization_id") INNER JOIN "joanie_user" ON ("joanie_organization_access"."user_id" = "joanie_user"."id") INNER JOIN "joanie_contract_definition" ON ("joanie_contract"."definition_id" = "joanie_contract_definition"."id") LEFT OUTER JOIN "joanie_user" T9 ON ("joanie_contract"."organization_signatory_id" = T9."id") WHERE (NOT (EXISTS(SELECT # AS "a" FROM "joanie_batch_order" U1 WHERE (U1."state" = # AND U1."contract_id" = ("joanie_contract"."id")) LIMIT #)) AND UPPER("joanie_organization"."code"::text) = UPPER(#) AND "joanie_user"."username" = #) ORDER BY "joanie_contract"."created_on" DESC LIMIT #' +- db: SELECT ... FROM "joanie_batch_order" WHERE "joanie_batch_order"."contract_id" IN (#::uuid) ORDER BY "joanie_batch_order"."created_on" ASC +- db: SELECT ... FROM "joanie_organization" WHERE "joanie_organization"."id" IN (#::uuid) ORDER BY "joanie_organization"."created_on" DESC +- db: 'SELECT ... FROM "joanie_course_product_relation" WHERE "joanie_course_product_relation"."id" = #::uuid LIMIT #' +- db: SELECT ... FROM "joanie_course" WHERE "joanie_course"."id" IN (#::uuid) ORDER BY "joanie_course"."code" ASC +- db: SELECT ... FROM "joanie_user" WHERE "joanie_user"."id" IN (#::uuid) +- db: SELECT ... FROM "joanie_product" WHERE "joanie_product"."id" IN (#::uuid) ORDER BY "joanie_product"."created_on" DESC +- cache|get: parler.core.CourseTranslation.#.en-us +- cache|get: parler.core.ProductTranslation.#.en-us +- db: 'SELECT ... FROM "joanie_user" WHERE "joanie_user"."username" = # LIMIT #' +- db: 'SELECT "joanie_organization_access"."role" FROM "joanie_organization_access" WHERE ("joanie_organization_access"."organization_id" = #::uuid AND "joanie_organization_access"."user_id" = #::uuid) ORDER BY "joanie_organization_access"."created_on" DESC LIMIT #' diff --git a/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.py b/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.py new file mode 100644 index 0000000000..2108c6e848 --- /dev/null +++ b/src/backend/joanie/tests/core/api/organizations/test_api_organizations_agreements.py @@ -0,0 +1,500 @@ +"""Test suite for the Organization Agreement API""" + +from http import HTTPStatus +from unittest import mock + +from django.conf import settings +from django.utils import timezone + +from joanie.core import enums, factories, models +from joanie.core.serializers import fields +from joanie.tests.base import BaseAPITestCase + + +def create_offerings(organizations): + """Create offerings related to organizations""" + offering_1 = factories.OfferingFactory( + organizations=[*organizations], + product__contract_definition_order=factories.ContractDefinitionFactory(), + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + ) + offering_2 = factories.OfferingFactory( + organizations=[organizations[1]], + product__contract_definition_order=factories.ContractDefinitionFactory(), + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + ) + return offering_1, offering_2 + + +def create_batch_orders( + size, offering, organization, state=enums.BATCH_ORDER_STATE_QUOTED +): + """Create batch orders with agreement with the passed offering and organization""" + return factories.BatchOrderFactory.create_batch( + size, + organization=organization, + offering=offering, + state=state, + ) + + +class OrganizationAgreementApiTest(BaseAPITestCase): + """Test suite for the Organization Agreement API""" + + def test_api_organizations_agreements_list_anonymous(self): + """ + Anonymous user should not be able to retrieve the list of contracts of batch orders + of an organization. + """ + organization = factories.OrganizationFactory() + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/agreements/", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.UNAUTHORIZED) + + def test_api_organizations_agreements_create(self): + """ + Authenticated user should not be able to create an agreement for an organization. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + + response = self.client.post( + f"/api/v1.0/organizations/{organization.id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.METHOD_NOT_ALLOWED) + + def test_api_organizations_agreements_update(self): + """ + Authenticated user should not be able to update an agreement of an organization. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + + response = self.client.put( + f"/api/v1.0/organizations/{organization.id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.METHOD_NOT_ALLOWED) + + def test_api_organizations_agreements_partial_update(self): + """ + Authenticated user should not be able to partially update an agreement of an organization. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + + response = self.client.patch( + f"/api/v1.0/organizations/{organization.id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.METHOD_NOT_ALLOWED) + + def test_api_organizations_agreements_delete(self): + """ + Authenticated user should not be able to delete an agreement of an organization. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + + response = self.client.delete( + f"/api/v1.0/organizations/{organization.id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.METHOD_NOT_ALLOWED) + + def test_api_organizations_agreements_list_no_access(self): + """ + Authenticated user without access to the organization cannot query the organization's + batch orders agreements (contracts). + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertDictEqual( + { + "count": 0, + "next": None, + "previous": None, + "results": [], + }, + response.json(), + ) + + @mock.patch.object( + fields.ThumbnailDetailField, + "to_representation", + return_value="_this_field_is_mocked", + ) + def test_api_organizations_agreements_list_with_accesses(self, _): + """ + Authenticated user with all accesses to the organization can query organization's + agreements (contracts) of batch orders. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organizations = factories.OrganizationFactory.create_batch(2) + for organization in organizations: + factories.UserOrganizationAccessFactory( + user=user, organization=organization + ) + offering_1, offering_2 = create_offerings(organizations) + # Batch Orders contracts (agreements) related to organization[0] + create_batch_orders(2, offering_1, organizations[0]) + create_batch_orders(3, offering_2, organizations[0]) + # Batch orders contracts (agreement) related to organization[1] + create_batch_orders(1, offering_1, organizations[1]) + + with self.record_performance(): + response = self.client.get( + f"/api/v1.0/organizations/{organizations[0].id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + contracts = models.Contract.objects.filter( + batch_order__organization=organizations[0] + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertDictEqual( + { + "count": 5, + "next": None, + "previous": None, + "results": [ + { + "id": str(contract.id), + "abilities": { + "sign": contract.get_abilities(user)["sign"], + }, + "batch_order": { + "id": str(contract.batch_order.id), + "contract_submitted": contract.batch_order.contract_submitted, + "nb_seats": contract.batch_order.nb_seats, + "owner_name": contract.batch_order.owner.get_full_name(), + "organization_id": str( + contract.batch_order.organization.id + ), + "state": contract.batch_order.state, + "company_name": contract.batch_order.company_name, + "payment_method": contract.batch_order.payment_method, + "total": float(contract.batch_order.total), + "total_currency": settings.DEFAULT_CURRENCY, + "relation": { + "id": str(contract.batch_order.offering.id), + "course": { + "id": str(contract.batch_order.offering.course.id), + "title": contract.batch_order.offering.course.title, + "code": contract.batch_order.offering.course.code, + "cover": "_this_field_is_mocked", + }, + "product": { + "id": str(contract.batch_order.offering.product.id), + "title": contract.batch_order.offering.product.title, + }, + }, + }, + } + for contract in contracts + ], + }, + response.json(), + ) + + def test_api_organizations_agreements_list_filter(self): + """ + Authenticated user with all access to the organization can query organization's + agreements (contracts) and retrieve the list. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organizations = factories.OrganizationFactory.create_batch(2) + for organization in organizations: + factories.UserOrganizationAccessFactory( + user=user, organization=organization + ) + offering_1, offering_2 = create_offerings(organizations) + # Batch Orders contracts (agreements) related to organization[0] + create_batch_orders(2, offering_1, organizations[0]) + # Batch orders contracts (agreement) related to organization[1] + create_batch_orders(3, offering_1, organizations[1]) + create_batch_orders(7, offering_2, organizations[1]) + # Orders Contract that are not related to batch orders + factories.ContractFactory.create_batch( + 6, + order__product=offering_1.product, + order__course=offering_1.course, + order__organization=organizations[0], + ) + + # Without filter we should find the 2 agreements of the first organization + response = self.client.get( + f"/api/v1.0/organizations/{organizations[0].id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertEqual(content["count"], 2) + + response = self.client.get( + f"/api/v1.0/organizations/{organizations[1].id}/agreements/", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertEqual(content["count"], 10) + + def test_api_organizations_agreements_list_by_offering(self): + """ + Authenticated organization user with all access can list agreements (contracts) + and filter them by offering. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organizations = factories.OrganizationFactory.create_batch(2) + for organization in organizations: + factories.UserOrganizationAccessFactory( + user=user, organization=organization + ) + # Create offerings + offering_1, offering_2 = create_offerings(organizations) + # Batch Orders contracts (agreements) related to organization[0] + create_batch_orders(1, offering_1, organizations[0], 1) + # Batch orders contracts (agreement) related to organization[1] + create_batch_orders(2, offering_1, organizations[1], 2) + create_batch_orders(3, offering_2, organizations[1], 3) + + # - Filter by `offering_1.id`, we should find 1 contracts for organization[0] + with self.record_performance(): + response = self.client.get( + f"/api/v1.0/organizations/{organizations[0].id}/agreements/" + f"?offering_id={offering_1.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertEqual(content["count"], 1) + + # - Filter by `offering_2.id`, we should find 3 contracts for organization[1] + response = self.client.get( + f"/api/v1.0/organizations/{organizations[1].id}/agreements/" + f"?offering_id={offering_2.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertEqual(content["count"], 3) + + # - Filter by an `offering.id` that is not associated with the organization should return 0 + response = self.client.get( + f"/api/v1.0/organizations/{organizations[0].id}/agreements/" + f"?offering_id={offering_2.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertEqual(content["count"], 0) + + def test_api_organizations_agreement_list_by_signature_state(self): + """ + Authenticated user with all access to an organization can filter the agreements (contracts) + by signature state. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organizations = factories.OrganizationFactory.create_batch(2) + for organization in organizations: + factories.UserOrganizationAccessFactory( + user=user, organization=organization + ) + # Create offerings + offering_1, offering_2 = create_offerings(organizations) + # Contracts (agreements) of batch orders related to organization[0] + unsigned = create_batch_orders( + 1, offering_1, organizations[0], enums.BATCH_ORDER_STATE_QUOTED + )[0].contract + # Contracts (agreement) of batch orders related to organization[1] + half_signed = create_batch_orders( + 1, offering_1, organizations[1], enums.BATCH_ORDER_STATE_SIGNING + )[0].contract + completed = create_batch_orders( + 1, offering_2, organizations[1], enums.BATCH_ORDER_STATE_COMPLETED + )[0].contract + completed.submitted_for_signature_on = None + completed.organization_signed_on = timezone.now() + completed.save() + + # - Filter by an signature state `unsigned`, organization[0] should have 1 result + with self.record_performance(): + response = self.client.get( + f"/api/v1.0/organizations/{organizations[0].id}/agreements/" + f"?signature_state={enums.CONTRACT_SIGNATURE_STATE_UNSIGNED}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + content = response.json() + self.assertEqual(content["count"], 1) + self.assertEqual(str(unsigned.id), content["results"][0]["id"]) + + # - Filter by an signature state `unsigned`, organization[1] should have 0 result + response = self.client.get( + f"/api/v1.0/organizations/{organizations[1].id}/agreements/" + f"?signature_state={enums.CONTRACT_SIGNATURE_STATE_UNSIGNED}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + content = response.json() + self.assertEqual(content["count"], 0) + + # - Filter by an signature state `half_signed`, organization[1] should have 1 results + response = self.client.get( + f"/api/v1.0/organizations/{organizations[1].id}/agreements/" + f"?signature_state={enums.CONTRACT_SIGNATURE_STATE_HALF_SIGNED}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + content = response.json() + self.assertEqual(content["count"], 1) + self.assertEqual(str(half_signed.id), content["results"][0]["id"]) + + # - Filter by an signature state `completed`, organization[1] should have 1 result + response = self.client.get( + f"/api/v1.0/organizations/{organizations[1].id}/agreements/" + f"?signature_state={enums.CONTRACT_SIGNATURE_STATE_SIGNED}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + content = response.json() + self.assertEqual(content["count"], 1) + self.assertEqual(str(completed.id), content["results"][0]["id"]) + + def test_api_organizations_agreement_retrieve_without_accesses(self): + """ + Authenticated user without organization accesses cannot retrieve an agreement (contract) + from a batch order. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + batch_order = factories.BatchOrderFactory(state=enums.BATCH_ORDER_STATE_SIGNING) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/agreements/" + f"?contract_id={batch_order.contract.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertDictEqual( + { + "count": 0, + "next": None, + "previous": None, + "results": [], + }, + response.json(), + ) + + @mock.patch.object( + fields.ThumbnailDetailField, + "to_representation", + return_value="_this_field_is_mocked", + ) + def test_api_organizations_agreements_retrieve_with_accesses(self, _): + """ + Authenticated user with all accesses to an organization can retrieve a single contract + from a batch order using the organization code into the url. + """ + user = factories.UserFactory() + token = self.generate_token_from_user(user) + organization = factories.OrganizationFactory() + factories.UserOrganizationAccessFactory(user=user, organization=organization) + # Create a batch order related to the organization + batch_order = factories.BatchOrderFactory( + organization=organization, state=enums.BATCH_ORDER_STATE_SIGNING + ) + contract = batch_order.contract + + # with self.record_performance(): + response = self.client.get( + f"/api/v1.0/organizations/{organization.code}/agreements/" + f"?contract_id={contract.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertDictEqual( + { + "count": 1, + "next": None, + "previous": None, + "results": [ + { + "id": str(contract.id), + "abilities": { + "sign": contract.get_abilities(user)["sign"], + }, + "batch_order": { + "id": str(batch_order.id), + "owner_name": batch_order.owner.get_full_name(), + "contract_submitted": batch_order.contract_submitted, + "nb_seats": batch_order.nb_seats, + "organization_id": str(batch_order.organization.id), + "state": batch_order.state, + "company_name": batch_order.company_name, + "payment_method": batch_order.payment_method, + "total": float(batch_order.total), + "total_currency": settings.DEFAULT_CURRENCY, + "relation": { + "id": str(batch_order.offering.id), + "course": { + "id": str(batch_order.offering.course.id), + "title": batch_order.offering.course.title, + "code": batch_order.offering.course.code, + "cover": "_this_field_is_mocked", + }, + "product": { + "id": str(batch_order.offering.product.id), + "title": batch_order.offering.product.title, + }, + }, + }, + }, + ], + }, + response.json(), + ) diff --git a/src/backend/joanie/tests/core/api/organizations/test_contracts_signature_link.py b/src/backend/joanie/tests/core/api/organizations/test_contracts_signature_link.py index 2335d61775..4afc55ca0a 100644 --- a/src/backend/joanie/tests/core/api/organizations/test_contracts_signature_link.py +++ b/src/backend/joanie/tests/core/api/organizations/test_contracts_signature_link.py @@ -387,3 +387,323 @@ def test_api_organization_contracts_signature_link_cumulative_filters(self): response.json(), {"detail": "Some contracts are not available for this organization."}, ) + + def test_api_organization_contracts_from_batch_order_signature_link_without_owner( + self, + ): + """ + Authenticated users which is not an organization owner should not be able + to sign contracts in bulk. + """ + organization_roles_not_owner = [ + role[0] + for role in OrganizationAccess.ROLE_CHOICES + if role[0] != enums.OWNER + ] + + for organization_role in organization_roles_not_owner: + with self.subTest(role=organization_role): + user = factories.UserFactory() + organization = factories.BatchOrderFactory( + state=enums.BATCH_ORDER_STATE_TO_SIGN + ).organization + factories.UserOrganizationAccessFactory( + user=user, + organization=organization, + role=organization_role, + ) + + token = self.generate_token_from_user(user) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + "?from_batch_order=true", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertContains( + response, + '{"detail":"You do not have permission to perform this action."}', + status_code=HTTPStatus.FORBIDDEN, + ) + + def test_api_organization_contracts_from_batch_order_signature_link_success(self): + """ + Authenticated users with the owner role should be able to sign contracts in bulk. + Contracts where the batch order is canceled should not be returned. + """ + user = factories.UserFactory() + batch_order = factories.BatchOrderFactory(state=enums.BATCH_ORDER_STATE_TO_SIGN) + offering = batch_order.offering + organization = batch_order.organization + factories.UserOrganizationAccessFactory( + user=user, + organization=organization, + role=enums.OWNER, + ) + + # Create one batch order that is canceled + batch_order_2 = factories.BatchOrderFactory( + organization=organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + batch_order_2.flow.cancel() + # Create simple orders that should not be retrieved + factories.OrderGeneratorFactory.create_batch( + 3, + organization=organization, + product=offering.product, + course=offering.course, + ) + + token = self.generate_token_from_user(user) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + "?from_batch_order=true", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + # We should not find the batch_order_2 contract since it's canceled. + self.assertCountEqual( + content["contract_ids"], + [str(batch_order.contract.id)], + ) + + def test_api_organization_contracts_from_batch_order_signature_link_with_offering_id( + self, + ): + """ + Authenticated user with the owner role should be able to sign the contracts in + bulk by passing offering ids. + """ + user = factories.UserFactory() + [organization, other_organization] = factories.OrganizationFactory.create_batch( + 2 + ) + factories.UserOrganizationAccessFactory( + user=user, + organization=organization, + role=enums.OWNER, + ) + offering_1 = factories.OfferingFactory( + organizations=[organization, other_organization], + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + product__price=0, + ) + offering_2 = factories.OfferingFactory( + organizations=[organization], + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + product__price=0, + ) + batch_order_1 = factories.BatchOrderFactory( + offering=offering_1, + organization=organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + factories.BatchOrderFactory( + offering=offering_1, + organization=other_organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + batch_order_2 = factories.BatchOrderFactory( + offering=offering_2, + organization=organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + + token = self.generate_token_from_user(user) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&offering_ids={offering_1.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + # We should only find batch_order_1.contract in the response + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_1.contract.id)], + ) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&offering_ids={offering_1.id}&offering_ids={offering_2.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + # We should only find both contracts related to organization's batch orders. + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_1.contract.id), str(batch_order_2.contract.id)], + ) + + def test_api_organization_contracts_from_batch_order_signature_link_with_contract_ids( + self, + ): + """ + Authenticated user with the owner role should be able to sign the contracts in + bulk by passing contract ids. + """ + user = factories.UserFactory() + organization = factories.OrganizationFactory() + factories.UserOrganizationAccessFactory( + user=user, + organization=organization, + role=enums.OWNER, + ) + batch_order_1 = factories.BatchOrderFactory( + organization=organization, state=enums.BATCH_ORDER_STATE_TO_SIGN + ) + batch_order_2 = factories.BatchOrderFactory( + organization=organization, state=enums.BATCH_ORDER_STATE_TO_SIGN + ) + + token = self.generate_token_from_user(user) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&contract_ids={batch_order_1.contract.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_1.contract.id)], + ) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&contract_ids={batch_order_1.contract.id}" + f"&contract_ids={batch_order_2.contract.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_1.contract.id), str(batch_order_2.contract.id)], + ) + + def test_api_organization_contracts_from_batch_order_signature_link_cumulative_filters( + self, + ): + """ + When filter by both a list of offering ids and a list of contract ids, + those filter should be combined. + """ + user = factories.UserFactory() + organization = factories.OrganizationFactory() + factories.UserOrganizationAccessFactory( + user=user, + organization=organization, + role=enums.OWNER, + ) + offering_1 = factories.OfferingFactory( + organizations=[organization], + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + product__price=0, + ) + offering_2 = factories.OfferingFactory( + organizations=[organization], + product__contract_definition_batch_order=factories.ContractDefinitionFactory(), + product__quote_definition=factories.QuoteDefinitionFactory(), + product__price=0, + ) + batch_order_1 = factories.BatchOrderFactory( + offering=offering_1, + organization=organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + batch_order_2 = factories.BatchOrderFactory( + offering=offering_2, + organization=organization, + state=enums.BATCH_ORDER_STATE_TO_SIGN, + ) + + token = self.generate_token_from_user(user) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&contract_ids={batch_order_1.contract.id}" + f"&offering_ids={offering_1.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_1.contract.id)], + ) + + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&contract_ids={batch_order_2.contract.id}" + f"&offering_ids={offering_2.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + content = response.json() + self.assertStatusCodeEqual(response, HTTPStatus.OK) + self.assertIn( + "https://dummysignaturebackend.fr/?reference=", + content["invitation_link"], + ) + self.assertCountEqual( + content["contract_ids"], + [str(batch_order_2.contract.id)], + ) + + # Requesting a wrong pair of offering and contract available + response = self.client.get( + f"/api/v1.0/organizations/{organization.id}/contracts-signature-link/" + f"?from_batch_order=true&contract_ids={batch_order_1.contract.id}" + f"&offering_ids={offering_2.id}", + HTTP_AUTHORIZATION=f"Bearer {token}", + ) + + self.assertStatusCodeEqual(response, HTTPStatus.BAD_REQUEST) + self.assertEqual( + response.json(), + {"detail": "Some contracts are not available for this organization."}, + ) diff --git a/src/backend/joanie/tests/swagger/swagger.json b/src/backend/joanie/tests/swagger/swagger.json index 9eb70d1428..329a593bef 100644 --- a/src/backend/joanie/tests/swagger/swagger.json +++ b/src/backend/joanie/tests/swagger/swagger.json @@ -3992,6 +3992,151 @@ } } }, + "/api/v1.0/organizations/{organization_id}/agreements/": { + "get": { + "operationId": "organizations_agreements_list", + "description": "Nested Organization and Agreements (contracts) related to batch orders inside organization\nroute.\n\nIt allows to list & retrieve organization's agreements (contracts) if the user is\nan administrator or an owner of the organization.\n\nGET /api/organizations//agreements/\n Return list of all organization's contracts\n\nGET /api/organizations//agreements//\n Return an organization's contract if one matches the provided id\n\nYou can use query params to filter by signature state when retrieving the list, or by\noffering id.", + "parameters": [ + { + "in": "query", + "name": "id", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "description": "primary key for the record as UUID", + "explode": true, + "style": "form" + }, + { + "in": "query", + "name": "offering_id", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "pattern": "^[a-zA-Z0-9-_]+$" + }, + "required": true + }, + { + "in": "query", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "signature_state", + "schema": { + "type": "string", + "enum": [ + "half_signed", + "signed", + "unsigned" + ] + }, + "description": "* `unsigned` - Unsigned\n* `half_signed` - Partially signed\n* `signed` - Signed" + } + ], + "tags": [ + "organizations" + ], + "security": [ + { + "DelegatedJWTAuthentication": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedAgreementBatchOrderList" + } + } + }, + "description": "" + } + } + } + }, + "/api/v1.0/organizations/{organization_id}/agreements/{id}/": { + "get": { + "operationId": "organizations_agreements_retrieve", + "description": "Nested Organization and Agreements (contracts) related to batch orders inside organization\nroute.\n\nIt allows to list & retrieve organization's agreements (contracts) if the user is\nan administrator or an owner of the organization.\n\nGET /api/organizations//agreements/\n Return list of all organization's contracts\n\nGET /api/organizations//agreements//\n Return an organization's contract if one matches the provided id\n\nYou can use query params to filter by signature state when retrieving the list, or by\noffering id.", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "format": "uuid", + "description": "primary key for the record as UUID" + }, + "required": true + }, + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "pattern": "^[a-zA-Z0-9-_]+$" + }, + "required": true + } + ], + "tags": [ + "organizations" + ], + "security": [ + { + "DelegatedJWTAuthentication": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgreementBatchOrder" + } + } + }, + "description": "" + } + } + } + }, "/api/v1.0/organizations/{organization_id}/contracts/": { "get": { "operationId": "organizations_contracts_list", @@ -5064,6 +5209,14 @@ }, "description": "List of contract ids to sign, if not provided all the available contracts will be signed." }, + { + "in": "query", + "name": "from_batch_order", + "schema": { + "type": "boolean" + }, + "description": "Retrieve contracts links for batch orders" + }, { "in": "path", "name": "id", @@ -5623,6 +5776,30 @@ "title" ] }, + "AgreementBatchOrder": { + "type": "object", + "description": "Small serializer for Contracts models related to Batch Orders (agreements)", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "readOnly": true, + "description": "primary key for the record as UUID" + }, + "batch_order": { + "allOf": [ + { + "$ref": "#/components/schemas/BatchOrderLight" + } + ], + "readOnly": true + } + }, + "required": [ + "batch_order", + "id" + ] + }, "BatchOrder": { "type": "object", "description": "BatchOrder client serializer", @@ -8186,6 +8363,37 @@ } } }, + "PaginatedAgreementBatchOrderList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgreementBatchOrder" + } + } + } + }, "PaginatedBatchOrderList": { "type": "object", "required": [