def test_get_appointments_in_datetime_range( self, stylist_data: Stylist, ): appointments: Dict[str, Appointment] = stylist_appointments_data( stylist_data) all_appointments = stylist_data.get_appointments_in_datetime_range() assert (all_appointments.count() == 7) appointments_from_start = stylist_data.get_appointments_in_datetime_range( datetime_from=None, datetime_to=stylist_data.get_current_now(), including_to=True, exclude_statuses=[ AppointmentStatus.CANCELLED_BY_CLIENT, AppointmentStatus.CANCELLED_BY_STYLIST ]) assert (frozenset([a.id for a in appointments_from_start]) == frozenset([ appointments['past_appointment'].id, appointments['current_appointment'].id, appointments['last_week_appointment'].id, ])) apppointmens_to_end = stylist_data.get_appointments_in_datetime_range( datetime_from=stylist_data.get_current_now(), datetime_to=None, exclude_statuses=[ AppointmentStatus.CANCELLED_BY_CLIENT, AppointmentStatus.CANCELLED_BY_STYLIST ]) assert (frozenset([a.id for a in apppointmens_to_end]) == frozenset([ appointments['current_appointment'].id, appointments['future_appointment'].id, appointments['late_night_appointment'].id, appointments['next_day_appointment'].id, appointments['next_week_appointment'].id, ])) appointments_between = stylist_data.get_appointments_in_datetime_range( datetime_from=pytz.timezone('UTC').localize( datetime.datetime(2018, 5, 13)), datetime_to=pytz.timezone('UTC').localize( datetime.datetime(2018, 5, 15, 23, 59, 59)), exclude_statuses=[ AppointmentStatus.CANCELLED_BY_CLIENT, AppointmentStatus.CANCELLED_BY_STYLIST ]) assert (frozenset([a.id for a in appointments_between]) == frozenset([ appointments['past_appointment'].id, appointments['current_appointment'].id, appointments['future_appointment'].id, appointments['late_night_appointment'].id, appointments['next_day_appointment'].id, ]))
def build_appointment_preview_dict( stylist: Stylist, client: Optional[Client], preview_request: AppointmentPreviewRequest ) -> AppointmentPreviewResponse: service_items: List[AppointmentServicePreview] = [] appointment: Optional[Appointment] = None status = AppointmentStatus.NEW if preview_request.appointment_uuid is not None: appointment = get_object_or_404(stylist.appointments, uuid=preview_request.appointment_uuid) status = appointment.status total_discount_percentage: int = 0 if appointment: total_discount_percentage = appointment.total_discount_percentage for service_request_item in preview_request.services: appointment_service: Optional[AppointmentService] = None if appointment: appointment_service = appointment.services.filter( service_uuid=service_request_item['service_uuid']).last() service_client_price: Optional[Decimal] = service_request_item[ 'client_price'] if 'client_price' in service_request_item else None if appointment_service: if service_client_price: # client price (overriding current regular price) was provided. We will # temporarily (w/out committing to DB) replace regular price of the service # with client-supplied, and will re-calculate client_price (i.e. client-facing) # with existing appointment's discount appointment_service.set_client_price(service_client_price, commit=False) # service already exists in appointment, and will not be recalculated, # so we should take it's data verbatim service_item = AppointmentServicePreview( uuid=appointment_service.uuid, service_uuid=appointment_service.service_uuid, service_name=appointment_service.service_name, regular_price=appointment_service.regular_price, client_price=appointment_service.client_price, duration=appointment_service.duration, is_original=appointment_service.is_original) if not total_discount_percentage: total_discount_percentage = appointment_service.discount_percentage else: # appointment service doesn't exist in appointment yet, and is to be added, so we # need to calculate the price for it. service: StylistService = stylist.services.get( uuid=service_request_item['service_uuid']) # We need to decide what we use for the base price. If client_price is supplied # we will use it as a base price. Otherwise, we will take base price from stylist's # service if not service_client_price: regular_price = service.regular_price else: regular_price = service_client_price # now when we know the base price, we need to calculate price with discount. # if the appointment we're previewing is based on the existing appointment - # we will just take discount percentage from it. Otherwise, we need to run # pricing calculation for given client and stylist on the given date, and see # if there is any discount there if not appointment: calculated_price = calculate_price_and_discount_for_client_on_date( service=service, client=client, date=preview_request.datetime_start_at.date()) client_price = Decimal(calculated_price.price) if not total_discount_percentage: total_discount_percentage = calculated_price.discount_percentage else: client_price = calculate_price_with_discount_based_on_appointment( regular_price, appointment) service_item = AppointmentServicePreview( uuid=None, service_uuid=service.uuid, service_name=service.name, regular_price=service.regular_price, client_price=client_price, duration=service.duration, is_original=True if not appointment else False) service_items.append(service_item) total_client_price_before_tax = sum( [s.client_price for s in service_items], Decimal(0)) total_regular_price = sum([s.regular_price for s in service_items], Decimal(0)) appointment_prices: AppointmentPrices = calculate_appointment_prices( price_before_tax=total_client_price_before_tax, include_card_fee=preview_request.has_card_fee_included, include_tax=preview_request.has_tax_included, tax_rate=stylist.tax_rate, card_fee=stylist.card_fee) duration = stylist.service_time_gap conflicts_with = stylist.get_appointments_in_datetime_range( preview_request.datetime_start_at, preview_request.datetime_start_at, including_to=True, exclude_statuses=[ AppointmentStatus.CANCELLED_BY_STYLIST, AppointmentStatus.CANCELLED_BY_CLIENT ]) return AppointmentPreviewResponse( duration=duration, conflicts_with=conflicts_with, total_client_price_before_tax=appointment_prices. total_client_price_before_tax, grand_total=appointment_prices.grand_total, tax_percentage=float(stylist.tax_rate) * 100, card_fee_percentage=float(stylist.card_fee) * 100, total_tax=appointment_prices.total_tax, total_card_fee=appointment_prices.total_card_fee, has_tax_included=appointment_prices.has_tax_included, has_card_fee_included=appointment_prices.has_card_fee_included, services=service_items, stylist=stylist, datetime_start_at=preview_request.datetime_start_at, status=status, total_discount_percentage=total_discount_percentage, total_discount_amount=max( total_regular_price - total_client_price_before_tax, Decimal(0)))