123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- # -*- 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
- 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 necessaire 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 (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
|