diff --git a/stregsystem/admin.py b/stregsystem/admin.py index 3082c2f0..3091204c 100644 --- a/stregsystem/admin.py +++ b/stregsystem/admin.py @@ -24,7 +24,7 @@ class SaleAdmin(admin.ModelAdmin): list_filter = ('room', 'timestamp') - list_display = ('get_username', 'get_fullname', 'get_product_name', 'get_room_name', 'timestamp', 'get_price_display') + list_display = ('get_username', 'get_fullname', 'get_product_name', 'get_room_name', 'timestamp', 'get_price_display', 'is_reimbursed') actions = ['refund'] search_fields = ['^member__username', '=product__id', 'product__name'] valid_lookups = ('member') @@ -224,7 +224,7 @@ def get_queryset(self): class PaymentAdmin(admin.ModelAdmin): - list_display = ('get_username', 'timestamp', 'get_amount_display', 'is_mobilepayment') + list_display = ('get_username', 'timestamp', 'get_amount_display', 'is_mobilepayment', 'is_reimbursement') valid_lookups = ('member') search_fields = ['member__username'] autocomplete_fields = ['member'] diff --git a/stregsystem/migrations/0014_added_reimbursement.py b/stregsystem/migrations/0014_added_reimbursement.py new file mode 100644 index 00000000..87723475 --- /dev/null +++ b/stregsystem/migrations/0014_added_reimbursement.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stregsystem', '0013_mobilepayment_permission_20201123_1344'), + ] + + operations = [ + migrations.AddField( + model_name='payment', + name='is_reimbursement', + field=models.BooleanField(default=False, help_text='Viser om en betaling er tilbageført'), + ), + migrations.AddField( + model_name='sale', + name='is_reimbursed', + field=models.BooleanField(default=False, help_text='Viser om et salg er tilbageført'), + ), + ] diff --git a/stregsystem/models.py b/stregsystem/models.py index 8ead975b..52cbeea3 100644 --- a/stregsystem/models.py +++ b/stregsystem/models.py @@ -142,6 +142,18 @@ def execute(self): # We changed the user balance, so save that self.member.save() +class Reimbursement(object): + #Limits the amount of hours a reimbursement is available. + MAX_REIMBUSEMENT_HOURS = 12 + + @transaction.atomic + def from_sale(sale_id): + sale = Sale.objects.get(id=sale_id) + if not sale.is_reimbursable(): + return + sale.is_reimbursed = True + sale.save() + Payment.objects.create(amount=sale.price, member=sale.member, is_reimbursement=True) class GetTransaction(MoneyTransaction): # The change to the users account @@ -295,6 +307,8 @@ class Meta: member = models.ForeignKey(Member, on_delete=models.CASCADE) timestamp = models.DateTimeField(auto_now_add=True) amount = models.IntegerField() # penge, oere... + is_reimbursement = models.BooleanField(default=False, null=False, + help_text='Viser om en betaling er tilbageført') @deprecated def amount_display(self): @@ -513,6 +527,8 @@ class Sale(models.Model): room = models.ForeignKey(Room, on_delete=models.CASCADE, null=True) timestamp = models.DateTimeField(auto_now_add=True) price = models.IntegerField() + is_reimbursed = models.BooleanField(default=False, null=False, + help_text='Viser om et salg er tilbageført',) class Meta: index_together = [ @@ -530,6 +546,10 @@ def price_display(self): # XXX - django bug - kan ikke vaelge mellem desc og asc i admin, som ved normalt felt price_display.admin_order_field = 'price' + def is_reimbursable(self): + from datetime import timedelta + return (not self.is_reimbursed) and timedelta(hours=Reimbursement.MAX_REIMBUSEMENT_HOURS) >= timezone.now() - self.timestamp + @deprecated def __unicode__(self): return self.__str__() @@ -537,10 +557,10 @@ def __unicode__(self): def __str__(self): return self.member.username + " " + self.product.name + " (" + money(self.price) + ") " + str(self.timestamp) - def save(self, *args, **kwargs): - if self.id: - raise RuntimeError("Updates of sales are not allowed") - super(Sale, self).save(*args, **kwargs) + #def save(self, *args, **kwargs): + # if self.id: + # raise RuntimeError("Updates of sales are not allowed") + # super(Sale, self).save(*args, **kwargs) def delete(self, *args, **kwargs): if self.id: diff --git a/stregsystem/templates/stregsystem/menu_userinfo.html b/stregsystem/templates/stregsystem/menu_userinfo.html index 63389b61..d39acd4a 100644 --- a/stregsystem/templates/stregsystem/menu_userinfo.html +++ b/stregsystem/templates/stregsystem/menu_userinfo.html @@ -39,6 +39,7 @@ Dato og tidspunkt Produkt Pris + Tilbagefør {% autoescape off %} {% for sale in last_sale_list %} @@ -46,6 +47,15 @@ {{sale.timestamp}} {{sale.product.name}} {{sale.price|money}} + + {% if sale.is_reimbursable %} +
+ {% csrf_token %} + + +
+ {% endif %} + {% endfor %} {% endautoescape %} diff --git a/stregsystem/tests.py b/stregsystem/tests.py index eebfda7a..18e3ce59 100644 --- a/stregsystem/tests.py +++ b/stregsystem/tests.py @@ -28,6 +28,7 @@ Payment, PayTransaction, Product, + Reimbursement, Room, Sale, StregForbudError, @@ -770,9 +771,7 @@ def test_sale_save_already_saved(self): price=100 ) sale.save() - - with self.assertRaises(RuntimeError): - sale.save() + sale.save() def test_sale_delete_not_saved(self): sale = Sale( @@ -796,6 +795,73 @@ def test_sale_delete_already_saved(self): self.assertIsNone(sale.id) +class ReimbursementTests(TestCase): + def setUp(self): + self.member = Member.objects.create( + username="jon", + balance=500 + ) + self.product = Product.objects.create( + name="beer", + price=1.0, + active=True, + ) + with freeze_time(timezone.now() - datetime.timedelta(hours=11)): + self.almost_oldsale = Sale.objects.create( + member=self.member, + product=self.product, + price=200, + ) + with freeze_time(timezone.now() - datetime.timedelta(hours=12, minutes=1)): + self.oldsale = Sale.objects.create( + member=self.member, + product=self.product, + price=300, + ) + self.newsale = Sale.objects.create( + member=self.member, + product=self.product, + price=100, + ) + + def test_reimbursement_success(self): + self.assertFalse(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertFalse(Sale.objects.filter(is_reimbursed=True, id=self.newsale.id).exists()) + Reimbursement.from_sale(self.newsale.id) + self.assertTrue(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertTrue(Sale.objects.filter(is_reimbursed=True, id=self.newsale.id).exists()) + + def test_reimbursement_later_success(self): + self.assertFalse(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertFalse(Sale.objects.filter(is_reimbursed=True, id=self.almost_oldsale.id).exists()) + Reimbursement.from_sale(self.almost_oldsale.id) + self.assertTrue(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertTrue(Sale.objects.filter(is_reimbursed=True, id=self.almost_oldsale.id).exists()) + + def test_reimbursement_fail(self): + self.assertFalse(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertFalse(Sale.objects.filter(is_reimbursed=True, id=self.oldsale.id).exists()) + Reimbursement.from_sale(self.oldsale.id) + self.assertFalse(Payment.objects.filter(is_reimbursement=True).exists()) + self.assertFalse(Sale.objects.filter(is_reimbursed=True, id=self.oldsale.id).exists()) + + def test_reimbursement_balance_updated(self): + self.assertEqual(self.member.balance, 500) + Reimbursement.from_sale(self.newsale.id) + self.member.refresh_from_db() + self.assertEqual(self.member.balance, 600) + + def test_reimbursement_balance_updated_another(self): + self.assertEqual(self.member.balance, 500) + Reimbursement.from_sale(self.almost_oldsale.id) + self.member.refresh_from_db() + self.assertEqual(self.member.balance, 700) + + def test_reimbursement_balance_not_updated(self): + self.assertEqual(self.member.balance, 500) + Reimbursement.from_sale(self.oldsale.id) + self.member.refresh_from_db() + self.assertEqual(self.member.balance, 500) class MemberTests(TestCase): def test_fulfill_pay_transaction(self): diff --git a/stregsystem/views.py b/stregsystem/views.py index 1787137f..f66f08a4 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -25,7 +25,8 @@ Room, Sale, StregForbudError, - MobilePayment + MobilePayment, + Reimbursement, ) from stregsystem.utils import ( make_active_productlist_query, @@ -185,11 +186,14 @@ def usermenu(request, room, member, bought, from_sale=False): def menu_userinfo(request, room_id, member_id): room = Room.objects.get(pk=room_id) news = __get_news() + + if request.method == 'POST' and request.POST.get('action') is not None and request.POST['action'] == 'reimburse': + Reimbursement.from_sale(int(request.POST['sale_id'])) member = Member.objects.get(pk=member_id, active=True) - last_sale_list = member.sale_set.order_by('-timestamp')[:10] + last_sale_list = member.sale_set.filter(is_reimbursed=False).order_by('-timestamp')[:10] try: - last_payment = member.payment_set.order_by('-timestamp')[0] + last_payment = member.payment_set.filter(is_reimbursement=False).order_by('-timestamp')[0] except IndexError: last_payment = None