def update_pending_offer_validation( offer: Offer, validation_status: OfferValidationStatus) -> bool: offer = offer_queries.get_offer_by_id(offer.id) if offer.validation != OfferValidationStatus.PENDING: logger.info( "Offer validation status cannot be updated, initial validation status is not PENDING. %s", extra={"offer": offer.id}, ) return False offer.validation = validation_status if validation_status == OfferValidationStatus.APPROVED: offer.isActive = True try: db.session.commit() except Exception as exception: # pylint: disable=broad-except logger.exception( "Could not update offer validation status: %s", extra={ "offer": offer.id, "validation_status": validation_status, "exc": str(exception) }, ) return False search.async_index_offer_ids([offer.id]) logger.info("Offer validation status updated", extra={"offer": offer.id}) return True
def after_model_change(self, form: wtforms.Form, offer: Offer, is_created: bool = False) -> None: if hasattr(form, "validation"): previous_validation = form._fields["validation"].object_data new_validation = offer.validation if previous_validation != new_validation: offer.lastValidationDate = datetime.utcnow() if new_validation == OfferValidationStatus.APPROVED: offer.isActive = True if new_validation == OfferValidationStatus.REJECTED: offer.isActive = False cancelled_bookings = cancel_bookings_from_rejected_offer( offer) if cancelled_bookings: send_cancel_booking_notification.delay( [booking.id for booking in cancelled_bookings]) repository.save(offer) recipients = ([ offer.venue.bookingEmail ] if offer.venue.bookingEmail else [ recipient.user.email for recipient in offer.venue.managingOfferer.UserOfferers ]) send_offer_validation_status_update_email( offer, new_validation, recipients) send_offer_validation_notification_to_administration( new_validation, offer) flash("Le statut de l'offre a bien été modifié", "success") search.async_index_offer_ids([offer.id])
def update_offer( # pylint: disable=redefined-builtin offer: Offer, bookingEmail: str = UNCHANGED, description: str = UNCHANGED, isNational: bool = UNCHANGED, name: str = UNCHANGED, extraData: dict = UNCHANGED, type: str = UNCHANGED, url: str = UNCHANGED, withdrawalDetails: str = UNCHANGED, isActive: bool = UNCHANGED, isDuo: bool = UNCHANGED, durationMinutes: int = UNCHANGED, mediaUrls: List[str] = UNCHANGED, ageMin: int = UNCHANGED, ageMax: int = UNCHANGED, conditions: str = UNCHANGED, venueId: str = UNCHANGED, productId: str = UNCHANGED, ) -> Offer: # fmt: off modifications = { field: new_value for field, new_value in locals().items() if field != 'offer' and new_value is not UNCHANGED # has the user provided a value for this field and getattr(offer, field) != new_value # is the value different from what we have on database? } # fmt: on if not modifications: return offer validation.check_offer_is_editable(offer) if offer.isFromAllocine: validation.check_update_only_allowed_offer_fields_for_allocine_offer(set(modifications)) offer.populate_from_dict(modifications) if offer.product.owningOfferer and offer.product.owningOfferer == offer.venue.managingOfferer: offer.product.populate_from_dict(modifications) product_has_been_updated = True else: product_has_been_updated = False if offer.isFromAllocine: offer.fieldsUpdated = list(set(offer.fieldsUpdated) | set(modifications)) repository.save(offer) if product_has_been_updated: repository.save(offer.product) if feature_queries.is_active(FeatureToggle.SYNCHRONIZE_ALGOLIA): redis.add_offer_id(client=app.redis_client, offer_id=offer.id) return offer
def test_on_datetime_list_returns_string_with_date_in_ISO_8601_list(self): # Given offer = Offer() offer.stocks = [create_stock(beginning_datetime=now, offer=offer)] # When serialized_list = serialize(offer.dateRange) # Then for dt in serialized_list: # pylint: disable=not-an-iterable self._assert_is_in_ISO_8601_format(dt)
def _complete_common_offer_fields(offer: Offer, offer_data: PostOfferBodyModel, venue: Venue) -> None: offer.venue = venue offer.bookingEmail = offer_data.booking_email offer.externalTicketOfficeUrl = offer_data.external_ticket_office_url offer.audioDisabilityCompliant = offer_data.audio_disability_compliant offer.mentalDisabilityCompliant = offer_data.mental_disability_compliant offer.motorDisabilityCompliant = offer_data.motor_disability_compliant offer.visualDisabilityCompliant = offer_data.visual_disability_compliant offer.validation = OfferValidationStatus.DRAFT offer.isEducational = offer_data.is_educational
def test_create_payment_for_booking_when_iban_is_on_venue_should_take_payment_info_from_venue( app): # given user = create_user() stock = create_stock(price=10, quantity=5) offerer = create_offerer(name="Test Offerer") venue = create_venue( offerer, name="Test Venue", ) booking = create_booking(user=user, quantity=1, stock=stock) create_bank_information(bic="Lajr93", iban="B135TGGEG532TG", offerer=offerer) create_bank_information(bic="LokiJU76", iban="KD98765RFGHZ788", venue=venue) booking.stock.offer = Offer() booking.stock.offer.venue = venue booking.stock.offer.venue.managingOfferer = offerer booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert payment.iban == "KD98765RFGHZ788" assert payment.bic == "LOKIJU76"
def test_create_payment_for_booking_takes_recipient_name_and_siren_from_offerer( app): # given user = create_user() stock = create_stock(price=10, quantity=5) booking = create_booking(user=user, quantity=1, stock=stock) booking.stock.offer = Offer() offerer = create_offerer(name="Test Offerer", siren="123456789") venue = create_venue(offerer, name="Test Venue") create_bank_information(bic="QSDFGH8Z555", iban="CF13QSDFGH456789", offerer=offerer) create_bank_information(bic=None, iban=None, venue=venue) booking.stock.offer.venue = venue booking.stock.offer.venue.managingOfferer = offerer booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert payment.recipientName == "Test Offerer" assert payment.recipientSiren == "123456789"
def test_create_payment_for_booking_with_common_information(app): # given user = create_user() stock = create_stock(price=10, quantity=5) booking = create_booking(user=user, quantity=1, stock=stock) booking.stock.offer = Offer() booking.stock.offer.venue = VenueSQLEntity() offerer = create_offerer() create_bank_information(bic="QSDFGH8Z555", iban="CF13QSDFGH456789", offerer=offerer) booking.stock.offer.venue.managingOfferer = offerer booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert payment.booking == booking assert payment.amount == Decimal(10) assert payment.reimbursementRule == ReimbursementRules.PHYSICAL_OFFERS.value.description assert payment.reimbursementRate == ReimbursementRules.PHYSICAL_OFFERS.value.rate assert payment.comment is None assert payment.author == "batch" assert payment.transactionLabel == "pass Culture Pro - remboursement 2nde quinzaine 10-2018"
def test_create_payment_for_booking_when_no_iban_on_venue_should_take_payment_info_from_offerer( app): # given user = create_user() stock = create_stock(price=10, quantity=5) offerer = create_offerer(name="Test Offerer") venue = create_venue(offerer, name="Test Venue") create_bank_information(bic="QsdFGH8Z555", iban="cf13QSDFGH456789", offerer=offerer) create_bank_information(bic=None, iban=None, venue=venue) booking = create_booking(user=user, quantity=1, stock=stock) booking.stock.offer = Offer() booking.stock.offer.venue = venue booking.stock.offer.venue.managingOfferer = offerer booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert payment.iban == "CF13QSDFGH456789" assert payment.bic == "QSDFGH8Z555"
def create_booking_for_thing( amount: int = 50, date_created: datetime = datetime.utcnow(), is_cancelled: bool = False, quantity: int = 1, product_type: ThingType = ThingType.JEUX, url: str = None, user: User = None, ) -> Booking: product = Product(from_dict={"url": url, "type": str(product_type)}) offer = Offer(from_dict={"url": url, "type": str(product_type)}) stock = Stock() booking = Booking(from_dict={"amount": amount}) offer.product = product stock.offer = offer booking.stock = stock booking.quantity = quantity booking.user = user booking.isCancelled = is_cancelled booking.dateCreated = date_created return booking
def create_booking_for_event( # pylint: disable=redefined-builtin amount: int = 50, date_created: datetime = datetime.utcnow(), is_cancelled: bool = False, quantity: int = 1, type: EventType = EventType.CINEMA, user: User = None, ) -> Booking: product = Product(from_dict={"type": str(type)}) offer = Offer() stock = Stock() booking = Booking(from_dict={"amount": amount}) offer.product = product stock.offer = offer booking.stock = stock booking.quantity = quantity booking.user = user booking.isCancelled = is_cancelled booking.token = random_token() booking.dateCreated = date_created return booking
def fill_offer_attributes(self, offer: Offer) -> None: offer.bookingEmail = self.venue.bookingEmail offer.description = self.product.description offer.extraData = self.product.extraData offer.name = self.product.name offer.productId = self.product.id offer.venueId = self.venue.id offer.type = self.product.type is_new_offer_to_create = not offer.id if is_new_offer_to_create: next_id = self.get_next_offer_id_from_sequence() offer.id = next_id self.offer_id = offer.id
def _initialize_offer_with_new_data(offer_data: PostOfferBodyModel, subcategory: subcategories.Subcategory, venue: Venue) -> Offer: data = offer_data.dict(by_alias=True) product = Product() if data.get("url"): data["isNational"] = True product.populate_from_dict(data) offer = Offer() offer.populate_from_dict(data) offer.product = product offer.subcategoryId = subcategory.id if subcategory else None offer.product.owningOfferer = venue.managingOfferer return offer
def test_create_payment_for_booking_with_not_processable_status_when_no_bank_information_linked_to_venue_or_offerer( ): # given user = create_user() stock = create_stock(price=10, quantity=5) booking = create_booking(user=user, quantity=1, stock=stock) booking.stock.offer = Offer() booking.stock.offer.venue = VenueSQLEntity() booking.stock.offer.venue.managingOfferer = create_offerer( name="Test Offerer") booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert len(payment.statuses) == 1 assert payment.statuses[0].status == TransactionStatus.NOT_PROCESSABLE assert payment.statuses[0].detail == "IBAN et BIC manquants sur l'offreur"
def test_create_payment_for_booking_with_pending_status(app): # given user = create_user() stock = create_stock(price=10, quantity=5) booking = create_booking(user=user, quantity=1, stock=stock) booking.stock.offer = Offer() booking.stock.offer.venue = VenueSQLEntity() offerer = create_offerer() booking.stock.offer.venue.managingOfferer = offerer create_bank_information(bic="QSDFGH8Z555", iban="CF13QSDFGH456789", offerer=offerer) booking_reimbursement = BookingReimbursement( booking, ReimbursementRules.PHYSICAL_OFFERS, Decimal(10)) # when payment = create_payment_for_booking(booking_reimbursement) # then assert len(payment.statuses) == 1 assert payment.statuses[0].status == TransactionStatus.PENDING assert payment.statuses[0].detail is None assert payment.statuses[0].date == datetime(2018, 10, 15, 9, 21, 34)
def create_offer(offer_data: PostOfferBodyModel, user: User) -> models.Offer: venue = load_or_raise_error(VenueSQLEntity, offer_data.venue_id) ensure_current_user_has_rights(rights=RightsType.editor, offerer_id=venue.managingOffererId, user=user) if offer_data.product_id: product = load_or_raise_error(Product, offer_data.product_id) offer = models.Offer( product=product, type=product.type, name=product.name, description=product.description, url=product.url, mediaUrls=product.mediaUrls, conditions=product.conditions, ageMin=product.ageMin, ageMax=product.ageMax, durationMinutes=product.durationMinutes, isNational=product.isNational, extraData=product.extraData, ) else: if offer_data.type == str(EventType.ACTIVATION): validation.check_user_can_create_activation_event(user) data = offer_data.dict(by_alias=True) product = Product() if data.get("url"): data["isNational"] = True product.populate_from_dict(data) offer = Offer() offer.populate_from_dict(data) offer.product = product offer.product.owningOfferer = venue.managingOfferer offer.venue = venue offer.bookingEmail = offer_data.booking_email repository.save(offer) admin_emails.send_offer_creation_notification_to_administration(offer, user, mailing.send_raw_email) return offer
def _initialize_book_offer_from_template( offer_data: PostOfferBodyModel) -> Offer: product = _load_product_by_isbn_and_check_is_gcu_compatible_or_raise_error( offer_data.extra_data["isbn"]) extra_data = product.extraData extra_data.update(offer_data.extra_data) offer = Offer( product=product, subcategoryId=product.subcategoryId, name=offer_data.name, description=offer_data.description if offer_data.description else product.description, url=offer_data.url if offer_data.url else product.url, mediaUrls=offer_data.url if offer_data.url else product.url, conditions=offer_data.conditions if offer_data.conditions else product.conditions, ageMin=offer_data.age_min if offer_data.age_min else product.ageMin, ageMax=offer_data.age_max if offer_data.age_max else product.ageMax, isNational=offer_data.is_national if offer_data.is_national else product.isNational, extraData=extra_data, ) return offer
def create_offer_with_thing_product( venue: VenueSQLEntity, author_name: str = "Test Author", booking_email: Optional[str] = "*****@*****.**", date_created: datetime = datetime.utcnow(), description: Optional[str] = None, id_at_providers: str = None, idx: int = None, is_active: bool = True, is_digital: bool = False, is_national: bool = False, is_offline_only: bool = False, media_urls: Iterable[str] = ("test/urls", ), product: Product = None, thing_name: str = "Test Book", thing_type: ThingType = ThingType.AUDIOVISUEL, thumb_count: int = 0, url: Optional[str] = None, last_provider_id: int = None, last_provider: Provider = None, extra_data: Dict = None, withdrawal_details: Optional[str] = None, ) -> Offer: offer = Offer() if product: offer.product = product offer.productId = product.id offer.name = product.name offer.type = product.type offer.mediaUrls = product.mediaUrls offer.extraData = product.extraData offer.url = product.url offer.isNational = product.isNational offer.description = product.description else: if is_digital: url = "fake/url" if is_offline_only: thing_type = ThingType.CINEMA_ABO offer.product = create_product_with_thing_type( thing_name=thing_name, thing_type=thing_type, media_urls=media_urls, author_name=author_name, url=url, thumb_count=thumb_count, is_national=is_national, description=description, ) offer.name = thing_name offer.type = str(thing_type) offer.mediaUrls = media_urls offer.extraData = {"author": author_name} offer.url = url offer.isNational = is_national offer.description = description offer.venue = venue offer.dateCreated = date_created offer.bookingEmail = booking_email offer.isActive = is_active offer.lastProviderId = last_provider_id offer.lastProvider = last_provider offer.id = idx offer.withdrawalDetails = withdrawal_details if extra_data: offer.extraData = extra_data if id_at_providers: offer.idAtProviders = id_at_providers elif venue is not None: offer.idAtProviders = "%s@%s" % (offer.product.idAtProviders, venue.siret or venue.id) return offer
def create_offer_with_event_product( venue: VenueSQLEntity = None, booking_email: str = "*****@*****.**", date_created: datetime = datetime.utcnow(), description: Optional[str] = None, duration_minutes: Optional[int] = 60, event_name: str = "Test event", event_type: EventType = EventType.SPECTACLE_VIVANT, id_at_providers: str = None, idx: int = None, is_active: bool = True, is_duo: bool = False, is_national: bool = False, last_provider_id: int = None, product: Product = None, last_provider: Provider = None, thumb_count: int = 0, withdrawal_details: Optional[str] = None, ) -> Offer: offer = Offer() if product is None: product = create_product_with_event_type( event_name=event_name, event_type=event_type, duration_minutes=duration_minutes, thumb_count=thumb_count, is_national=is_national, ) offer.product = product offer.venue = venue offer.name = product.name offer.type = product.type offer.description = description offer.isNational = product.isNational offer.durationMinutes = product.durationMinutes offer.dateCreated = date_created offer.bookingEmail = booking_email offer.isActive = is_active offer.id = idx offer.lastProviderId = last_provider_id offer.lastProvider = last_provider offer.idAtProviders = id_at_providers offer.isDuo = is_duo offer.withdrawalDetails = withdrawal_details return offer
def test_equality(): assert Offer(id=1) == Offer(id=1) assert Offer(id=1) != Offer(id=2) assert Offer(id=1) != User(id=1) assert Offer(id=1) != 1
def create_offer(offer_data: PostOfferBodyModel, user: User) -> Offer: venue = load_or_raise_error(Venue, offer_data.venue_id) check_user_has_access_to_offerer(user, offerer_id=venue.managingOffererId) if offer_data.product_id: product = load_or_raise_error(Product, offer_data.product_id) offer = Offer( product=product, type=product.type, name=product.name, description=product.description, url=product.url, mediaUrls=product.mediaUrls, conditions=product.conditions, ageMin=product.ageMin, ageMax=product.ageMax, durationMinutes=product.durationMinutes, isNational=product.isNational, extraData=product.extraData, ) else: if offer_data.type == str(EventType.ACTIVATION): validation.check_user_can_create_activation_event(user) data = offer_data.dict(by_alias=True) product = Product() if data.get("url"): data["isNational"] = True product.populate_from_dict(data) offer = Offer() offer.populate_from_dict(data) offer.product = product offer.product.owningOfferer = venue.managingOfferer offer.venue = venue offer.bookingEmail = offer_data.booking_email offer.externalTicketOfficeUrl = offer_data.external_ticket_office_url offer.audioDisabilityCompliant = offer_data.audio_disability_compliant offer.mentalDisabilityCompliant = offer_data.mental_disability_compliant offer.motorDisabilityCompliant = offer_data.motor_disability_compliant offer.visualDisabilityCompliant = offer_data.visual_disability_compliant repository.save(offer) admin_emails.send_offer_creation_notification_to_administration( offer, user) return offer
def fill_offer_attributes(self, allocine_offer: Offer): allocine_offer.venueId = self.venue.id allocine_offer.bookingEmail = self.venue.bookingEmail if "description" in self.movie_information: allocine_offer.description = self.movie_information["description"] if "duration" in self.movie_information: allocine_offer.durationMinutes = self.movie_information["duration"] if not allocine_offer.extraData: allocine_offer.extraData = {} if "visa" in self.movie_information: allocine_offer.extraData["visa"] = self.movie_information["visa"] if "stageDirector" in self.movie_information: allocine_offer.extraData["stageDirector"] = self.movie_information[ "stageDirector"] movie_version = (ORIGINAL_VERSION_SUFFIX if _is_original_version_offer( allocine_offer.idAtProviders) else FRENCH_VERSION_SUFFIX) allocine_offer.name = f"{self.movie_information['title']} - {movie_version}" allocine_offer.type = str(EventType.CINEMA) allocine_offer.productId = self.last_product_id is_new_offer_to_insert = allocine_offer.id is None if is_new_offer_to_insert: allocine_offer.id = get_next_offer_id_from_database() allocine_offer.isDuo = self.isDuo if movie_version == ORIGINAL_VERSION_SUFFIX: self.last_vo_offer_id = allocine_offer.id else: self.last_vf_offer_id = allocine_offer.id
def fill_offer_attributes(self, allocine_offer: Offer) -> None: allocine_offer.venueId = self.venue.id allocine_offer.bookingEmail = self.venue.bookingEmail allocine_offer.withdrawalDetails = self.venue.withdrawalDetails self.update_from_movie_information(allocine_offer, self.movie_information) allocine_offer.extraData["theater"] = { "allocine_movie_id": self.movie_information["internal_id"], "allocine_room_id": self.room_internal_id, } if "visa" in self.movie_information: allocine_offer.extraData["visa"] = self.movie_information["visa"] if "stageDirector" in self.movie_information: allocine_offer.extraData["stageDirector"] = self.movie_information[ "stageDirector"] movie_version = (ORIGINAL_VERSION_SUFFIX if _is_original_version_offer( allocine_offer.idAtProviders) else FRENCH_VERSION_SUFFIX) allocine_offer.name = f"{self.movie_information['title']} - {movie_version}" allocine_offer.subcategoryId = subcategories.SEANCE_CINE.id allocine_offer.productId = self.last_product_id is_new_offer_to_insert = allocine_offer.id is None if is_new_offer_to_insert: allocine_offer.id = get_next_offer_id_from_database() allocine_offer.isDuo = self.isDuo if movie_version == ORIGINAL_VERSION_SUFFIX: self.last_vo_offer_id = allocine_offer.id else: self.last_vf_offer_id = allocine_offer.id
def update_offer( offer: Offer, bookingEmail: str = UNCHANGED, description: str = UNCHANGED, isNational: bool = UNCHANGED, name: str = UNCHANGED, extraData: dict = UNCHANGED, externalTicketOfficeUrl: str = UNCHANGED, url: str = UNCHANGED, withdrawalDetails: str = UNCHANGED, isActive: bool = UNCHANGED, isDuo: bool = UNCHANGED, durationMinutes: int = UNCHANGED, mediaUrls: list[str] = UNCHANGED, ageMin: int = UNCHANGED, ageMax: int = UNCHANGED, conditions: str = UNCHANGED, venueId: str = UNCHANGED, productId: str = UNCHANGED, audioDisabilityCompliant: bool = UNCHANGED, mentalDisabilityCompliant: bool = UNCHANGED, motorDisabilityCompliant: bool = UNCHANGED, visualDisabilityCompliant: bool = UNCHANGED, ) -> Offer: validation.check_validation_status(offer) # fmt: off modifications = { field: new_value for field, new_value in locals().items() if field != 'offer' and new_value is not UNCHANGED # has the user provided a value for this field and getattr(offer, field) != new_value # is the value different from what we have on database? } # fmt: on if not modifications: return offer if offer.isFromProvider: validation.check_update_only_allowed_fields_for_offer_from_provider( set(modifications), offer.lastProvider) offer.populate_from_dict(modifications) if offer.product.owningOfferer and offer.product.owningOfferer == offer.venue.managingOfferer: offer.product.populate_from_dict(modifications) product_has_been_updated = True else: product_has_been_updated = False if offer.isFromAllocine: offer.fieldsUpdated = list( set(offer.fieldsUpdated) | set(modifications)) repository.save(offer) logger.info("Offer has been updated", extra={"offer": offer.id}) if product_has_been_updated: repository.save(offer.product) logger.info("Product has been updated", extra={"product": offer.product.id}) search.async_index_offer_ids([offer.id]) return offer