Skip to content

Commit

Permalink
Add support for removing box item from receipt
Browse files Browse the repository at this point in the history
It was already possible before box item codes were removed, so add that
support back by accepting box-codes in the form.
  • Loading branch information
jlaunonen committed Jul 20, 2024
1 parent cf8ef37 commit 2524a1a
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 9 deletions.
39 changes: 30 additions & 9 deletions kirppu/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
Expand All @@ -8,6 +10,7 @@
from .fields import ItemPriceField, SuffixField, StripField
from .models import (
AccessSignup,
Box,
Clerk,
Event,
ReceiptItem,
Expand Down Expand Up @@ -312,6 +315,10 @@ def clean_receipt(self):

def clean_code(self):
data = self.cleaned_data["code"]
if box_match := re.match(r"box[_ -]?(\d+)$", data):
if not Box.objects.filter(pk=int(box_match[1])).exists():
raise forms.ValidationError("Box {} not found".format(box_match[1]))
return data
if not Item.is_item_barcode(data):
raise forms.ValidationError("Value is not an item barcode")
if not Item.objects.filter(code=data, vendor__event=self._event).exists():
Expand All @@ -320,22 +327,36 @@ def clean_code(self):


@transaction.atomic
def remove_item_from_receipt(request, item_or_code, receipt_id, update_receipt=True):
if isinstance(item_or_code, Item):
item = item_or_code
else:
item = Item.objects.select_for_update().get(code=item_or_code)

if item.state not in (Item.SOLD, Item.STAGED):
raise ValueError("Item is not sold or staged, but {}".format(item.state))

def remove_item_from_receipt(
request, item_or_code: str | Item, receipt_id: int | Receipt, update_receipt=True
):
if isinstance(receipt_id, Receipt):
receipt = receipt_id
assert receipt.type == Receipt.TYPE_PURCHASE, "This function cannot be used for non-purchase receipts."
else:
receipt = Receipt.objects.select_for_update().get(pk=receipt_id, type=Receipt.TYPE_PURCHASE)
assert update_receipt, "Receipt must be updated if accessed by id."

if isinstance(item_or_code, Item):
item = item_or_code
else:
if box_match := re.match(r"box[_ -]?(\d+)$", item_or_code):
box_number = int(box_match[1])
receipt_box_item = ReceiptItem.objects.filter(
receipt=receipt,
action=ReceiptItem.ADD,
item__box__box_number=box_number,
).order_by("-add_time")[0:1]
if not receipt_box_item:
raise ValueError("Box item not found on receipt")
item = receipt_box_item[0].item
item = Item.objects.select_for_update().get(pk=item.pk)
else:
item = Item.objects.select_for_update().get(code=item_or_code)

if item.state not in (Item.SOLD, Item.STAGED):
raise ValueError("Item is not sold or staged, but {}".format(item.state))

last_added_item = ReceiptItem.objects \
.filter(receipt=receipt, item=item, action=ReceiptItem.ADD) \
.select_for_update() \
Expand Down
65 changes: 65 additions & 0 deletions kirppu/tests/test_receipt_removals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from django.test import TestCase

from . import ResultMixin

from ..models import ReceiptItem, Item, Receipt
from .factories import BoxFactory, ReceiptItemFactory, ItemFactory, UserFactory


class ReceiptRemovalTests(TestCase, ResultMixin):
def setUp(self):
self.receipt_item: ReceiptItem = ReceiptItemFactory()
self.item = self.receipt_item.item
self.item.state = Item.SOLD
self.item.save(update_fields=["state"])

vendor = self.item.vendor
self.event = vendor.event
self.receipt = self.receipt_item.receipt

self.other_item = ItemFactory(vendor=vendor, state=Item.SOLD)
self.other_receipt_item = ReceiptItemFactory(
item=self.other_item, receipt=self.receipt
)

self.receipt.calculate_total()
self.receipt.status = Receipt.FINISHED
self.receipt.save(update_fields=["status", "total"])
self.assertEqual(250, self.receipt.total_cents)

user = UserFactory(is_superuser=True, is_staff=True)
self.client.force_login(user)

def _refresh(self):
self.item.refresh_from_db()
self.other_item.refresh_from_db()
self.receipt.refresh_from_db()

def _perform(self, code: str):
data = {
"code": code,
"receipt": self.receipt.id,
}
self.assertResult(
self.client.post(f"/kirppu/{self.event.slug}/remove_item", data=data),
expect=302,
)

def test_item_removal(self):
self._perform(self.item.code)
self._refresh()

self.assertEqual(Item.BROUGHT, self.item.state)
self.assertEqual(Item.SOLD, self.other_item.state)
self.assertEqual(125, self.receipt.total_cents)

def test_box_item_removal(self):
box = BoxFactory(adopt=True, items=[self.item, self.other_item])

self._perform(f"box{box.box_number}")
self._refresh()

result_states = [self.item.state, self.other_item.state]
self.assertTrue(Item.BROUGHT in result_states)
self.assertTrue(Item.SOLD in result_states)
self.assertEqual(125, self.receipt.total_cents)

0 comments on commit 2524a1a

Please sign in to comment.