# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime from decimal import Decimal from dateutil.relativedelta import relativedelta from django.http import HttpResponse from django.db import transaction from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist from coin.offers.models import Offer, OfferSubscription from coin.members.models import Member from coin.billing.models import Invoice, InvoiceDetail from django.conf import settings def create_all_members_invoices_for_a_period(date=None): """ Pour chaque membre ayant au moins un abonnement actif, génère les factures en prenant la date comme premier mois de la période de facturation """ if date is None: date = datetime.date.today() members = Member.objects.filter( Q(offersubscription__resign_date__isnull=True) | Q(offersubscription__resign_date__gte=date)) invoices = [] for member in members: invoice = create_member_invoice_for_a_period(member, date) if invoice is not None: invoices.append(invoice) return invoices @transaction.atomic def create_member_invoice_for_a_period(member, date): """ Créé si nécessaire une facture pour un membre en prenant la date passée en paramètre comme premier mois de période. Renvoi la facture générée ou None si aucune facture n'était necessaire. """ sid = transaction.savepoint() date_first_of_month = datetime.date(date.year, date.month, 1) date_last_of_month = (date_first_of_month + relativedelta(months=+1) - relativedelta(days=+1)) invoice = Invoice.objects.create( date_due=datetime.date.today(), member=member ) # Récupère les abonnements actifs du membre à la fin du mois offer_subscriptions = member.get_active_subscriptions(date_last_of_month) # Pour chaque abonnement for offer_subscription in offer_subscriptions: # Récupère l'offre de l'abonnement offer = offer_subscription.offer # Si l'offre n'est pas facturable, ne la prend pas en compte if offer.non_billable: continue # Vérifie s'il s'agit de la première facture d'un abonnement, # Alors facture en plus les frais de mise en service invoicedetail_test_first = InvoiceDetail.objects.filter( offersubscription__exact=offer_subscription.pk, invoice__member__exact=member.pk) if not invoicedetail_test_first.exists(): invoice.details.create( label=offer.name + " - Frais de mise en service", amount=offer.initial_fees, offersubscription=offer_subscription, period_from=None, period_to=None) # Période de facturation de l'item par defaut # - Du début du mois de la date passée en paramètre # - Jusqu'à la fin du mois de la période de facturation de l'offre period_from = date_first_of_month period_to = (date_first_of_month + relativedelta(months=+offer.billing_period) - relativedelta(days=+1)) planned_period_number_of_days = (period_to - period_from).days + 1 quantity = 1 # Si la facture est le premier mois de l'abonnement, alors met la # date de début de facturation au jour de l'ouverture de # l'abonnement if date_first_of_month == datetime.date( offer_subscription.subscription_date.year, offer_subscription.subscription_date.month, 1): period_from = offer_subscription.subscription_date # Recherche dans les factures déjà existantes de ce membre des # items ayant cet abonnement pour lesquels la période de # facturation englobe le début de notre période de facturation # actuelle invoicedetail_test_before = InvoiceDetail.objects.filter( offersubscription__exact=offer_subscription.pk, period_from__lte=period_from, period_to__gt=period_from, invoice__member__exact=member.pk) # Si une facture de ce genre existe alors ne fait rien. if not invoicedetail_test_before.exists(): # Recherche dans les factures déjà existantes de ce membre des # items ayant cet abonnement pour lesquels la période de # facturation commence avant la fin de notre période de facturation # actuelle invoicedetail_test_after = InvoiceDetail.objects.filter( offersubscription__exact=offer_subscription.pk, period_from__lte=period_to, period_from__gte=period_from, invoice__member__exact=member.pk) # Si une telle facture existe, récupère la date de début de # facturation pour en faire la date de fin de facturation if invoicedetail_test_after.exists(): invoicedetail_after = invoicedetail_test_after.first() period_to = ( datetime.date(invoicedetail_after.period_from.year, invoicedetail_after.period_from.month, 1) - relativedelta(days=+1)) # Si la période de facturation varie par rapport à celle prévue par # l'offre, calcul au prorata en faisant varier la quantité period_number_of_days = (period_to - period_from).days + 1 if planned_period_number_of_days != period_number_of_days: quantity = (Decimal(period_number_of_days) / Decimal(planned_period_number_of_days)) # Si durée de 0jours ou dates incohérentes, alors on ajoute pas # (Si la period est de 0jours c'est que la facture existe déjà.) if period_from < period_to: # Ajout l'item de l'offre correspondant à l'abonnement # à la facture label = offer.name try: if settings.INVOICES_INCLUDE_CONFIG_COMMENTS and \ (offer_subscription.configuration.comment): label += " (%s)" % offer_subscription.configuration.comment except ObjectDoesNotExist: pass invoice.details.create(label=label, amount=offer.period_fees, quantity=quantity, offersubscription=offer_subscription, period_from=period_from, period_to=period_to) # S'il n'y a pas d'items dans la facture, ne commit pas la transaction. if invoice.details.count() > 0: invoice.save() transaction.savepoint_commit(sid) invoice.validate() # Valide la facture et génère le PDF return invoice else: transaction.savepoint_rollback(sid) return None