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"]},
    }
예제 #2
0
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"]
        },
    }
예제 #3
0
 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(),
     )
예제 #4
0
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
예제 #5
0
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,
    }
예제 #6
0
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
예제 #7
0
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)
예제 #8
0
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)
예제 #9
0
    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()