2020-02-28 07:59:32 +00:00
|
|
|
from functools import reduce
|
2020-02-29 08:08:30 +00:00
|
|
|
from datetime import datetime
|
2020-02-28 13:57:45 +00:00
|
|
|
from rest_framework import mixins
|
|
|
|
from rest_framework.viewsets import GenericViewSet
|
2020-02-29 08:08:30 +00:00
|
|
|
from django.db.models import Q
|
|
|
|
from .models import Bill, Payment, PaymentMethod, Order
|
|
|
|
from django.utils import timezone
|
2020-03-01 11:23:04 +00:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
from dateutil.relativedelta import relativedelta
|
2020-02-28 07:59:32 +00:00
|
|
|
|
|
|
|
def sum_amounts(entries):
|
2020-02-28 08:10:36 +00:00
|
|
|
return reduce(lambda acc, entry: acc + entry.amount, entries, 0)
|
2020-02-28 07:59:32 +00:00
|
|
|
|
|
|
|
def get_balance_for(user):
|
2020-02-28 08:10:36 +00:00
|
|
|
bills = sum_amounts(Bill.objects.filter(owner=user))
|
|
|
|
payments = sum_amounts(Payment.objects.filter(owner=user))
|
|
|
|
return payments - bills
|
2020-02-28 07:59:32 +00:00
|
|
|
|
2020-02-28 08:10:36 +00:00
|
|
|
def get_payment_method_for(user):
|
|
|
|
methods = PaymentMethod.objects.filter(owner=user)
|
|
|
|
for method in methods:
|
|
|
|
# Do we want to do something with non-primary method?
|
|
|
|
if method.primary:
|
|
|
|
return method
|
|
|
|
|
|
|
|
return None
|
2020-02-28 13:57:45 +00:00
|
|
|
|
2020-02-29 08:08:30 +00:00
|
|
|
def beginning_of_month(date):
|
|
|
|
return datetime(year=date.year, date=now.month, day=0)
|
|
|
|
|
|
|
|
def generate_bills_for(year, month, user, allowed_delay):
|
|
|
|
# /!\ We exclusively work on the specified year and month.
|
|
|
|
|
|
|
|
# Default values for next bill (if any). Only saved at the end of
|
|
|
|
# this method, if relevant.
|
|
|
|
tz = timezone.get_current_timezone()
|
|
|
|
next_bill = Bill(owner=user,
|
|
|
|
starting_date=datetime(year=year, month=month, day=1, tzinfo=tz),
|
|
|
|
ending_date=datetime(year=year, month=month, day=28, tzinfo=tz),
|
|
|
|
creation_date=timezone.now(),
|
|
|
|
due_date=timezone.now() + allowed_delay)
|
|
|
|
|
|
|
|
# Select all orders active on the request period.
|
|
|
|
orders = Order.objects.filter(
|
|
|
|
Q(ending_date__gt=next_bill.starting_date) | Q(ending_date__isnull=True),
|
|
|
|
owner=user)
|
|
|
|
|
|
|
|
# Check if there is already a bill covering the order and period pair:
|
|
|
|
# * Get latest bill by ending_date: previous_bill.ending_date
|
|
|
|
# * If previous_bill.ending_date is before next_bill.ending_date, a new
|
|
|
|
# bill has to be generated.
|
|
|
|
unpaid_orders = []
|
|
|
|
for order in orders:
|
|
|
|
try:
|
|
|
|
previous_bill = order.bill.latest('-ending_date')
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
previous_bill = None
|
|
|
|
|
|
|
|
if previous_bill == None or previous_bill.ending_date < next_bill.ending_date:
|
|
|
|
unpaid_orders.append(order)
|
|
|
|
|
|
|
|
# Commit next_bill if it there are 'unpaid' orders.
|
|
|
|
if len(unpaid_orders) > 0:
|
|
|
|
next_bill.save()
|
|
|
|
|
|
|
|
# It is not possible to register many-to-many relationship before
|
|
|
|
# the two end-objects are saved in database.
|
|
|
|
for order in unpaid_orders:
|
|
|
|
order.bill.add(next_bill)
|
|
|
|
|
|
|
|
return next_bill
|
|
|
|
|
|
|
|
# Return None if no bill was created.
|
2020-02-28 13:57:45 +00:00
|
|
|
|
|
|
|
class ProductViewSet(mixins.CreateModelMixin,
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
mixins.ListModelMixin,
|
|
|
|
GenericViewSet):
|
|
|
|
"""
|
|
|
|
A customer-facing viewset that provides default `create()`, `retrieve()`
|
|
|
|
and `list()`.
|
|
|
|
"""
|
|
|
|
pass
|