create_subscriptions_invoices.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. from decimal import Decimal
  5. from dateutil.relativedelta import relativedelta
  6. from django.http import HttpResponse
  7. from django.db import transaction
  8. from django.db.models import Q
  9. from django.core.exceptions import ObjectDoesNotExist
  10. from coin.offers.models import Offer, OfferSubscription
  11. from coin.members.models import Member
  12. from coin.billing.models import Invoice, InvoiceDetail
  13. from django.conf import settings
  14. def create_all_members_invoices_for_a_period(date=None):
  15. """
  16. Pour chaque membre ayant au moins un abonnement actif, génère les factures
  17. en prenant la date comme premier mois de la période de facturation
  18. """
  19. if date is None:
  20. date = datetime.date.today()
  21. members = Member.objects.filter(
  22. Q(offersubscription__resign_date__isnull=True) |
  23. Q(offersubscription__resign_date__gte=date))
  24. invoices = []
  25. for member in members:
  26. invoice = create_member_invoice_for_a_period(member, date)
  27. if invoice is not None:
  28. invoices.append(invoice)
  29. return invoices
  30. @transaction.atomic
  31. def create_member_invoice_for_a_period(member, date):
  32. """
  33. Créé si nécessaire une facture pour un membre en prenant la date passée
  34. en paramètre comme premier mois de période. Renvoi la facture générée
  35. ou None si aucune facture n'était necessaire.
  36. """
  37. sid = transaction.savepoint()
  38. date_first_of_month = datetime.date(date.year, date.month, 1)
  39. date_last_of_month = (date_first_of_month + relativedelta(months=+1) -
  40. relativedelta(days=+1))
  41. invoice = Invoice.objects.create(
  42. date_due=datetime.date.today(),
  43. member=member
  44. )
  45. # Récupère les abonnements actifs du membre à la fin du mois
  46. offer_subscriptions = member.get_active_subscriptions(date_last_of_month)
  47. # Pour chaque abonnement
  48. for offer_subscription in offer_subscriptions:
  49. # Récupère l'offre de l'abonnement
  50. offer = offer_subscription.offer
  51. # Si l'offre n'est pas facturable, ne la prend pas en compte
  52. if offer.non_billable:
  53. continue
  54. # Vérifie s'il s'agit de la première facture d'un abonnement,
  55. # Alors facture en plus les frais de mise en service
  56. invoicedetail_test_first = InvoiceDetail.objects.filter(
  57. offersubscription__exact=offer_subscription.pk,
  58. invoice__member__exact=member.pk)
  59. if not invoicedetail_test_first.exists():
  60. invoice.details.create(
  61. label=offer.name + " - Frais de mise en service",
  62. amount=offer.initial_fees,
  63. offersubscription=offer_subscription,
  64. period_from=None,
  65. period_to=None)
  66. # Période de facturation de l'item par defaut
  67. # - Du début du mois de la date passée en paramètre
  68. # - Jusqu'à la fin du mois de la période de facturation de l'offre
  69. period_from = date_first_of_month
  70. period_to = (date_first_of_month +
  71. relativedelta(months=+offer.billing_period) -
  72. relativedelta(days=+1))
  73. planned_period_number_of_days = (period_to - period_from).days + 1
  74. quantity = 1
  75. # Si la facture est le premier mois de l'abonnement, alors met la
  76. # date de début de facturation au jour de l'ouverture de
  77. # l'abonnement
  78. if date_first_of_month == datetime.date(
  79. offer_subscription.subscription_date.year,
  80. offer_subscription.subscription_date.month, 1):
  81. period_from = offer_subscription.subscription_date
  82. # Recherche dans les factures déjà existantes de ce membre des
  83. # items ayant cet abonnement pour lesquels la période de
  84. # facturation englobe le début de notre période de facturation
  85. # actuelle
  86. invoicedetail_test_before = InvoiceDetail.objects.filter(
  87. offersubscription__exact=offer_subscription.pk,
  88. period_from__lte=period_from,
  89. period_to__gt=period_from,
  90. invoice__member__exact=member.pk)
  91. # Si une facture de ce genre existe alors ne fait rien.
  92. if not invoicedetail_test_before.exists():
  93. # Recherche dans les factures déjà existantes de ce membre des
  94. # items ayant cet abonnement pour lesquels la période de
  95. # facturation commence avant la fin de notre période de facturation
  96. # actuelle
  97. invoicedetail_test_after = InvoiceDetail.objects.filter(
  98. offersubscription__exact=offer_subscription.pk,
  99. period_from__lte=period_to,
  100. period_from__gte=period_from,
  101. invoice__member__exact=member.pk)
  102. # Si une telle facture existe, récupère la date de début de
  103. # facturation pour en faire la date de fin de facturation
  104. if invoicedetail_test_after.exists():
  105. invoicedetail_after = invoicedetail_test_after.first()
  106. period_to = (
  107. datetime.date(invoicedetail_after.period_from.year,
  108. invoicedetail_after.period_from.month, 1) -
  109. relativedelta(days=+1))
  110. # Si la période de facturation varie par rapport à celle prévue par
  111. # l'offre, calcul au prorata en faisant varier la quantité
  112. period_number_of_days = (period_to - period_from).days + 1
  113. if planned_period_number_of_days != period_number_of_days:
  114. quantity = (Decimal(period_number_of_days) /
  115. Decimal(planned_period_number_of_days))
  116. # Si durée de 0jours ou dates incohérentes, alors on ajoute pas
  117. # (Si la period est de 0jours c'est que la facture existe déjà.)
  118. if period_from < period_to:
  119. # Ajout l'item de l'offre correspondant à l'abonnement
  120. # à la facture
  121. label = offer.name
  122. try:
  123. if settings.ADD_COMMENTS_IN_BILLING and \
  124. (offer_subscription.configuration.comment):
  125. label += " (%s)" % offer_subscription.configuration.comment
  126. except ObjectDoesNotExist:
  127. pass
  128. invoice.details.create(label=label,
  129. amount=offer.period_fees,
  130. quantity=quantity,
  131. offersubscription=offer_subscription,
  132. period_from=period_from,
  133. period_to=period_to)
  134. # S'il n'y a pas d'items dans la facture, ne commit pas la transaction.
  135. if invoice.details.count() > 0:
  136. invoice.save()
  137. transaction.savepoint_commit(sid)
  138. invoice.validate() # Valide la facture et génère le PDF
  139. return invoice
  140. else:
  141. transaction.savepoint_rollback(sid)
  142. return None