Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
mhieta committed Aug 30, 2024
2 parents 85594ca + 216fb7c commit f918885
Show file tree
Hide file tree
Showing 35 changed files with 954 additions and 246 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
environment: test
strategy:
matrix:
Expand Down
34 changes: 28 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2024-08-30

### Added

- Add VAT and VAT-amount to Refund-model ([d4e72f8](https://github.com/City-of-Helsinki/parking-permits/commit/d4e72f80d6ac20fc85b69c47e843d55f8bbbb31b))
- Add VAT to Refund in Django Admin ([273018b](https://github.com/City-of-Helsinki/parking-permits/commit/273018bd9c34c5456b1d52c0fd83ce91e9fbd1ed))

### Changed

- Update low-emission criteria evaluation rules ([08aeef5](https://github.com/City-of-Helsinki/parking-permits/commit/08aeef571eab6228830ced83c9c4d2028023b5d9))
- Explicitly use Ubuntu 20.04 base image with GitHub Actions ([8c29d7a](https://github.com/City-of-Helsinki/parking-permits/commit/8c29d7a3aa4038350bec140d4f6baa83d9f506e3))
- Update VAT-percentages from Talpa-endpoints ([f79f9dd](https://github.com/City-of-Helsinki/parking-permits/commit/f79f9dda6b258823ec689da2ac8a48b533a87766))
- Update VAT-percentages for Refund ([4afdb80](https://github.com/City-of-Helsinki/parking-permits/commit/4afdb80edf6f1f1fc339a24fa39f43c3fc43a4c2))
- Use AWS ECR Docker image repository ([c7ef79d](https://github.com/City-of-Helsinki/parking-permits/commit/c7ef79d7dd9ca830297a7fd0da2ff301b576db79))
- Change Refund VAT to be dynamic ([8e7ca2f](https://github.com/City-of-Helsinki/parking-permits/commit/8e7ca2fa716569815813bcb31b5e951aa1672f48))
- Change Refund creation to be VAT-based ([1ce4469](https://github.com/City-of-Helsinki/parking-permits/commit/1ce44694eff95ff4a42c3d8012d1242da30c067c))

### Fixed

- Fix price calculation for first days of the month ([f96ebd6](https://github.com/City-of-Helsinki/parking-permits/commit/f96ebd69a737acad40e52df9332d4c257b38f2b0))
- Fix find_next_date-utility function ([326da62](https://github.com/City-of-Helsinki/parking-permits/commit/326da625d2fb8d46ebb1cc918854b35af24d890e))

## [1.1.0] - 2024-06-19

### Added

- Add changelog to project
- Add changelog to project ([2d4300a](https://github.com/City-of-Helsinki/parking-permits/commit/2d4300a6bc329533474e33b861d2e73cc887460c))

### Changed

- Update Python version to 3.11 from CI
- Update application packages
- Update fi/sv/en translations
- Update Azure CI-settings
- Update Python version to 3.11 from CI ([dd07848](https://github.com/City-of-Helsinki/parking-permits/commit/dd0784825344cdf09081b6a7dad937241e1c65d5))
- Update application packages ([f0e2e5c](https://github.com/City-of-Helsinki/parking-permits/commit/f0e2e5c3dd72cfc8102d2e888651b5e60b3d6019))
- Update fi/sv/en translations ([f60e86d](https://github.com/City-of-Helsinki/parking-permits/commit/f60e86d659daf8bf342e294c6c53ddd41becfee5))
- Update Azure CI-settings ([456ddf4](https://github.com/City-of-Helsinki/parking-permits/commit/456ddf40b77952101877d9b7375c596fae4c447d))

### Removed

- Remove obsolete Docker Compose version
- Remove obsolete Docker Compose version ([8e3b8ab](https://github.com/City-of-Helsinki/parking-permits/commit/8e3b8ab3d8173b575dc8d55313469b438238cbb0))

## [1.0.0] - 2024-06-12

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:20.04 as base
FROM public.ecr.aws/ubuntu/ubuntu:20.04 as base

WORKDIR /app

Expand Down
1 change: 1 addition & 0 deletions parking_permits/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class RefundAdmin(admin.ModelAdmin):
"status",
"created_at",
"accepted_at",
"vat",
)
list_select_related = ("order",)
ordering = ("-created_at",)
Expand Down
10 changes: 7 additions & 3 deletions parking_permits/admin_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from parking_permits.services.parkkihubi import sync_with_parkkihubi
from users.models import ParkingPermitGroups

from .constants import EventFields, Origin
from .constants import DEFAULT_VAT, EventFields, Origin
from .decorators import (
is_customer_service,
is_inspectors,
Expand Down Expand Up @@ -817,14 +817,19 @@ def resolve_update_resident_permit(
order=order,
amount=-customer_total_price_change,
iban=iban,
vat=(
order.order_items.first().vat
if order.order_items.exists()
else DEFAULT_VAT
),
description=f"Refund for updating permit: {permit.id}",
)
refund.permits.add(permit)
logger.info(f"Refund for lowered permit price created: {refund}")
ParkingPermitEventFactory.make_create_refund_event(
permit, refund, created_by=request.user
)
send_refund_email(RefundEmailType.CREATED, customer, refund)
send_refund_email(RefundEmailType.CREATED, customer, [refund])

bypass_traficom_validation = permit_info.get("bypass_traficom_validation", False)

Expand Down Expand Up @@ -1004,7 +1009,6 @@ def resolve_end_permit(
request.user,
permit,
end_type=end_type,
payment_type=OrderPaymentType.CASHIER_PAYMENT,
iban=iban,
)
return {"success": True}
Expand Down
4 changes: 3 additions & 1 deletion parking_permits/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from decimal import Decimal

DEFAULT_VAT = Decimal(0.255)
SECONDARY_VEHICLE_PRICE_INCREASE = 50
VAT_PERCENTAGE = 24


class Origin:
Expand Down
1 change: 0 additions & 1 deletion parking_permits/customer_permit.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ def end(
user,
*permits,
end_type=end_type,
payment_type=OrderPaymentType.ONLINE_PAYMENT,
iban=iban,
subscription_cancel_reason=subscription_cancel_reason,
cancel_from_talpa=cancel_from_talpa,
Expand Down
4 changes: 3 additions & 1 deletion parking_permits/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ def get_refund_content(refund):
_("Customer")
+ ": "
+ f"{refund.order.customer.first_name} {refund.order.customer.last_name}",
_("Amount") + ": " + f"{refund.amount} e (" + _("incl. VAT") + " 24%)",
_("Amount")
+ ": "
+ f"{refund.amount} e ({_('incl. VAT')} {_format_percentage(refund.vat_percent)} %)",
_("IBAN") + ": " + f"{refund.iban}",
_("Status") + ": " + f"{refund.get_status_display()}",
_("Extra info") + ": " + f"{refund.description}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def handle(self, *args, **options):
"type": ProductType.RESIDENT,
"unit_price": zone_price
* Decimal(options["price_increment_factor_old_zone"]),
"vat": Decimal(0.24),
"vat": Decimal(0.255),
"low_emission_discount": Decimal(
options["low_emission_discount_old_zone"]
),
Expand All @@ -90,7 +90,7 @@ def handle(self, *args, **options):
"type": ProductType.RESIDENT,
"unit_price": zone_price
* Decimal(options["price_increment_factor_new_zone"]),
"vat": Decimal(0.24),
"vat": Decimal(0.255),
"low_emission_discount": Decimal(
options["low_emission_discount_new_zone"]
),
Expand Down
20 changes: 20 additions & 0 deletions parking_permits/migrations/0059_refund_vat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 5.0.6 on 2024-08-09 04:50

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("parking_permits", "0058_alter_vehicle_vehicle_class"),
]

operations = [
migrations.AddField(
model_name="refund",
name="vat",
field=models.DecimalField(
decimal_places=4, default=0.24, max_digits=6, verbose_name="VAT"
),
),
]
8 changes: 7 additions & 1 deletion parking_permits/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.utils.translation import gettext_lazy as _
from helsinki_gdpr.models import SerializableMixin

from ..constants import DEFAULT_VAT
from ..exceptions import (
OrderCancelError,
OrderCreationFailed,
Expand Down Expand Up @@ -664,10 +665,15 @@ def cancel(self, cancel_reason, cancel_from_talpa=True, iban=""):
order=order,
amount=permit.total_refund_amount,
iban=iban,
vat=(
order.order_items.first().vat
if order.order_items.exists()
else DEFAULT_VAT
),
description=f"Refund for ending permit {str(permit.id)}",
)
refund.permits.add(permit)
send_refund_email(RefundEmailType.CREATED, permit.customer, refund)
send_refund_email(RefundEmailType.CREATED, permit.customer, [refund])
ParkingPermitEventFactory.make_create_refund_event(
permit, refund, created_by=permit.customer.user
)
Expand Down
113 changes: 94 additions & 19 deletions parking_permits/models/parking_permit.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ def can_be_refunded(self):

@property
def total_refund_amount(self):
return self.get_refund_amount_for_unused_items()
return self.get_total_refund_amount_for_unused_items()

@property
def zone_changed(self):
Expand Down Expand Up @@ -677,6 +677,7 @@ def get_price_change_list(self, new_zone, is_low_emission):
"previous_price": previous_price,
"new_price": new_price,
"price_change_vat": price_change_vat,
"price_change_vat_percent": new_product.vat_percentage,
"price_change": diff_price,
"start_date": start_date,
"end_date": end_date,
Expand Down Expand Up @@ -720,18 +721,25 @@ def get_price_change_list(self, new_zone, is_low_emission):
# quantity by 1
price_change_list[-1]["month_count"] += 1
else:
# if the price is decreased, get VAT from the previous permit order
vat = (
self.latest_order.vat
if diff_price < 0 and self.latest_order
else new_product.vat
)
# if the product is different or diff price is different,
# create a new price change item
price_change_vat = calc_vat_price(
diff_price, new_product.vat
).quantize(Decimal("0.0001"))
price_change_vat = calc_vat_price(diff_price, vat).quantize(
Decimal("0.0001")
)

price_change_list.append(
{
"product": new_product.name,
"previous_price": previous_price,
"new_price": new_price,
"price_change_vat": price_change_vat,
"price_change_vat_percent": vat * 100,
"price_change": diff_price,
"start_date": month_start_date,
"month_count": 1,
Expand Down Expand Up @@ -813,7 +821,22 @@ def extend_permit(self, additional_months):
def cancel_extension_requests(self):
self.permit_extension_requests.cancel_pending()

def get_refund_amount_for_unused_items(self):
def get_vat_based_refund_amounts_for_unused_items(self):
totals_per_vat = {}
if not self.can_be_refunded:
return totals_per_vat

unused_order_items = self.get_unused_order_items_for_all_orders()

for order_item, quantity, date_range in unused_order_items:
vat = order_item.vat
if vat not in totals_per_vat:
totals_per_vat[vat] = {"total": Decimal(0), "order": None}
totals_per_vat[vat]["total"] += order_item.unit_price * quantity
totals_per_vat[vat]["order"] = order_item.order
return totals_per_vat

def get_total_refund_amount_for_unused_items(self):
total = Decimal(0)
if not self.can_be_refunded:
return total
Expand All @@ -824,6 +847,19 @@ def get_refund_amount_for_unused_items(self):
total += order_item.unit_price * quantity
return total

def can_create_single_refund(self):
if not self.can_be_refunded:
return False

unused_order_items = self.get_unused_order_items()
if not unused_order_items:
return False
first_order_item, _, _ = unused_order_items[0]
first_vat = first_order_item.vat
return all(
order_item.vat == first_vat for order_item, _, _ in unused_order_items
)

def parse_temporary_vehicle_times(
self,
start_time: str,
Expand Down Expand Up @@ -908,22 +944,61 @@ def is_temporary_vehicle_limit_exceeded(self) -> bool:
)

def get_unused_order_items(self):
unused_start_date = timezone.localdate(self.next_period_start_time)
if not self.is_fixed_period:
order_items = self.latest_order_items
return [
[
item,
item.quantity,
(
timezone.localtime(item.start_time).date(),
timezone.localtime(item.end_time).date(),
),
]
for item in order_items
if self.is_open_ended:
return self.get_unused_order_items_for_open_ended_permit()
return self.get_unused_order_items_for_order(self.latest_order)

def get_unused_order_items_for_open_ended_permit(self):
order_items = self.latest_order_items
return [
[
item,
item.quantity,
(
timezone.localtime(item.start_time).date(),
timezone.localtime(item.end_time).date(),
),
]
for item in order_items
]

def get_unused_order_items_for_all_orders(self):
if self.is_open_ended:
return self.get_unused_order_items_for_open_ended_permit()
unused_order_items = []
for order in self.orders.all().order_by("created_at"):
unused_order_items.extend(self.get_unused_order_items_for_order(order))
# sort by order item start time
unused_order_items.sort(key=lambda x: x[0].start_time)

# loop over unused order items, compare dates and remove overlapping month quantities from next order items
prev_order_item_end_date = None
for unused_order_item in unused_order_items:
_, quantity, date_range = unused_order_item
start_date, end_date = date_range
if not prev_order_item_end_date and end_date:
prev_order_item_end_date = end_date
continue
if (
quantity == 0
or not start_date
or start_date >= prev_order_item_end_date
):
continue
if start_date < prev_order_item_end_date:
# reduce quantity by overlapping months
overlapping_months = diff_months_ceil(
start_date, prev_order_item_end_date
)
unused_order_item[1] -= overlapping_months
prev_order_item_end_date = end_date

return unused_order_items

def get_unused_order_items_for_order(self, order):
unused_start_date = timezone.localdate(self.next_period_start_time)

order_items = self.latest_order_items.filter(
order_items = order.order_items.filter(
end_time__date__gte=unused_start_date
).order_by("start_time")

Expand Down
2 changes: 2 additions & 0 deletions parking_permits/models/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def get_talpa_pricing(self, is_low_emission, is_secondary):
"price_gross": "20.00",
"price_net": "16.13",
"price_vat": "3.87",
"vat_percentage": "25.50",
}
"""
price_gross = self.get_modified_unit_price(is_low_emission, is_secondary)
Expand All @@ -193,6 +194,7 @@ def get_talpa_pricing(self, is_low_emission, is_secondary):
"price_gross": pricing.format_gross(),
"price_net": pricing.format_net(),
"price_vat": pricing.format_vat(),
"vat_percentage": "%.2f" % self.vat_percentage,
}

def get_merchant_id(self):
Expand Down
Loading

0 comments on commit f918885

Please sign in to comment.