def test_create_order_berth_product(api_client): berth_lease = BerthLeaseFactory( start_date=calculate_season_start_date(), berth__berth_type__mooring_type=7 ) customer_id = to_global_id(ProfileNode, berth_lease.customer.id) lease_id = to_global_id(BerthLeaseNode, berth_lease.id) expected_product = BerthProduct.objects.get_in_range( berth_lease.berth.berth_type.width, get_berth_lease_pricing_category(berth_lease), ) expected_product_id = to_global_id(BerthProductNode, expected_product.id) variables = { "leaseId": lease_id, } assert Order.objects.count() == 0 executed = api_client.execute(CREATE_ORDER_MUTATION, input=variables) assert Order.objects.count() == 1 assert executed["data"]["createOrder"]["order"].pop("id") is not None assert executed["data"]["createOrder"]["order"] == { "price": str( expected_product.price_for_tier(berth_lease.berth.pier.price_tier) ), "taxPercentage": str(expected_product.tax_percentage), "customer": {"id": customer_id}, "product": {"id": expected_product_id}, "lease": {"id": variables["leaseId"]}, }
def test_create_berth_switch_offer_old_lease(api_client, berth_application, berth): berth_lease = BerthLeaseFactory( start_date=calculate_season_start_date(), end_date=calculate_season_end_date(), status=LeaseStatus.PAID, ) BerthLeaseFactory( customer=berth_lease.customer, start_date=calculate_season_start_date(), end_date=calculate_season_end_date(), status=LeaseStatus.PAID, ) berth_application.customer = berth_lease.customer berth_application.berth_switch = BerthSwitchFactory( berth=berth_lease.berth) berth_application.save() variables = { "applicationId": to_global_id(BerthApplicationNode, berth_application.id), "newBerthId": to_global_id(BerthNode, berth.id), "oldLeaseId": to_global_id(BerthLeaseNode, berth_lease.id), } executed = api_client.execute(CREATE_BERTH_SWITCH_OFFER_MUTATION, input=variables) assert executed["data"]["createBerthSwitchOffer"]["berthSwitchOffer"] == { "status": OfferStatus.DRAFTED.name, "dueDate": None, "application": { "id": variables["applicationId"], "status": "OFFER_GENERATED" }, "customer": { "id": to_global_id(ProfileNode, berth_lease.customer.id) }, "lease": { "id": to_global_id(BerthLeaseNode, berth_lease.id) }, "berth": { "id": variables["newBerthId"] }, }
def get_old_lease(application: BerthApplication) -> BerthLease: # Based on the information filled by the customer on the switch application, # we retrieve the corresponding lease on the current season return BerthLease.objects.get( customer=application.customer, berth=application.berth_switch.berth, status=LeaseStatus.PAID, start_date=calculate_season_start_date(), end_date=calculate_season_end_date(), )
def calculate_lease_start_and_end_dates(start_date: date) -> Tuple[date, date]: """ When importing customers from the old Timmi system, we try to create leases based on the order data we have. For that, we use the created_at timestamp of the order. Assuming that the order was generated for the upcoming season, we calculate the start and end dates for the lease. """ end_date = calculate_season_end_date(start_date) # If the start date is after the calculated end date for the associated season (year), # the lease would belong to the next year's season if start_date > end_date: start_date = calculate_season_start_date(start_date) + relativedelta( years=1) end_date = calculate_season_end_date(start_date) # If the order was created before the start date's year's season start, the start date will # be the that year's season elif start_date < calculate_season_start_date(start_date): start_date = calculate_season_start_date(start_date) # Otherwise, if the start date is during the season dates, it should be the same return start_date, end_date
def test_create_berth_switch_offer_refresh_profile(api_client, berth_application, berth): faker = Faker("fi_FI") berth_lease = BerthLeaseFactory( start_date=calculate_season_start_date(), end_date=calculate_season_end_date(), status=LeaseStatus.PAID, ) berth_application.customer = berth_lease.customer berth_application.berth_switch = BerthSwitchFactory( berth=berth_lease.berth) berth_application.save() first_name = faker.first_name() last_name = faker.last_name() email = faker.email() phone = faker.phone_number() data = { "id": to_global_id(ProfileNode, berth_lease.customer.id), "first_name": first_name, "last_name": last_name, "primary_email": { "email": email }, "primary_phone": { "phone": phone }, } variables = { "applicationId": to_global_id(BerthApplicationNode, berth_application.id), "newBerthId": to_global_id(BerthNode, berth.id), "profileToken": "profile-token", } with mock.patch( "customers.services.profile.requests.post", side_effect=mocked_response_profile(count=0, data=data, use_edges=False), ): executed = api_client.execute( CREATE_BERTH_SWITCH_OFFER_MUTATION_CUSTOMER_FIELDS, input=variables) assert executed["data"]["createBerthSwitchOffer"]["berthSwitchOffer"] == { "customerFirstName": first_name, "customerLastName": last_name, "customerEmail": email, "customerPhone": phone, }
def calculate_product_partial_season_price( base_price: Decimal, start_date: date, end_date: date, summer_season: bool = True) -> Decimal: # If it's the "normal" (summer) season, calculate with the normal dates season_days = calculate_season_end_date() - calculate_season_start_date() # If it's for the opposite season ("winter season"), calculate the inverse if not summer_season: season_days = (calculate_winter_season_end_date() - calculate_winter_season_start_date()) delta = (end_date - start_date).days price = (delta * base_price) / season_days.days return price
def test_create_berth_switch_offer_missing_application_switch( api_client, berth_application, berth): berth_lease = BerthLeaseFactory(start_date=calculate_season_start_date(), end_date=calculate_season_end_date()) berth_application.customer = berth_lease.customer berth_application.berth_switch = None berth_application.save() berth_lease.status = LeaseStatus.PAID berth_lease.save() variables = { "applicationId": to_global_id(BerthApplicationNode, berth_application.id), "newBerthId": to_global_id(BerthNode, berth.id), } executed = api_client.execute(CREATE_BERTH_SWITCH_OFFER_MUTATION, input=variables) assert_in_errors("Application must be a switch application", executed)
def test_create_berth_switch_offer_wrong_berth(api_client, berth_application, berth): berth_lease = BerthLeaseFactory( start_date=calculate_season_start_date(), end_date=calculate_season_end_date(), status=LeaseStatus.PAID, ) berth_application.customer = berth_lease.customer berth_application.berth_switch = BerthSwitchFactory( berth=BerthFactory(number="9999"), ) berth_application.save() variables = { "applicationId": to_global_id(BerthApplicationNode, berth_application.id), "newBerthId": to_global_id(BerthNode, berth.id), } executed = api_client.execute(CREATE_BERTH_SWITCH_OFFER_MUTATION, input=variables) assert_in_errors("NO_LEASE", executed)
def get_queryset(self): """ The QuerySet annotates whether a berth is available or not. For this, it considers the following criteria: - If there are leases associated to the berth - If any lease ends during the current or the last season (previous year) + If a lease ends during the current season: * It needs to have a "valid" status (DRAFTED, OFFERED, PAID, ERROR) + If a lease ended during the last season: * It should not have been renewed for the next season * It needs to have a "valid" status (PAID) """ from leases.models import BerthLease from payments.models import BerthSwitchOffer season_start = calculate_season_start_date() season_end = calculate_berth_lease_end_date() current_date = today().date() # If today is before the season ends but during the same year if current_date < season_end and current_date.year == season_end.year: last_year = current_date.year - 1 else: last_year = current_date.year in_current_season = Q( # Check the lease starts at some point the during the season start_date__gte=season_start, # Check the lease ends earliest at the beginning of the season # (for leases terminated before the season started) end_date__gte=season_start, # Check the lease ends latest at the end of the season end_date__lte=season_end, ) in_last_season = Q(end_date__year=last_year) active_current_status = Q(status__in=ACTIVE_LEASE_STATUSES) paid_status = Q(status=LeaseStatus.PAID) # In case the renewed leases for the upcoming season haven't been sent # or some of the leases that had to be fixed (also for the upcoming season) # are pending, we check for leases on the previous season that have already been paid, # which in most cases means that the customer will keep the berth for the next season as well. # # Pre-filter the leases for the upcoming/current season renewed_leases = BerthLease.objects.filter( in_current_season, berth=OuterRef("berth"), customer=OuterRef("customer"), ).values("pk") # Filter the leases from the previous season that have already been renewed previous_leases = (BerthLease.objects.exclude( Exists(renewed_leases)).filter(in_last_season, paid_status, berth=OuterRef("pk")).values("pk")) # For the leases that have been renewed or are valid during the current season. # Filter the leases on the current season that have not been rejected current_leases = BerthLease.objects.filter( in_current_season, active_current_status, berth=OuterRef("pk")).values("pk") # A berth is NOT available when it already has a lease on the current (or upcoming) season # or when the previous season lease has been paid and the new leases have not been sent. active_leases = ~Exists(previous_leases | current_leases) # Additionally, the berth is also NOT available when there is a switch offer drafted or offered # (this requires separate Exists clauses active_offers = ~Exists( BerthSwitchOffer.objects.filter( status__in=(OfferStatus.DRAFTED, OfferStatus.OFFERED), berth=OuterRef("pk"), ).values("pk")) # Need to explicitly mark the result of the AND as a BooleanField is_available = ExpressionWrapper(Q(active_leases & active_offers), output_field=BooleanField()) return (super().get_queryset().annotate( is_available=is_available, _int_number=RawSQL( "CAST(substring(number FROM '^[0-9]+') AS INTEGER)", params=[], output_field=models.PositiveSmallIntegerField(), ), ).order_by("_int_number"))
else: return None # place for variables that are used while applying a template template_context = {} LEASE_TEMPLATE = { "harbor_servicemap_id": lambda: template_context["berth"]["harbor_servicemap_id"], "pier_id": lambda: template_context["berth"]["pier_identifier"], "berth_number": lambda: int(template_context["berth"]["number"]), "start_date": lambda: calculate_season_start_date( datetime.date(template_context["season_year"], 1, 1)).isoformat(), "end_date": lambda: calculate_season_end_date( datetime.date(template_context["season_year"], 1, 1)).isoformat(), "boat_index": lambda: template_context["boat_index"], } def _order_created_at(): return fake.date_between_dates( datetime.date(template_context["season_year"], 1, 1), calculate_season_end_date( datetime.date(template_context["season_year"], 1, 1)), ).isoformat()