Parcourir la source

Improve subscriptions invoices creation by preventing an offer being charged twice

Fabs il y a 11 ans
Parent
commit
babff5169e

+ 99 - 0
coin/billing/create_subscriptions_invoices.py

@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+import datetime
+from dateutil.relativedelta import relativedelta
+from django.http import HttpResponse
+from django.db.models import Q
+from coin.offers.models import Offer, OfferSubscription
+from coin.members.models import Member
+from coin.billing.models import Invoice, InvoiceDetail
+
+
+def create_missing_invoices(request):
+	"""
+	TODO
+	Pour chaque abonnement (OfferSubscription):
+		Vérifie pour chaque période de facturation si elle a été facturée
+		Sinon génère la facture correspondante
+	"""
+	members = Member.objects.filter(
+		Q(offersubscription__resign_date__isnull=True) |
+		Q(offersubscription__resign_date__gte=datetime.date.today()))
+	offer_subscriptions = OfferSubscription.objects.filter(
+		Q(resign_date__isnull=True) | Q(resign_date__gte=datetime.date.today()))
+	sortie = ""
+	for member in members:
+		sortie += '<br /> %s - %s' % (member, 
+			generate_invoice_for_a_period(member, datetime.date(2014,5,17)))
+
+	return HttpResponse(sortie)
+
+
+def create_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.
+	"""
+	invoice = None
+
+	# Récupère les abonnements en cours du membre
+	offer_subscriptions = (
+		OfferSubscription.get_member_offer_subscriptions(member,date))
+
+	#Pour chaque abonnement
+	for offer_subscription in offer_subscriptions:
+		#Récupère l'offre de l'abonnement
+		offer = offer_subscription.offer
+
+		# Recherche dans les factures déjà existantes de ce membre des items
+		# ayant cette offre pour lesquels la période de facturation englobe
+		# la date
+		invoicedetail_test_before = InvoiceDetail.objects.filter(
+			offer__exact=offer.pk,
+			period_from__lte=date,
+			period_to__gte=date,
+			invoice__member__exact=member.pk)
+
+		# Si une telle facture n'existe pas, alors ajout l'item à facturer à la
+		# facture à générer
+		if not invoicedetail_test_before.exists():
+			#Si l'object facture n'a pas encore été créé, le créé
+			if invoice == None:
+				invoice = Invoice.objects.create(
+					date_due=datetime.date.today(),
+					member=member
+				)
+			# 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=datetime.date(date.year,date.month,1)
+			period_to=(datetime.date(date.year,date.month,1) +
+					   relativedelta(months = +offer.billing_period) -
+					   relativedelta(days = +1))
+
+			# Recherche dans les factures déjà existantes de ce membre des items
+			# ayant cette offre pour lesquels la période de facturation
+			# commence avant la fin de notre période de facturation actuelle
+			invoicedetail_test_after = InvoiceDetail.objects.filter(
+				offer__exact=offer.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))
+
+			#Ajout l'item de l'offre correspondant à l'abonnement à la facture			
+			invoice.details.create(label=offer.name,
+				amount=offer.period_fees,
+				offer=offer,
+				period_from=period_from,
+				period_to=period_to)
+
+			invoice.save();
+
+	return invoice

+ 0 - 75
coin/billing/generate_invoices.py

@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-import datetime
-from dateutil.relativedelta import relativedelta
-from django.http import HttpResponse
-from django.db.models import Q
-from coin.offers.models import Offer, OfferSubscription
-from coin.members.models import Member
-from coin.billing.models import Invoice, InvoiceDetail
-
-
-def generate_missing_invoices(request):
-	"""
-	TODO
-	Pour chaque abonnement (OfferSubscription):
-		Vérifie pour chaque période de facturation si elle a été facturée
-		Sinon génère la facture correspondante
-	"""
-	members = Member.objects.filter(
-		Q(offersubscription__resign_date__isnull=True) | Q(offersubscription__resign_date__gte=datetime.date.today()))
-	offer_subscriptions = OfferSubscription.objects.filter(
-		Q(resign_date__isnull=True) | Q(resign_date__gte=datetime.date.today()))
-	sortie = ""
-	for member in members:
-		sortie += '<br /> %s - %s' % (member, 
-			generate_invoice_for_a_period(member, datetime.date(2014,5,17)))
-
-	return HttpResponse(sortie)
-
-
-def generate_invoice_for_a_period(member, date):
-	"""
-	Génère 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.
-	"""
-	invoice = None
-
-	# Récupère les abonnements en cours du membre
-	offer_subscriptions = (
-		OfferSubscription.get_member_offer_subscriptions(member,date))
-
-	#Pour chaque abonnement
-	for offer_subscription in offer_subscriptions:
-		#Récupère l'offre de l'abonnement
-		offer = offer_subscription.offer
-
-		# Recherche les factures déjà existantes de ce membre ayant cette offre
-		# comme item pour lesquels la période de facturation englobe la date
-		invoice_test = Invoice.objects.filter(
-			details__offer__exact=offer.pk,
-			details__period_from__lte=date,
-			details__period_to__gte=date,
-			member__exact=member.pk)
-
-		#Si une telle facture n'existe pas
-		if not invoice_test.exists():
-			#Si l'object facture n'a pas encore été créé, le créé
-			if invoice == None:
-				invoice = Invoice.objects.create(
-					date_due=datetime.date.today(),
-					member=member
-				)
-
-			#Ajout l'item de l'offre correspondant à l'abonnement à la facture			
-			invoice.details.create(label=offer.name,
-				amount=offer.period_fees,
-				offer=offer,
-				period_from=datetime.date(date.year,date.month,1),
-				period_to=(datetime.date(date.year,date.month,1) +
-							relativedelta(months = +offer.billing_period) -
-							relativedelta(days = +1)))
-
-			invoice.save();
-
-	return invoice

+ 69 - 2
coin/billing/tests.py

@@ -1,12 +1,79 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
+import datetime
 from django.test import TestCase, Client
 from django.test import TestCase, Client
 from coin.members.tests import MemberTestsUtils
 from coin.members.tests import MemberTestsUtils
 from coin.members.models import Member, LdapUser
 from coin.members.models import Member, LdapUser
 from coin.billing.models import Invoice
 from coin.billing.models import Invoice
+from coin.offers.models import Offer, Service, OfferSubscription
+from coin.billing.create_subscriptions_invoices import create_invoice_for_a_period
 
 
+class BillingInvoiceCreationTests(TestCase):
 
 
-# Create your tests here.
-class BillingTests(TestCase):
+	def test_subscription_cant_be_charged_twice(self):
+		"""
+		Test qu'un abonnement ne peut pas être facturé deux fois pendant une
+		période
+		"""
+
+		# Créé une offre
+		service = Service(name='ServiceTest')
+		service.save()
+		offer = Offer(name='Offre', billing_period=3, period_fees=30, 
+					  initial_fees=0, service=service)
+		offer.save()
+		# Créé un membre
+		ldap_cn = MemberTestsUtils.get_random_ldap_cn()
+		member = Member(first_name = 'Balthazar', last_name = 'Picsou',
+		                ldap_cn = ldap_cn)
+		member.save()
+		# Créé un abonnement
+		subscription = OfferSubscription(
+			subscription_date=datetime.date(2014,1,1),
+			member=member,
+			offer=offer)
+		subscription.save()
+		# Créé une facture
+		invoice = Invoice(member=member)
+		invoice.save()
+
+		# Créé une facturation pour cette offre pour la première période
+		# de janvier à mars
+		invoice.details.create(label=offer.name,
+				amount=offer.period_fees,
+				offer=offer,
+				period_from=datetime.date(2014,1,1),
+				period_to=datetime.date(2014,3,31))
+
+		# Créé une facturation pour cette offre pour une seconde période
+		# de juin à aout
+		invoice.details.create(label=offer.name,
+				amount=offer.period_fees,
+				offer=offer,
+				period_from=datetime.date(2014,6,1),
+				period_to=datetime.date(2014,8,31))
+
+		# Demande la génération d'une facture pour février
+		# Elle doit échouer et renvoyer None car l'offre est déjà facturée de
+		# janvier à mars
+		invoice_test_1 = create_invoice_for_a_period(member,
+													 datetime.date(2014,2,1))
+		self.assertEqual(invoice_test_1, None)
+
+		# Demande la création d'une facture pour avril
+		# Elle doit fonctionner, mais devrait avoir une période de facturation
+		# que de 2 mois, d'avril à mai car il y a déjà une facture pour
+		# la période de juin à aout
+		invoice_test_2 = create_invoice_for_a_period(member,
+													 datetime.date(2014,4,1))
+		self.assertEqual(invoice_test_2.details.first().period_from,
+						 datetime.date(2014,4,1))
+		self.assertEqual(invoice_test_2.details.first().period_to,
+						 datetime.date(2014,5,31))
+
+		LdapUser.objects.get(pk=ldap_cn).delete();
+
+
+class BillingPDFTests(TestCase):
 
 
 	def test_download_invoice_pdf_return_a_pdf(self):
 	def test_download_invoice_pdf_return_a_pdf(self):
 		"""
 		"""