# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime from decimal import Decimal from django.conf import settings from django.test import TestCase, Client from freezegun import freeze_time from coin.members.tests import MemberTestsUtils from coin.members.models import Member, LdapUser from coin.billing.models import Invoice, InvoiceQuerySet from coin.offers.models import Offer, OfferSubscription from coin.billing.create_subscriptions_invoices import create_member_invoice_for_a_period from coin.billing.create_subscriptions_invoices import create_all_members_invoices_for_a_period class BillingInvoiceCreationTests(TestCase): def setUp(self): # Créé une offre self.offer = Offer(name='Offre', billing_period=3, period_fees=30, initial_fees=50) self.offer.save() # Créé un membre self.username = MemberTestsUtils.get_random_username() self.member = Member(first_name='Balthazar', last_name='Picsou', username=self.username) self.member.save() # Créé un abonnement self.subscription = OfferSubscription( subscription_date=datetime.date(2014, 1, 10), member=self.member, offer=self.offer) self.subscription.save() def tearDown(self): # Supprime l'utilisateur LDAP créé if settings.LDAP_ACTIVATE: LdapUser.objects.get(pk=self.username).delete() def test_first_subscription_invoice_has_initial_fees(self): """ Test que la première facture générée pour un abonnement possède les frais de mise en service """ # Demande la création de la première facture invoice = create_member_invoice_for_a_period( self.member, datetime.date(2014, 1, 1)) # La facture doit avoir les frais de mise en service # Pour tester cela on tri par montant d'item décroissant. # Comme dans l'offre créé, les initial_fees sont plus élevées que # les period_fees, il doit sortir en premier self.assertEqual(invoice.details.order_by('-amount').first().amount, 50) def test_prorata_for_first_month_subscription(self): """ Test que la première facture d'un abonnement est facturée au prorata du nombre de jours restants """ # Créé la facture pour le mois de janvier invoice = create_member_invoice_for_a_period( self.member, datetime.date(2014, 1, 1)) # Comme l'abonnement a été souscris le 10/01 et que la période de # facturation est de 3 mois, alors le prorata doit être : # janvier : 22j (31-9) # fevrier : 28j # mars : 31j #22+28+31 / 31+28+31 quantity = Decimal((22.0 + 28.0 + 31.0) / (31.0 + 28.0 + 31.0)) for detail in invoice.details.all(): if detail.amount != 50: self.assertEqual(detail.quantity.quantize(Decimal('0.01')), quantity.quantize(Decimal('0.01'))) def test_subscription_cant_be_charged_twice(self): """ Test qu'un abonnement ne peut pas être facturé deux fois (pas de chevauchement possible) """ # Créé une facture invoice = Invoice(member=self.member) invoice.save() # Créé une facturation pour cet abonnement pour la première période # de janvier à mars invoice.details.create(label=self.offer.name, amount=self.offer.period_fees, offersubscription=self.subscription, period_from=datetime.date(2014, 1, 1), period_to=datetime.date(2014, 3, 31)) # Créé une facturation pour cet abonnement pour une seconde période # de juin à aout invoice.details.create(label=self.offer.name, amount=self.offer.period_fees, offersubscription=self.subscription, 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 renvoyer None car l'offre est déjà facturée de # janvier à mars invoice_test_1 = create_member_invoice_for_a_period( self.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_member_invoice_for_a_period( self.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)) def test_invoice_amount(self): invoice = Invoice(member=self.member) invoice.save() invoice.details.create(label=self.offer.name, amount=100, offersubscription=self.subscription, period_from=datetime.date(2014, 1, 1), period_to=datetime.date(2014, 3, 31), tax=0) invoice.details.create(label=self.offer.name, amount=10, offersubscription=self.subscription, period_from=datetime.date(2014, 6, 1), period_to=datetime.date(2014, 8, 31), tax=10) self.assertEqual(invoice.amount(), 111) def test_invoice_partial_payment(self): invoice = Invoice(member=self.member) invoice.save() invoice.details.create(label=self.offer.name, amount=100, offersubscription=self.subscription, period_from=datetime.date(2014, 1, 1), period_to=datetime.date(2014, 3, 31), tax=0) self.assertEqual(invoice.status, 'open') invoice.payments.create(payment_mean='cash', amount=10) self.assertEqual(invoice.status, 'open') invoice.payments.create(payment_mean='cash', amount=90) self.assertEqual(invoice.status, 'closed') def test_invoice_amount_before_tax(self): invoice = Invoice(member=self.member) invoice.save() invoice.details.create(label=self.offer.name, amount=100, offersubscription=self.subscription, period_from=datetime.date(2014, 1, 1), period_to=datetime.date(2014, 3, 31), tax=0) invoice.details.create(label=self.offer.name, amount=10, offersubscription=self.subscription, period_from=datetime.date(2014, 6, 1), period_to=datetime.date(2014, 8, 31), tax=10) self.assertEqual(invoice.amount_before_tax(), 110) def test_non_billable_offer_isnt_charged(self): """ Test qu'une offre non facturable n'est pas prise en compte """ # Créé une offre non facturable offer = Offer(name='Offre', billing_period=3, period_fees=30, initial_fees=50, non_billable=True) offer.save() # Créé un abonnement self.subscription = OfferSubscription( subscription_date=datetime.date(2014, 1, 10), member=self.member, offer=offer) self.subscription.save() # Demande la création de la première facture invoice = create_member_invoice_for_a_period( self.member, datetime.date(2014, 1, 1)) # Vérifie qu'il n'y a pas l'offre dans la facture, si c'est le cas génère une exception if invoice: for detail in invoice.details.all(): if detail.offersubscription.offer == offer: raise Exception class BillingTests(TestCase): def test_download_invoice_pdf_return_a_pdf(self): """ Test que le téléchargement d'une facture en format pdf retourne bien un pdf """ # Créé un membre username = MemberTestsUtils.get_random_username() member = Member(first_name='A', last_name='A', username=username) member.set_password('1234') member.save() # Créé une facture invoice = Invoice(member=member) invoice.save() invoice.validate() # Se connect en tant que le membre client = Client() client.login(username=username, password='1234') # Tente de télécharger la facture response = client.get('/billing/invoice/%i/pdf' % invoice.id) # Vérifie return code 200 et contient chaine %PDF-1. self.assertContains(response, b'%PDF-1.', status_code=200, html=False) member.delete() def test_that_only_owner_of_invoice_can_access_it(self): """ Test qu'une facture ne peut pas être téléchargée par quelqu'un qui n'en est pas le propriétaire. Test qu'une erreur 403 est bien retournée en cas de tentative infructueuse """ # Créé un membre A member_a_login = MemberTestsUtils.get_random_username() member_a_pwd = '1234' member_a = Member(first_name='A', last_name='A', email='a@a.com', username=member_a_login) member_a.set_password(member_a_pwd) member_a.save() # Créé un membre B member_b_login = MemberTestsUtils.get_random_username() member_b_pwd = '1234' member_b = Member(first_name='B', last_name='B', email='b@b.com', username=member_b_login) member_b.set_password(member_b_pwd) member_b.save() # Créé une facture pour le membre A invoice_a = Invoice(member=member_a) invoice_a.save() invoice_a.validate() # Simule une connexion en tant que A client = Client() client.login(username=member_a_login, password=member_a_pwd) # Tente d'accéder à la facture en tant que A response = client.get(invoice_a.get_absolute_url()) # Vérifie que A a reçu retour OK 200 self.assertEqual(response.status_code, 200) # Tente de télécharger la facture pdf de A en tant que A response = client.get('/billing/invoice/%i/pdf' % invoice_a.id) # Vérifie que A a reçu retour OK 200 self.assertEqual(response.status_code, 200) # Simule une connexion en tant que B client = Client() client.login(username=member_b_login, password=member_b_pwd) # Tente d'accéder à la facture de A en tant que B response = client.get(invoice_a.get_absolute_url()) # Vérifie que B a reçu retour Forbissen 403 self.assertEqual(response.status_code, 403) # Tente de télécharger la facture pdf de A en tant que B response = client.get('/billing/invoice/%i/pdf' % invoice_a.id) # Vérifie que B a reçu retour Forbidden 403 self.assertEqual(response.status_code, 403) member_a.delete() member_b.delete() class InvoiceQuerySetTests(TestCase): def test_get_first_invoice_number_ever(self): self.assertEqual( Invoice.objects.get_next_invoice_number(datetime.date(2016,1,1)), '2016-01-000001') @freeze_time('2016-01-01') def test_get_first_of_month_invoice_number(self): # One bill on a month… Invoice.objects.create().validate() # … Does not affect the numbering of following month. self.assertEqual( Invoice.objects.get_next_invoice_number(datetime.date(2016,2,15)), '2016-02-000001') @freeze_time('2016-01-01') def test_number_workflow(self): iv = Invoice.objects.create() self.assertEqual(iv.number, 'DRAFT-1') iv.validate() self.assertRegexpMatches(iv.number, r'2016-01-000001$') @freeze_time('2016-01-01') def test_get_second_of_month_invoice_number(self): first_bill = Invoice.objects.create(date=datetime.date(2016,1,1)) first_bill.validate() self.assertEqual( Invoice.objects.get_next_invoice_number(datetime.date(2016,1,1)), '2016-01-000002') def test_bill_date_is_validation_date(self): bill = Invoice.objects.create(date=datetime.date(2016,1,1)) self.assertEqual(bill.date, datetime.date(2016,1,1)) with freeze_time('2017-01-01'): bill.validate() self.assertEqual(bill.date, datetime.date(2017, 1, 1)) self.assertEqual(bill.number, '2017-01-000001')