def test_returns_creation_date_if_event_begins_too_soon(self, booking_date):
     event_date_too_close_to_cancel_booking = booking_date + timedelta(days=1)
     booking_creation = booking_date
     assert (
         api.compute_cancellation_limit_date(event_date_too_close_to_cancel_booking, booking_creation)
         == booking_creation
     )
Example #2
0
def create_domain_booking_recap(
    offer_identifier: int = 1,
    offer_name: str = "Le livre de la jungle",
    offerer_name: str = "Libraire de Caen",
    offer_isbn: Optional[str] = None,
    beneficiary_lastname: str = "Sans Nom",
    beneficiary_firstname: str = "Mowgli",
    beneficiary_email: str = "*****@*****.**",
    beneficiary_phonenumber: str = "0100000000",
    booking_amount: float = 0,
    booking_token: str = "JUNGLE",
    booking_date: datetime = datetime(2020, 3, 14, 19, 5, 3, 0),
    booking_is_duo: bool = False,
    booking_is_educational: bool = False,
    booking_is_used: bool = False,
    booking_is_cancelled: bool = False,
    booking_is_reimbursed: bool = False,
    booking_is_confirmed: bool = False,
    payment_date: Optional[datetime] = None,
    cancellation_date: Optional[datetime] = None,
    date_used: Optional[datetime] = None,
    redactor_lastname=None,
    redactor_firstname=None,
    redactor_email=None,
    venue_identifier: int = 1,
    venue_name="Librairie Kléber",
    venue_is_virtual=False,
    event_beginning_datetime: Optional[datetime] = None,
) -> BookingRecapLegacy:
    return BookingRecapLegacy(
        offer_identifier=offer_identifier,
        offer_name=offer_name,
        offerer_name=offerer_name,
        offer_isbn=offer_isbn,
        beneficiary_lastname=beneficiary_lastname,
        beneficiary_firstname=beneficiary_firstname,
        beneficiary_email=beneficiary_email,
        beneficiary_phonenumber=beneficiary_phonenumber,
        booking_amount=booking_amount,
        booking_token=booking_token,
        booking_date=booking_date,
        booking_is_duo=booking_is_duo,
        booking_is_educational=booking_is_educational,
        booking_is_used=booking_is_used,
        booking_is_cancelled=booking_is_cancelled,
        booking_is_reimbursed=booking_is_reimbursed,
        booking_is_confirmed=booking_is_confirmed,
        payment_date=payment_date,
        cancellation_date=cancellation_date,
        cancellation_limit_date=compute_cancellation_limit_date(event_beginning_datetime, booking_date),
        date_used=date_used,
        redactor_lastname=redactor_lastname,
        redactor_firstname=redactor_firstname,
        redactor_email=redactor_email,
        venue_identifier=venue_identifier,
        venue_name=venue_name,
        venue_is_virtual=venue_is_virtual,
        event_beginning_datetime=event_beginning_datetime,
    )
def check_is_usable(booking: Booking) -> None:
    if payment_queries.has_payment(booking):
        forbidden = api_errors.ForbiddenError()
        forbidden.add_error("payment", "Cette réservation a été remboursée")
        raise forbidden

    if booking.isUsed or booking.status is BookingStatus.USED:
        gone = api_errors.ResourceGoneError()
        gone.add_error("booking", "Cette réservation a déjà été validée")
        raise gone

    if booking.isCancelled or booking.status is BookingStatus.CANCELLED:
        forbidden = api_errors.ForbiddenError()
        forbidden.add_error("booking", "Cette réservation a été annulée")
        raise forbidden

    if booking.educationalBookingId is not None:
        if booking.educationalBooking.status is EducationalBookingStatus.REFUSED:
            reason = "Cette réservation pour une offre éducationnelle a été refusée par le chef d'établissement"
            raise api_errors.ForbiddenError(errors={"educationalBooking": reason})

        if booking.educationalBooking.status is not EducationalBookingStatus.USED_BY_INSTITUTE:
            reason = (
                "Cette réservation pour une offre éducationnelle n'est pas encore validée par le chef d'établissement"
            )
            raise api_errors.ForbiddenError(errors={"educationalBooking": reason})

    is_booking_for_event_and_not_confirmed = booking.stock.beginningDatetime and not booking.isConfirmed
    if is_booking_for_event_and_not_confirmed:
        forbidden = api_errors.ForbiddenError()
        venue_departement_code = booking.venue.departementCode
        booking_date = datetime.datetime.strftime(
            utc_datetime_to_department_timezone(booking.dateCreated, venue_departement_code), "%d/%m/%Y à %H:%M"
        )
        max_cancellation_date = datetime.datetime.strftime(
            utc_datetime_to_department_timezone(
                api.compute_cancellation_limit_date(
                    booking.stock.beginningDatetime,
                    booking.dateCreated,
                ),
                venue_departement_code,
            ),
            "%d/%m/%Y à %H:%M",
        )

        forbidden.add_error(
            "booking",
            f"Cette réservation a été effectuée le {booking_date}. "
            f"Veuillez attendre jusqu’au {max_cancellation_date} pour valider la contremarque.",
        )
        raise forbidden
 def test_returns_two_days_before_event_if_event_begins_between_two_and_four_days_from_now(self, booking_date):
     event_date_four_days_from_now = booking_date + timedelta(days=4)
     booking_creation = booking_date
     assert api.compute_cancellation_limit_date(
         event_date_four_days_from_now, booking_creation
     ) == event_date_four_days_from_now - timedelta(days=2)
 def test_returns_two_days_after_booking_creation_if_event_begins_in_more_than_four_days(self, booking_date):
     event_date_more_ten_days_from_now = booking_date + timedelta(days=6)
     booking_creation = booking_date
     assert api.compute_cancellation_limit_date(
         event_date_more_ten_days_from_now, booking_creation
     ) == booking_creation + timedelta(days=2)
 def test_returns_none_if_no_event_beginning(self, booking_date):
     event_beginning = None
     booking_creation = booking_date
     assert api.compute_cancellation_limit_date(event_beginning, booking_creation) is None
 def validate_cancellation_limit_date(cls, cancellation_limit_date, values):  # pylint: disable=no-self-argument
     return compute_cancellation_limit_date(values.get("beginningDatetime"), datetime.now())
Example #8
0
def book_educational_offer(redactor_informations: RedactorInformation,
                           stock_id: int) -> EducationalBooking:
    redactor = educational_repository.find_redactor_by_email(
        redactor_informations.email)
    if not redactor:
        redactor = _create_redactor(redactor_informations)

    educational_institution = educational_repository.find_educational_institution_by_uai_code(
        redactor_informations.uai)
    validation.check_institution_exists(educational_institution)

    # The call to transaction here ensures we free the FOR UPDATE lock
    # on the stock if validation issues an exception
    with transaction():
        stock = offers_repository.get_and_lock_stock(stock_id=stock_id)
        validation.check_stock_is_bookable(stock)

        educational_year = educational_repository.find_educational_year_by_date(
            stock.beginningDatetime)
        validation.check_educational_year_exists(educational_year)

        educational_booking = EducationalBooking(
            educationalInstitution=educational_institution,
            educationalYear=educational_year,
            educationalRedactor=redactor,
            confirmationLimitDate=stock.bookingLimitDatetime,
        )

        booking = bookings_models.Booking(
            educationalBooking=educational_booking,
            stockId=stock.id,
            amount=stock.price,
            token=bookings_repository.generate_booking_token(),
            venueId=stock.offer.venueId,
            offererId=stock.offer.venue.managingOffererId,
            status=bookings_models.BookingStatus.PENDING,
        )

        booking.dateCreated = datetime.utcnow()
        booking.cancellationLimitDate = compute_cancellation_limit_date(
            stock.beginningDatetime, booking.dateCreated)
        stock.dnBookedQuantity += EAC_DEFAULT_BOOKED_QUANTITY

        repository.save(booking)

    logger.info(
        "Redactor booked an educational offer",
        extra={
            "redactor": redactor_informations.email,
            "offerId": stock.offerId,
            "stockId": stock.id,
            "bookingId": booking.id,
        },
    )
    if stock.offer.bookingEmail:
        mails.send(recipients=[stock.offer.bookingEmail],
                   data=_build_prebooking_mail_data(booking))

    search.async_index_offer_ids([stock.offerId])

    return booking
def create_booking(
    user: User,
    amount: Optional[Union[Decimal, float]] = None,
    date_created: datetime = datetime.utcnow(),
    date_used: datetime = None,
    idx: int = None,
    is_cancelled: bool = False,
    is_used: bool = False,
    status: BookingStatus = BookingStatus.CONFIRMED,
    quantity: int = 1,
    stock: Stock = None,
    venue: Venue = None,
    token: str = None,
    offerer: Offerer = None,
) -> Booking:
    booking = Booking()
    if offerer is None:
        offerer = create_offerer(siren="987654321",
                                 address="Test address",
                                 city="Test city",
                                 postal_code="93000",
                                 name="Test name")
    if venue is None:
        venue = create_venue(
            offerer=offerer,
            name="Test offerer",
            booking_email="*****@*****.**",
            address="123 rue test",
            postal_code="93000",
            city="Test city",
            departement_code="93",
        )
    if stock is None:
        price = amount if amount is not None else 10
        product_with_thing_type = create_offer_with_thing_product(venue)
        stock = create_stock_with_thing_offer(offerer=offerer,
                                              venue=venue,
                                              offer=product_with_thing_type,
                                              price=price)

    if not stock.offer:
        stock.offer = create_offer_with_thing_product(venue)

    booking.amount = amount if amount is not None else stock.price
    booking.dateCreated = date_created
    booking.dateUsed = date_used
    booking.id = idx
    booking.isUsed = is_used
    booking.isCancelled = is_cancelled
    booking.status = BookingStatus.USED if is_used else (
        BookingStatus.CANCELLED if is_cancelled else status)
    booking.quantity = quantity
    booking.stock = stock
    booking.offerer = offerer
    booking.venue = venue
    booking.token = token if token is not None else random_token()
    booking.cancellationLimitDate = bookings_api.compute_cancellation_limit_date(
        stock.beginningDatetime, date_created)

    individual_booking = IndividualBooking()
    individual_booking.user = user
    individual_booking.userId = user.id
    individual_booking.booking = booking

    return individual_booking.booking
Example #10
0
 def _get_cancellation_limit_datetime(stock: Stock) -> Optional[datetime]:
     # compute date as if it were booked now
     return compute_cancellation_limit_date(stock.beginningDatetime, datetime.now())