tests.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. from decimal import Decimal
  5. from django.conf import settings
  6. from django.test import TestCase, Client, override_settings
  7. from freezegun import freeze_time
  8. from coin.members.tests import MemberTestsUtils
  9. from coin.members.models import Member, LdapUser
  10. from coin.billing.models import Invoice, InvoiceQuerySet, InvoiceDetail, Payment
  11. from coin.offers.models import Offer, OfferSubscription
  12. from coin.billing.create_subscriptions_invoices import create_member_invoice_for_a_period
  13. from coin.billing.create_subscriptions_invoices import create_all_members_invoices_for_a_period
  14. @override_settings(HANDLE_BALANCE=True)
  15. class BillingInvoiceCreationTests(TestCase):
  16. def setUp(self):
  17. # Créé une offre
  18. self.offer = Offer(name='Offre', billing_period=3, period_fees=30,
  19. initial_fees=50)
  20. self.offer.save()
  21. # Créé un membre
  22. self.username = MemberTestsUtils.get_random_username()
  23. self.member = Member(first_name='Balthazar', last_name='Picsou',
  24. username=self.username)
  25. self.member.save()
  26. # Créé un abonnement
  27. self.subscription = OfferSubscription(
  28. subscription_date=datetime.date(2014, 1, 10),
  29. member=self.member,
  30. offer=self.offer)
  31. self.subscription.save()
  32. def tearDown(self):
  33. # Supprime l'utilisateur LDAP créé
  34. if settings.LDAP_ACTIVATE:
  35. LdapUser.objects.get(pk=self.username).delete()
  36. def test_first_subscription_invoice_has_initial_fees(self):
  37. """
  38. Test que la première facture générée pour un abonnement possède les
  39. frais de mise en service
  40. """
  41. # Demande la création de la première facture
  42. invoice = create_member_invoice_for_a_period(
  43. self.member, datetime.date(2014, 1, 1))
  44. # La facture doit avoir les frais de mise en service
  45. # Pour tester cela on tri par montant d'item décroissant.
  46. # Comme dans l'offre créé, les initial_fees sont plus élevées que
  47. # les period_fees, il doit sortir en premier
  48. self.assertEqual(invoice.details.order_by('-amount').first().amount, 50)
  49. def test_prorata_for_first_month_subscription(self):
  50. """
  51. Test que la première facture d'un abonnement est facturée au prorata du
  52. nombre de jours restants
  53. """
  54. # Créé la facture pour le mois de janvier
  55. invoice = create_member_invoice_for_a_period(
  56. self.member, datetime.date(2014, 1, 1))
  57. # Comme l'abonnement a été souscris le 10/01 et que la période de
  58. # facturation est de 3 mois, alors le prorata doit être :
  59. # janvier : 22j (31-9)
  60. # fevrier : 28j
  61. # mars : 31j
  62. #22+28+31 / 31+28+31
  63. quantity = Decimal((22.0 + 28.0 + 31.0) / (31.0 + 28.0 + 31.0))
  64. for detail in invoice.details.all():
  65. if detail.amount != 50:
  66. self.assertEqual(detail.quantity.quantize(Decimal('0.01')),
  67. quantity.quantize(Decimal('0.01')))
  68. def test_subscription_cant_be_charged_twice(self):
  69. """
  70. Test qu'un abonnement ne peut pas être facturé deux fois
  71. (pas de chevauchement possible)
  72. """
  73. # Créé une facture
  74. invoice = Invoice(member=self.member)
  75. invoice.save()
  76. # Créé une facturation pour cet abonnement pour la première période
  77. # de janvier à mars
  78. invoice.details.create(label=self.offer.name,
  79. amount=self.offer.period_fees,
  80. offersubscription=self.subscription,
  81. period_from=datetime.date(2014, 1, 1),
  82. period_to=datetime.date(2014, 3, 31))
  83. # Créé une facturation pour cet abonnement pour une seconde période
  84. # de juin à aout
  85. invoice.details.create(label=self.offer.name,
  86. amount=self.offer.period_fees,
  87. offersubscription=self.subscription,
  88. period_from=datetime.date(2014, 6, 1),
  89. period_to=datetime.date(2014, 8, 31))
  90. # Demande la génération d'une facture pour février
  91. # Elle doit renvoyer None car l'offre est déjà facturée de
  92. # janvier à mars
  93. invoice_test_1 = create_member_invoice_for_a_period(
  94. self.member, datetime.date(2014, 2, 1))
  95. self.assertEqual(invoice_test_1, None)
  96. # Demande la création d'une facture pour avril
  97. # Elle doit fonctionner, mais devrait avoir une période de facturation
  98. # que de 2 mois, d'avril à mai car il y a déjà une facture pour
  99. # la période de juin à aout
  100. invoice_test_2 = create_member_invoice_for_a_period(
  101. self.member, datetime.date(2014, 4, 1))
  102. self.assertEqual(invoice_test_2.details.first().period_from,
  103. datetime.date(2014, 4, 1))
  104. self.assertEqual(invoice_test_2.details.first().period_to,
  105. datetime.date(2014, 5, 31))
  106. def test_invoice_amount(self):
  107. invoice = Invoice(member=self.member)
  108. invoice.save()
  109. invoice.details.create(label=self.offer.name,
  110. amount=100,
  111. offersubscription=self.subscription,
  112. period_from=datetime.date(2014, 1, 1),
  113. period_to=datetime.date(2014, 3, 31),
  114. tax=0)
  115. invoice.details.create(label=self.offer.name,
  116. amount=10,
  117. offersubscription=self.subscription,
  118. period_from=datetime.date(2014, 6, 1),
  119. period_to=datetime.date(2014, 8, 31),
  120. tax=10)
  121. self.assertEqual(invoice.amount(), 111)
  122. def test_invoice_partial_payment(self):
  123. invoice = Invoice(member=self.member)
  124. invoice.save()
  125. invoice.details.create(label=self.offer.name,
  126. amount=100,
  127. offersubscription=self.subscription,
  128. period_from=datetime.date(2014, 1, 1),
  129. period_to=datetime.date(2014, 3, 31),
  130. tax=0)
  131. invoice.validate()
  132. invoice.save()
  133. self.assertEqual(invoice.status, 'open')
  134. p1 = Payment.objects.create(member=self.member,
  135. invoice=invoice,
  136. payment_mean='cash',
  137. amount=10)
  138. p1.save()
  139. invoice = Invoice.objects.get(pk=invoice.pk)
  140. self.assertEqual(invoice.status, 'open')
  141. p2 = Payment.objects.create(member=self.member,
  142. invoice=invoice,
  143. payment_mean='cash',
  144. amount=90)
  145. p2.save()
  146. invoice = Invoice.objects.get(pk=invoice.pk)
  147. self.assertEqual(invoice.status, 'closed')
  148. def test_invoice_amount_before_tax(self):
  149. invoice = Invoice(member=self.member)
  150. invoice.save()
  151. invoice.details.create(label=self.offer.name,
  152. amount=100,
  153. offersubscription=self.subscription,
  154. period_from=datetime.date(2014, 1, 1),
  155. period_to=datetime.date(2014, 3, 31),
  156. tax=0)
  157. invoice.details.create(label=self.offer.name,
  158. amount=10,
  159. offersubscription=self.subscription,
  160. period_from=datetime.date(2014, 6, 1),
  161. period_to=datetime.date(2014, 8, 31),
  162. tax=10)
  163. self.assertEqual(invoice.amount_before_tax(), 110)
  164. def test_non_billable_offer_isnt_charged(self):
  165. """
  166. Test qu'une offre non facturable n'est pas prise en compte
  167. """
  168. # Créé une offre non facturable
  169. offer = Offer(name='Offre', billing_period=3, period_fees=30,
  170. initial_fees=50, non_billable=True)
  171. offer.save()
  172. # Créé un abonnement
  173. self.subscription = OfferSubscription(
  174. subscription_date=datetime.date(2014, 1, 10),
  175. member=self.member,
  176. offer=offer)
  177. self.subscription.save()
  178. # Demande la création de la première facture
  179. invoice = create_member_invoice_for_a_period(
  180. self.member, datetime.date(2014, 1, 1))
  181. # Vérifie qu'il n'y a pas l'offre dans la facture, si c'est le cas génère une exception
  182. if invoice:
  183. for detail in invoice.details.all():
  184. if detail.offersubscription.offer == offer:
  185. raise Exception
  186. class BillingTests(TestCase):
  187. def test_download_invoice_pdf_return_a_pdf(self):
  188. """
  189. Test que le téléchargement d'une facture en format pdf retourne bien un
  190. pdf
  191. """
  192. # Créé un membre
  193. username = MemberTestsUtils.get_random_username()
  194. member = Member(first_name='A', last_name='A',
  195. username=username)
  196. member.set_password('1234')
  197. member.save()
  198. # Créé une facture
  199. invoice = Invoice(member=member)
  200. invoice.save()
  201. invoice.validate()
  202. # Se connect en tant que le membre
  203. client = Client()
  204. client.login(username=username, password='1234')
  205. # Tente de télécharger la facture
  206. response = client.get('/billing/invoice/%i/pdf' % invoice.id)
  207. # Vérifie return code 200 et contient chaine %PDF-1.
  208. self.assertContains(response, b'%PDF-1.', status_code=200, html=False)
  209. member.delete()
  210. def test_that_only_owner_of_invoice_can_access_it(self):
  211. """
  212. Test qu'une facture ne peut pas être téléchargée par quelqu'un qui n'en
  213. est pas le propriétaire.
  214. Test qu'une erreur 403 est bien retournée en cas de tentative
  215. infructueuse
  216. """
  217. # Créé un membre A
  218. member_a_login = MemberTestsUtils.get_random_username()
  219. member_a_pwd = '1234'
  220. member_a = Member(first_name='A', last_name='A', email='a@a.com',
  221. username=member_a_login)
  222. member_a.set_password(member_a_pwd)
  223. member_a.save()
  224. # Créé un membre B
  225. member_b_login = MemberTestsUtils.get_random_username()
  226. member_b_pwd = '1234'
  227. member_b = Member(first_name='B', last_name='B', email='b@b.com',
  228. username=member_b_login)
  229. member_b.set_password(member_b_pwd)
  230. member_b.save()
  231. # Créé une facture pour le membre A
  232. invoice_a = Invoice(member=member_a)
  233. invoice_a.save()
  234. invoice_a.validate()
  235. # Simule une connexion en tant que A
  236. client = Client()
  237. client.login(username=member_a_login, password=member_a_pwd)
  238. # Tente d'accéder à la facture en tant que A
  239. response = client.get(invoice_a.get_absolute_url())
  240. # Vérifie que A a reçu retour OK 200
  241. self.assertEqual(response.status_code, 200)
  242. # Tente de télécharger la facture pdf de A en tant que A
  243. response = client.get('/billing/invoice/%i/pdf' % invoice_a.id)
  244. # Vérifie que A a reçu retour OK 200
  245. self.assertEqual(response.status_code, 200)
  246. # Simule une connexion en tant que B
  247. client = Client()
  248. client.login(username=member_b_login, password=member_b_pwd)
  249. # Tente d'accéder à la facture de A en tant que B
  250. response = client.get(invoice_a.get_absolute_url())
  251. # Vérifie que B a reçu retour Forbissen 403
  252. self.assertEqual(response.status_code, 403)
  253. # Tente de télécharger la facture pdf de A en tant que B
  254. response = client.get('/billing/invoice/%i/pdf' % invoice_a.id)
  255. # Vérifie que B a reçu retour Forbidden 403
  256. self.assertEqual(response.status_code, 403)
  257. member_a.delete()
  258. member_b.delete()
  259. class InvoiceQuerySetTests(TestCase):
  260. def test_get_first_invoice_number_ever(self):
  261. self.assertEqual(
  262. Invoice.objects.get_next_invoice_number(datetime.date(2016,1,1)),
  263. '2016-01-000001')
  264. @freeze_time('2016-01-01')
  265. def test_get_first_of_month_invoice_number(self):
  266. # One bill on a month…
  267. Invoice.objects.create().validate()
  268. # … Does not affect the numbering of following month.
  269. self.assertEqual(
  270. Invoice.objects.get_next_invoice_number(datetime.date(2016,2,15)),
  271. '2016-02-000001')
  272. @freeze_time('2016-01-01')
  273. def test_number_workflow(self):
  274. iv = Invoice.objects.create()
  275. self.assertEqual(iv.number, 'DRAFT-{}'.format(iv.pk))
  276. iv.validate()
  277. self.assertRegexpMatches(iv.number, r'2016-01-000001$')
  278. @freeze_time('2016-01-01')
  279. def test_get_second_of_month_invoice_number(self):
  280. first_bill = Invoice.objects.create(date=datetime.date(2016,1,1))
  281. first_bill.validate()
  282. self.assertEqual(
  283. Invoice.objects.get_next_invoice_number(datetime.date(2016,1,1)),
  284. '2016-01-000002')
  285. def test_get_right_year_invoice_number(self):
  286. with freeze_time('2016-01-01'):
  287. Invoice.objects.create(date=datetime.date(2016, 1, 1)).validate()
  288. with freeze_time('2017-01-01'):
  289. Invoice.objects.create(date=datetime.date(2017, 1, 1)).validate()
  290. with freeze_time('2018-01-01'):
  291. Invoice.objects.create(date=datetime.date(2018, 1, 1)).validate()
  292. self.assertEqual(
  293. Invoice.objects.get_next_invoice_number(datetime.date(2017, 1, 1)),
  294. '2017-01-000002')
  295. def test_bill_date_is_validation_date(self):
  296. bill = Invoice.objects.create(date=datetime.date(2016,1,1))
  297. self.assertEqual(bill.date, datetime.date(2016,1,1))
  298. with freeze_time('2017-01-01'):
  299. bill.validate()
  300. self.assertEqual(bill.date, datetime.date(2017, 1, 1))
  301. self.assertEqual(bill.number, '2017-01-000001')
  302. class PaymentInvoiceAutoReconciliationTests(TestCase):
  303. def test_accounting_update(self):
  304. johndoe = Member.objects.create(username=MemberTestsUtils.get_random_username(),
  305. first_name="John",
  306. last_name="Doe",
  307. email="johndoe@yolo.test")
  308. johndoe.set_password("trololo")
  309. # First facture
  310. invoice = Invoice.objects.create(number="1337",
  311. member=johndoe)
  312. InvoiceDetail.objects.create(label="superservice",
  313. amount="15.0",
  314. invoice=invoice)
  315. invoice.validate()
  316. # Second facture
  317. invoice2 = Invoice.objects.create(number="42",
  318. member=johndoe)
  319. InvoiceDetail.objects.create(label="superservice",
  320. amount="42",
  321. invoice=invoice2)
  322. invoice2.validate()
  323. # Payment
  324. payment = Payment.objects.create(amount=20,
  325. member=johndoe)
  326. invoice.delete()
  327. invoice2.delete()
  328. payment.delete()
  329. johndoe.delete()