From c418fd180a341e4cae90389c107fad3890958a9f Mon Sep 17 00:00:00 2001 From: Vencespass <113904392+vroullier-pass@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:29:08 +0100 Subject: [PATCH] (PC-34342)[API] feat: adapt finance docs to unique deposit type --- api/src/pcapi/core/bookings/models.py | 21 +++++++++++++- api/src/pcapi/core/finance/api.py | 34 ++++++++++------------- api/tests/core/finance/test_api.py | 6 ++-- api/tests/core/finance/test_api_legacy.py | 6 ++-- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/api/src/pcapi/core/bookings/models.py b/api/src/pcapi/core/bookings/models.py index eee0204a1c0..73f0879b513 100644 --- a/api/src/pcapi/core/bookings/models.py +++ b/api/src/pcapi/core/bookings/models.py @@ -4,10 +4,12 @@ import enum from typing import TYPE_CHECKING +from dateutil.relativedelta import relativedelta from sqlalchemy import BigInteger from sqlalchemy import Boolean from sqlalchemy import Column from sqlalchemy import DDL +from sqlalchemy import Date from sqlalchemy import DateTime from sqlalchemy import Enum from sqlalchemy import ForeignKey @@ -18,6 +20,7 @@ from sqlalchemy import Text from sqlalchemy import and_ from sqlalchemy import case +from sqlalchemy import cast from sqlalchemy import event from sqlalchemy import exists from sqlalchemy import select @@ -29,6 +32,7 @@ from sqlalchemy.sql.elements import BinaryExpression from sqlalchemy.sql.elements import BooleanClauseList from sqlalchemy.sql.elements import Label +from sqlalchemy.sql.functions import concat from pcapi.core.bookings import exceptions from pcapi.core.bookings.constants import BOOKINGS_AUTO_EXPIRY_DELAY @@ -39,6 +43,7 @@ import pcapi.core.finance.models as finance_models from pcapi.core.offers import models as offers_models from pcapi.core.reactions.models import ReactionTypeEnum +from pcapi.core.users import models as users_models from pcapi.models import Base from pcapi.models import Model from pcapi.models.pc_object import PcObject @@ -47,7 +52,6 @@ if TYPE_CHECKING: from pcapi.core.offerers import models as offerers_models - from pcapi.core.users import models as users_models class BookingCancellationReasons(enum.Enum): @@ -424,6 +428,21 @@ def display_even_if_used(cls) -> BooleanClauseList: # pylint: disable=no-self-a cls.amount == 0, ) + @hybrid_property + def made_by_underage_user(self) -> bool: + return self.dateCreated.date() < self.user.birth_date + relativedelta(years=18) + + @made_by_underage_user.expression # type: ignore[no-redef] + def made_by_underage_user(cls) -> BooleanClauseList: # pylint: disable=no-self-argument + return case( + ( + cast(cls.dateCreated, Date) + < users_models.User.birth_date + cast(concat(18, " YEARS"), postgresql.INTERVAL), + True, + ), + else_=False, + ) + @property def userReaction(self) -> ReactionTypeEnum | None: is_linked_to_product = self.stock.offer.product is not None diff --git a/api/src/pcapi/core/finance/api.py b/api/src/pcapi/core/finance/api.py index fae59a52802..23b7ff84429 100644 --- a/api/src/pcapi/core/finance/api.py +++ b/api/src/pcapi/core/finance/api.py @@ -1472,7 +1472,7 @@ def _generate_payments_file(batch: models.CashflowBatch) -> pathlib.Path: def get_individual_data(query: BaseQuery) -> BaseQuery: individual_data_query = ( query.filter(bookings_models.Booking.amount != 0) - .join(bookings_models.Booking.deposit) + .join(bookings_models.Booking.user) .join(models.Pricing.cashflows) .join( models.BankAccount, @@ -1483,7 +1483,7 @@ def get_individual_data(query: BaseQuery) -> BaseQuery: .group_by( models.BankAccount.id, offerers_models.Offerer.id, - models.Deposit.type, + bookings_models.Booking.made_by_underage_user, ) .with_entities( models.BankAccount.id.label("bank_account_id"), @@ -1493,7 +1493,7 @@ def get_individual_data(query: BaseQuery) -> BaseQuery: (offerers_models.Offerer.is_caledonian == True, offerers_models.Offerer.rid7), else_=offerers_models.Offerer.siren, ).label("offerer_siren"), - models.Deposit.type.label("deposit_type"), + bookings_models.Booking.made_by_underage_user.label("made_by_underage_user"), # type: ignore[attr-defined] sa.case((offerers_models.Offerer.is_caledonian == True, "NC"), else_=None).label("caledonian_label"), sqla_func.sum(models.Pricing.amount).label("pricing_amount"), ) @@ -1561,7 +1561,7 @@ def get_collective_data(query: BaseQuery) -> BaseQuery: sa.column("bank_account_label"), sa.column("offerer_name"), sa.column("offerer_siren"), - sa.column("deposit_type"), + sa.column("made_by_underage_user"), sa.column("caledonian_label"), ) .with_entities( @@ -1569,7 +1569,7 @@ def get_collective_data(query: BaseQuery) -> BaseQuery: sa.column("bank_account_label"), sa.column("offerer_name"), sa.column("offerer_siren"), - sa.column("deposit_type"), + sa.column("made_by_underage_user"), sa.column("caledonian_label"), sqla_func.sum(sa.column("pricing_amount")).label("pricing_amount"), ) @@ -1618,12 +1618,10 @@ def get_collective_data(query: BaseQuery) -> BaseQuery: def _payment_details_row_formatter(sql_row: typing.Any) -> tuple: if hasattr(sql_row, "ministry"): booking_type = "EACC" - elif sql_row.deposit_type == models.DepositType.GRANT_15_17.value: + elif sql_row.made_by_underage_user: booking_type = "EACI" - elif sql_row.deposit_type == models.DepositType.GRANT_18.value: - booking_type = "PC" else: - raise ValueError("Unknown booking type (not educational nor individual)") + booking_type = "PC" ministry = getattr(sql_row, "caledonian_label", getattr(sql_row, "ministry", "")) net_amount = utils.cents_to_full_unit(-sql_row.pricing_amount) @@ -1931,7 +1929,7 @@ def generate_invoice_file(batch: models.CashflowBatch) -> pathlib.Path: def get_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) -> BaseQuery: return ( query.join(models.Pricing.lines) - .join(bookings_models.Booking.deposit) + .join(bookings_models.Booking.user) .join(models.Invoice.bankAccount) .join(models.BankAccount.offerer) .filter( @@ -1945,7 +1943,7 @@ def get_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) -> BaseQuery models.Invoice.bankAccountId, models.PricingLine.category, offerers_models.Offerer.is_caledonian, - models.Deposit.type, + bookings_models.Booking.made_by_underage_user, ) .with_entities( models.Invoice.id, @@ -1953,7 +1951,7 @@ def get_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) -> BaseQuery models.Invoice.reference.label("invoice_reference"), models.Invoice.bankAccountId.label("bank_account_id"), models.PricingLine.category.label("pricing_line_category"), - models.Deposit.type.label("deposit_type"), + bookings_models.Booking.made_by_underage_user.label("made_by_underage_user"), # type: ignore[attr-defined] sa.case((offerers_models.Offerer.is_caledonian == True, "NC"), else_=None).label("ministry"), sqla_func.sum(models.PricingLine.amount).label("pricing_line_amount"), ) @@ -2036,7 +2034,7 @@ def get_collective_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) - sa.column("invoice_reference"), sa.column("bank_account_id"), sa.column("pricing_line_category"), - sa.column("deposit_type"), + sa.column("made_by_underage_user"), sa.column("ministry"), ) .with_entities( @@ -2044,7 +2042,7 @@ def get_collective_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) - sa.column("invoice_reference"), sa.column("bank_account_id"), sa.column("pricing_line_category"), - sa.column("deposit_type"), + sa.column("made_by_underage_user"), sa.column("ministry"), sqla_func.sum(sa.column("pricing_line_amount")).label("pricing_line_amount"), ) @@ -2094,7 +2092,7 @@ def get_collective_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) - indiv_data = sorted( indiv_data, - key=lambda o: (o.invoice_reference, o.deposit_type, pricing_line_dict[o.pricing_line_category]), + key=lambda o: (o.invoice_reference, o.made_by_underage_user, pricing_line_dict[o.pricing_line_category]), ) collective_data = sorted( collective_data, @@ -2112,12 +2110,10 @@ def get_collective_data(query: BaseQuery, bank_accounts: typing.Iterable[int]) - def _invoice_row_formatter(sql_row: typing.Any) -> tuple: if sql_row.ministry and sql_row.ministry != "NC": booking_type = "EACC" - elif sql_row.deposit_type == models.DepositType.GRANT_15_17.value: + elif sql_row.made_by_underage_user: booking_type = "EACI" - elif sql_row.deposit_type == models.DepositType.GRANT_18.value: - booking_type = "PC" else: - raise ValueError("Unknown booking type (not educational nor individual)") + booking_type = "PC" ministry = getattr(sql_row, "ministry", "") diff --git a/api/tests/core/finance/test_api.py b/api/tests/core/finance/test_api.py index 0123e6ad20f..d361c3f9668 100644 --- a/api/tests/core/finance/test_api.py +++ b/api/tests/core/finance/test_api.py @@ -2892,9 +2892,11 @@ def test_generate_invoice_file(clean_temp_files): offerers_factories.VenueBankAccountLinkFactory( venue=nc_venue, bankAccount=nc_bank_account, timespan=(datetime.datetime.utcnow(),) ) + underage_user = users_factories.UnderageBeneficiaryFactory() nc_pricing = factories.PricingFactory( status=models.PricingStatus.VALIDATED, booking__stock__offer__venue=nc_venue, + booking__user=underage_user, amount=-1000, ) nc_pline_1 = factories.PricingLineFactory(pricing=nc_pricing, amount=-1100) @@ -2972,7 +2974,7 @@ def test_generate_invoice_file(clean_temp_files): "Date du justificatif": datetime.date.today().isoformat(), "Référence du justificatif": nc_invoice.reference, "Type de ticket de facturation": nc_pline_1.category.value, - "Type de réservation": "PC", + "Type de réservation": "EACI", "Ministère": "NC", "Somme des tickets de facturation": nc_pline_1.amount, } @@ -2982,7 +2984,7 @@ def test_generate_invoice_file(clean_temp_files): "Date du justificatif": datetime.date.today().isoformat(), "Référence du justificatif": nc_invoice.reference, "Type de ticket de facturation": nc_pline_2.category.value, - "Type de réservation": "PC", + "Type de réservation": "EACI", "Ministère": "NC", "Somme des tickets de facturation": nc_pline_2.amount, } diff --git a/api/tests/core/finance/test_api_legacy.py b/api/tests/core/finance/test_api_legacy.py index 7295431b13e..6aac0ed713c 100644 --- a/api/tests/core/finance/test_api_legacy.py +++ b/api/tests/core/finance/test_api_legacy.py @@ -1529,9 +1529,11 @@ def test_generate_invoice_file(clean_temp_files): offerers_factories.VenueBankAccountLinkFactory( venue=nc_venue, bankAccount=nc_bank_account, timespan=(datetime.datetime.utcnow(),) ) + underage_user = users_factories.UnderageBeneficiaryFactory() nc_pricing = factories.PricingFactory( status=models.PricingStatus.VALIDATED, booking__stock__offer__venue=nc_venue, + booking__user=underage_user, amount=-1000, ) nc_pline_1 = factories.PricingLineFactory(pricing=nc_pricing, amount=-1100) @@ -1609,7 +1611,7 @@ def test_generate_invoice_file(clean_temp_files): "Date du justificatif": datetime.date.today().isoformat(), "Référence du justificatif": nc_invoice.reference, "Type de ticket de facturation": nc_pline_1.category.value, - "Type de réservation": "PC", + "Type de réservation": "EACI", "Ministère": "NC", "Somme des tickets de facturation": nc_pline_1.amount, } @@ -1619,7 +1621,7 @@ def test_generate_invoice_file(clean_temp_files): "Date du justificatif": datetime.date.today().isoformat(), "Référence du justificatif": nc_invoice.reference, "Type de ticket de facturation": nc_pline_2.category.value, - "Type de réservation": "PC", + "Type de réservation": "EACI", "Ministère": "NC", "Somme des tickets de facturation": nc_pline_2.amount, }