Refactor cancelling positions and orders in the data model (#1088)

- [x] Data model
- [x] display in order view in backend
- [x] review all usages of OrderPositions.objects
- [x] review all usages of order.positions
- [x] review all other model usages
- [x] review plugins
- [x] plugins backwards-compatible API?
- [x] decide on way forward for REST API
- [x] need to cancel fees
- [x] tests
- [ ] plugins
  - [ ] gdpr
  - [ ] reports
- [x] docs
This commit is contained in:
Raphael Michel
2019-01-10 16:52:34 +01:00
committed by GitHub
parent 588955901c
commit 8abfbba9d0
41 changed files with 579 additions and 351 deletions

View File

@@ -104,6 +104,16 @@ def order(event, item, taxrule, question):
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
pseudonymization_id="ABCDEFGHKL",
)
OrderPosition.objects.create(
order=o,
item=item,
variation=None,
price=Decimal("23"),
attendee_name_parts={"full_name": "Peter", "_scheme": "full"},
secret="YBiYJrmF5ufiTLdV1iDf",
pseudonymization_id="JKLM",
canceled=True
)
op.answers.create(question=question, answer='S')
return o
@@ -509,7 +519,7 @@ def test_refund_process_mark_refunded(token_client, organizer, event, order):
assert resp.status_code == 200
assert r.state == OrderRefund.REFUND_STATE_DONE
order.refresh_from_db()
assert order.status == Order.STATUS_REFUNDED
assert order.status == Order.STATUS_CANCELED
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/refunds/2/process/'.format(
organizer.slug, event.slug, order.code
@@ -692,6 +702,14 @@ def test_orderposition_detail(token_client, organizer, event, order, item, quest
assert len(resp.data['downloads']) == 1
@pytest.mark.django_db
def test_orderposition_detail_no_canceled(token_client, organizer, event, order, item, question):
op = order.all_positions.filter(canceled=True).first()
resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(organizer.slug, event.slug,
op.pk))
assert resp.status_code == 404
@pytest.mark.django_db
def test_orderposition_delete(token_client, organizer, event, order, item, question):
op = order.positions.first()
@@ -720,6 +738,7 @@ def test_orderposition_delete(token_client, organizer, event, order, item, quest
))
assert resp.status_code == 204
assert order.positions.count() == 1
assert order.all_positions.count() == 3
order.refresh_from_db()
assert order.total == Decimal('23.25')
@@ -952,8 +971,8 @@ def test_order_mark_canceled_pending_no_email(token_client, organizer, event, or
@pytest.mark.django_db
def test_order_mark_canceled_paid(token_client, organizer, event, order):
order.status = Order.STATUS_PAID
def test_order_mark_canceled_expired(token_client, organizer, event, order):
order.status = Order.STATUS_EXPIRED
order.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/{}/mark_canceled/'.format(
@@ -962,7 +981,7 @@ def test_order_mark_canceled_paid(token_client, organizer, event, order):
)
assert resp.status_code == 400
order.refresh_from_db()
assert order.status == Order.STATUS_PAID
assert order.status == Order.STATUS_EXPIRED
@pytest.mark.django_db
@@ -975,7 +994,7 @@ def test_order_mark_paid_refunded(token_client, organizer, event, order):
)
)
assert resp.status_code == 200
assert resp.data['status'] == Order.STATUS_REFUNDED
assert resp.data['status'] == Order.STATUS_CANCELED
@pytest.mark.django_db
@@ -2411,7 +2430,7 @@ def test_refund_create_mark_refunded(token_client, organizer, event, order):
assert r.info_data == {"foo": "bar"}
assert r.payment.local_id == 2
order.refresh_from_db()
assert order.status == Order.STATUS_REFUNDED
assert order.status == Order.STATUS_CANCELED
@pytest.mark.django_db

View File

@@ -58,6 +58,14 @@ def env():
price=Decimal("42.00"),
positionid=2,
)
OrderPosition.objects.create(
order=o,
item=t_shirt,
variation=variation,
price=Decimal("42.00"),
positionid=3,
canceled=True
)
gs = GlobalSettingsObject()
gs.settings.ecb_rates_date = date.today()
gs.settings.ecb_rates_dict = json.dumps({

View File

@@ -740,7 +740,7 @@ class OrderTestCase(BaseQuotaTestCase):
q = Question.objects.create(question='Foo', type=Question.TYPE_BOOLEAN, event=self.event)
self.item1.questions.add(q)
assert self.order.can_modify_answers
self.order.status = Order.STATUS_REFUNDED
self.order.status = Order.STATUS_CANCELED
assert not self.order.can_modify_answers
self.order.status = Order.STATUS_PAID
assert self.order.can_modify_answers
@@ -963,7 +963,7 @@ class OrderTestCase(BaseQuotaTestCase):
assert o.has_external_refund
def test_pending_order_pending_refund(self):
self.order.status = Order.STATUS_REFUNDED
self.order.status = Order.STATUS_CANCELED
self.order.save()
self.order.payments.create(
amount=Decimal('46.00'),
@@ -1023,6 +1023,14 @@ class OrderTestCase(BaseQuotaTestCase):
assert not o.has_pending_refund
assert not o.has_external_refund
def test_canceled_positions(self):
self.op1.canceled = True
self.op1.save()
assert OrderPosition.objects.count() == 1
assert OrderPosition.all.count() == 2
assert self.order.positions.count() == 1
assert self.order.all_positions.count() == 2
class ItemCategoryTest(TestCase):
"""

View File

@@ -616,6 +616,26 @@ class OrderChangeManagerTests(TestCase):
self.order.refresh_from_db()
assert self.order.positions.count() == 1
assert self.order.total == self.op2.price
self.op1.refresh_from_db()
assert self.op1.canceled
def test_cancel_with_addon(self):
self.shirt.category = self.event.categories.create(name='Add-ons', is_addon=True)
self.ticket.addons.create(addon_category=self.shirt.category)
self.ocm.add_position(self.shirt, None, Decimal('13.00'), self.op1)
self.ocm.commit()
self.order.refresh_from_db()
self.ocm = OrderChangeManager(self.order, None)
assert self.order.positions.count() == 3
self.ocm.cancel(self.op1)
self.ocm.commit()
self.order.refresh_from_db()
assert self.order.positions.count() == 1
assert self.order.total == self.op2.price
self.op1.refresh_from_db()
assert self.op1.canceled
assert self.op1.addons.first().canceled
def test_free_to_paid(self):
self.order.status = Order.STATUS_PAID

View File

@@ -53,6 +53,14 @@ def env():
price=Decimal("14"),
attendee_name_parts={'full_name': "Peter", "_scheme": "full"}
)
OrderPosition.objects.create(
order=o,
item=ticket,
variation=None,
price=Decimal("14"),
canceled=True,
attendee_name_parts={'full_name': "Lukas Gelöscht", "_scheme": "full"}
)
return event, user, o, ticket
@@ -125,6 +133,7 @@ def test_order_detail(client, env):
response = client.get('/control/event/dummy/dummy/orders/FOO/')
assert 'Early-bird' in response.rendered_content
assert 'Peter' in response.rendered_content
assert 'Lukas Gelöscht' in response.rendered_content
@pytest.mark.django_db
@@ -256,23 +265,15 @@ def test_order_deny(client, env):
# (Old status, new status, success expected)
(Order.STATUS_CANCELED, Order.STATUS_PAID, False),
(Order.STATUS_CANCELED, Order.STATUS_PENDING, False),
(Order.STATUS_CANCELED, Order.STATUS_REFUNDED, False),
(Order.STATUS_CANCELED, Order.STATUS_EXPIRED, False),
(Order.STATUS_PAID, Order.STATUS_PENDING, False),
(Order.STATUS_PAID, Order.STATUS_CANCELED, False),
(Order.STATUS_PAID, Order.STATUS_REFUNDED, False),
(Order.STATUS_PAID, Order.STATUS_CANCELED, True),
(Order.STATUS_PAID, Order.STATUS_EXPIRED, False),
(Order.STATUS_PENDING, Order.STATUS_CANCELED, True),
(Order.STATUS_PENDING, Order.STATUS_PAID, True),
(Order.STATUS_PENDING, Order.STATUS_REFUNDED, False),
(Order.STATUS_PENDING, Order.STATUS_EXPIRED, True),
(Order.STATUS_REFUNDED, Order.STATUS_CANCELED, False),
(Order.STATUS_REFUNDED, Order.STATUS_PAID, False),
(Order.STATUS_REFUNDED, Order.STATUS_PENDING, False),
(Order.STATUS_REFUNDED, Order.STATUS_EXPIRED, False),
])
def test_order_transition(client, env, process):
o = Order.objects.get(id=env[2].id)
@@ -784,6 +785,11 @@ class OrderChangeTests(SoupTest):
order=self.order, item=self.ticket, variation=None,
price=Decimal("23.00"), attendee_name_parts={'full_name': "Dieter", "_scheme": "full"}
)
self.op3 = OrderPosition.objects.create(
order=self.order, item=self.ticket, variation=None,
price=Decimal("23.00"), attendee_name_parts={'full_name': "Lukas", "_scheme": "full"},
canceled=True
)
self.quota = self.event.quotas.create(name="All", size=100)
self.quota.items.add(self.ticket)
self.quota.items.add(self.shirt)
@@ -793,6 +799,14 @@ class OrderChangeTests(SoupTest):
t.limit_events.add(self.event)
self.client.login(email='dummy@dummy.dummy', password='dummy')
def test_do_not_show_canceled(self):
r = self.client.get('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
))
assert self.op1.secret[:5] in r.rendered_content
assert self.op2.secret[:5] in r.rendered_content
assert self.op3.secret[:5] not in r.rendered_content
def test_change_item_success(self):
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
@@ -1137,7 +1151,7 @@ def test_process_refund_mark_refunded(client, env):
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_DONE
env[2].refresh_from_db()
assert env[2].status == Order.STATUS_REFUNDED
assert env[2].status == Order.STATUS_CANCELED
@pytest.mark.django_db
@@ -1240,7 +1254,7 @@ def test_refund_paid_order_fully_mark_as_refunded(client, env):
assert r.provider == "manual"
assert r.state == OrderRefund.REFUND_STATE_DONE
assert r.amount == Decimal('14.00')
assert env[2].status == Order.STATUS_REFUNDED
assert env[2].status == Order.STATUS_CANCELED
@pytest.mark.django_db

View File

@@ -162,7 +162,7 @@ def test_retry_refunded(env, client):
state=BankTransaction.STATE_ERROR,
amount=23, date='unknown', order=env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
env[3].status = Order.STATUS_REFUNDED
env[3].status = Order.STATUS_CANCELED
env[3].save()
r = json.loads(client.post('/control/event/{}/{}/banktransfer/action/'.format(env[0].organizer.slug, env[0].slug), {
'action_{}'.format(trans.pk): 'retry',
@@ -171,7 +171,7 @@ def test_retry_refunded(env, client):
trans.refresh_from_db()
assert trans.state == BankTransaction.STATE_ERROR
env[3].refresh_from_db()
assert env[3].status == Order.STATUS_REFUNDED
assert env[3].status == Order.STATUS_CANCELED
@pytest.mark.django_db

View File

@@ -63,6 +63,14 @@ class OrdersTest(TestCase):
price=Decimal("23"),
attendee_name_parts={'full_name': "Peter"}
)
self.deleted_pos = OrderPosition.objects.create(
order=self.order,
item=self.ticket,
variation=None,
price=Decimal("23"),
attendee_name_parts={'full_name': "Lukas"},
canceled=True
)
self.not_my_order = Order.objects.create(
status=Order.STATUS_PENDING,
event=self.event,
@@ -130,15 +138,17 @@ class OrdersTest(TestCase):
doc = BeautifulSoup(response.rendered_content, "lxml")
assert len(doc.select(".cart-row")) > 0
assert "pending" in doc.select(".label-warning")[0].text.lower()
assert "Peter" in response.rendered_content
assert "Lukas" not in response.rendered_content
def test_orders_modify_invalid(self):
self.order.status = Order.STATUS_REFUNDED
self.order.status = Order.STATUS_CANCELED
self.order.save()
self.client.get(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
self.order = Order.objects.get(id=self.order.id)
assert self.order.status == Order.STATUS_REFUNDED
assert self.order.status == Order.STATUS_CANCELED
def test_orders_modify_attendee_optional(self):
self.event.settings.set('attendee_names_asked', True)
@@ -169,6 +179,8 @@ class OrdersTest(TestCase):
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret))
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertEqual(len(doc.select('input[name=%s-attendee_name_parts_0]' % self.ticket_pos.id)), 1)
assert "Peter" in response.rendered_content
assert "Lukas" not in response.rendered_content
# Not all required fields filled out, expect failure
response = self.client.post(