Browse Source

Improve subscriptions invoices creation by preventing an offer being charged twice

Fabs 11 years ago
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 -*-
+import datetime
 from django.test import TestCase, Client
 from coin.members.tests import MemberTestsUtils
 from coin.members.models import Member, LdapUser
 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):
 		"""