forked from CGM_Public/pretix_original
* Fix #339 -- Allow to split orders * Add tests for split orders * Add notificatiosn to both users * Improve logdisplay
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from collections import Counter, namedtuple
|
from collections import Counter, namedtuple
|
||||||
@@ -23,7 +24,10 @@ from pretix.base.models import (
|
|||||||
User, Voucher,
|
User, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.orders import CachedTicket, InvoiceAddress, OrderFee
|
from pretix.base.models.orders import (
|
||||||
|
CachedTicket, InvoiceAddress, OrderFee, generate_position_secret,
|
||||||
|
generate_secret,
|
||||||
|
)
|
||||||
from pretix.base.models.organizer import TeamAPIToken
|
from pretix.base.models.organizer import TeamAPIToken
|
||||||
from pretix.base.models.tax import TaxedPrice
|
from pretix.base.models.tax import TaxedPrice
|
||||||
from pretix.base.payment import BasePaymentProvider
|
from pretix.base.payment import BasePaymentProvider
|
||||||
@@ -657,10 +661,12 @@ class OrderChangeManager:
|
|||||||
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
||||||
CancelOperation = namedtuple('CancelOperation', ('position',))
|
CancelOperation = namedtuple('CancelOperation', ('position',))
|
||||||
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent'))
|
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent'))
|
||||||
|
SplitOperation = namedtuple('SplitOperation', ('position',))
|
||||||
|
|
||||||
def __init__(self, order: Order, user, notify=True):
|
def __init__(self, order: Order, user, notify=True):
|
||||||
self.order = order
|
self.order = order
|
||||||
self.user = user
|
self.user = user
|
||||||
|
self.split_order = None
|
||||||
self._totaldiff = 0
|
self._totaldiff = 0
|
||||||
self._quotadiff = Counter()
|
self._quotadiff = Counter()
|
||||||
self._operations = []
|
self._operations = []
|
||||||
@@ -782,6 +788,12 @@ class OrderChangeManager:
|
|||||||
self._quotadiff.update(new_quotas)
|
self._quotadiff.update(new_quotas)
|
||||||
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent))
|
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent))
|
||||||
|
|
||||||
|
def split(self, position: OrderPosition):
|
||||||
|
if self.order.event.settings.invoice_include_free or position.price != Decimal('0.00'):
|
||||||
|
self._invoice_dirty = True
|
||||||
|
|
||||||
|
self._operations.append(self.SplitOperation(position))
|
||||||
|
|
||||||
def _check_quotas(self):
|
def _check_quotas(self):
|
||||||
for quota, diff in self._quotadiff.items():
|
for quota, diff in self._quotadiff.items():
|
||||||
if diff <= 0:
|
if diff <= 0:
|
||||||
@@ -801,12 +813,26 @@ class OrderChangeManager:
|
|||||||
def _check_paid_to_free(self):
|
def _check_paid_to_free(self):
|
||||||
if self.order.total == 0:
|
if self.order.total == 0:
|
||||||
try:
|
try:
|
||||||
mark_order_paid(self.order, 'free', send_mail=False, count_waitinglist=False)
|
mark_order_paid(
|
||||||
|
self.order, 'free', send_mail=False, count_waitinglist=False,
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
|
except Quota.QuotaExceededException:
|
||||||
|
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
||||||
|
|
||||||
|
if self.split_order and self.split_order.total == 0:
|
||||||
|
try:
|
||||||
|
mark_order_paid(
|
||||||
|
self.split_order, 'free', send_mail=False, count_waitinglist=False,
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
except Quota.QuotaExceededException:
|
except Quota.QuotaExceededException:
|
||||||
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
||||||
|
|
||||||
def _perform_operations(self):
|
def _perform_operations(self):
|
||||||
nextposid = self.order.positions.aggregate(m=Max('positionid'))['m'] + 1
|
nextposid = self.order.positions.aggregate(m=Max('positionid'))['m'] + 1
|
||||||
|
split_positions = []
|
||||||
|
|
||||||
for op in self._operations:
|
for op in self._operations:
|
||||||
if isinstance(op, self.ItemOperation):
|
if isinstance(op, self.ItemOperation):
|
||||||
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
|
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
|
||||||
@@ -891,23 +917,89 @@ class OrderChangeManager:
|
|||||||
'positionid': pos.positionid,
|
'positionid': pos.positionid,
|
||||||
'subevent': op.subevent.pk if op.subevent else None,
|
'subevent': op.subevent.pk if op.subevent else None,
|
||||||
})
|
})
|
||||||
|
elif isinstance(op, self.SplitOperation):
|
||||||
|
split_positions.append(op.position)
|
||||||
|
|
||||||
def _recalculate_total_and_payment_fee(self):
|
if split_positions:
|
||||||
payment_fee = Decimal('0.00')
|
self.split_order = self._create_split_order(split_positions)
|
||||||
if self.order.total != 0:
|
|
||||||
prov = self._get_payment_provider()
|
|
||||||
if prov:
|
|
||||||
payment_fee = prov.calculate_fee(self.order.total)
|
|
||||||
|
|
||||||
if payment_fee:
|
def _create_split_order(self, split_positions):
|
||||||
fee = self.order.fees.get_or_create(fee_type=OrderFee.FEE_TYPE_PAYMENT, defaults={'value': 0})[0]
|
split_order = Order.objects.get(pk=self.order.pk)
|
||||||
|
split_order.pk = None
|
||||||
|
split_order.code = None
|
||||||
|
split_order.datetime = now()
|
||||||
|
split_order.secret = generate_secret()
|
||||||
|
split_order.save()
|
||||||
|
split_order.log_action('pretix.event.order.changed.split_from', user=self.user, data={
|
||||||
|
'original_order': self.order.code
|
||||||
|
})
|
||||||
|
|
||||||
|
for op in split_positions:
|
||||||
|
self.order.log_action('pretix.event.order.changed.split', user=self.user, data={
|
||||||
|
'position': op.pk,
|
||||||
|
'positionid': op.positionid,
|
||||||
|
'old_item': op.item.pk,
|
||||||
|
'old_variation': op.variation.pk if op.variation else None,
|
||||||
|
'old_price': op.price,
|
||||||
|
'new_order': split_order.code,
|
||||||
|
})
|
||||||
|
op.order = split_order
|
||||||
|
op.secret = generate_position_secret()
|
||||||
|
op.save()
|
||||||
|
|
||||||
|
try:
|
||||||
|
ia = copy.copy(self.order.invoice_address)
|
||||||
|
ia.pk = None
|
||||||
|
ia.order = split_order
|
||||||
|
ia.save()
|
||||||
|
except InvoiceAddress.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
split_order.total = sum([p.price for p in split_positions])
|
||||||
|
if split_order.total != Decimal('0.00') and self.order.status != Order.STATUS_PAID:
|
||||||
|
payment_fee = self._get_payment_provider().calculate_fee(split_order.total)
|
||||||
|
fee = split_order.fees.get_or_create(fee_type=OrderFee.FEE_TYPE_PAYMENT, defaults={'value': 0})[0]
|
||||||
fee.value = payment_fee
|
fee.value = payment_fee
|
||||||
fee._calculate_tax()
|
fee._calculate_tax()
|
||||||
fee.save()
|
if payment_fee != 0:
|
||||||
else:
|
fee.save()
|
||||||
self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).delete()
|
elif fee.pk:
|
||||||
|
fee.delete()
|
||||||
|
split_order.total += fee.value
|
||||||
|
|
||||||
self.order.total = sum([p.price for p in self.order.positions.all()]) + sum([f.value for f in self.order.fees.all()])
|
for fee in self.order.fees.exclude(fee_type=OrderFee.FEE_TYPE_PAYMENT):
|
||||||
|
new_fee = copy.copy(fee)
|
||||||
|
new_fee.pk = None
|
||||||
|
new_fee.order = split_order
|
||||||
|
split_order.total += new_fee.value
|
||||||
|
new_fee.save()
|
||||||
|
|
||||||
|
split_order.save()
|
||||||
|
|
||||||
|
if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last():
|
||||||
|
generate_invoice(split_order)
|
||||||
|
return split_order
|
||||||
|
|
||||||
|
def _recalculate_total_and_payment_fee(self):
|
||||||
|
self.order.total = sum([p.price for p in self.order.positions.all()])
|
||||||
|
|
||||||
|
if self.order.status != Order.STATUS_PAID:
|
||||||
|
# Do not change payment fees of paid orders
|
||||||
|
payment_fee = Decimal('0.00')
|
||||||
|
if self.order.total != 0:
|
||||||
|
prov = self._get_payment_provider()
|
||||||
|
if prov:
|
||||||
|
payment_fee = prov.calculate_fee(self.order.total)
|
||||||
|
|
||||||
|
if payment_fee:
|
||||||
|
fee = self.order.fees.get_or_create(fee_type=OrderFee.FEE_TYPE_PAYMENT, defaults={'value': 0})[0]
|
||||||
|
fee.value = payment_fee
|
||||||
|
fee._calculate_tax()
|
||||||
|
fee.save()
|
||||||
|
else:
|
||||||
|
self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).delete()
|
||||||
|
|
||||||
|
self.order.total += sum([f.value for f in self.order.fees.all()])
|
||||||
self.order.save()
|
self.order.save()
|
||||||
|
|
||||||
def _reissue_invoice(self):
|
def _reissue_invoice(self):
|
||||||
@@ -917,7 +1009,7 @@ class OrderChangeManager:
|
|||||||
generate_invoice(self.order)
|
generate_invoice(self.order)
|
||||||
|
|
||||||
def _check_complete_cancel(self):
|
def _check_complete_cancel(self):
|
||||||
cancels = len([o for o in self._operations if isinstance(o, self.CancelOperation)])
|
cancels = len([o for o in self._operations if isinstance(o, (self.CancelOperation, self.SplitOperation))])
|
||||||
if cancels == self.order.positions.count():
|
if cancels == self.order.positions.count():
|
||||||
raise OrderError(self.error_messages['complete_cancel'])
|
raise OrderError(self.error_messages['complete_cancel'])
|
||||||
|
|
||||||
@@ -928,27 +1020,27 @@ class OrderChangeManager:
|
|||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _notify_user(self):
|
def _notify_user(self, order):
|
||||||
with language(self.order.locale):
|
with language(order.locale):
|
||||||
try:
|
try:
|
||||||
invoice_name = self.order.invoice_address.name
|
invoice_name = order.invoice_address.name
|
||||||
invoice_company = self.order.invoice_address.company
|
invoice_company = order.invoice_address.company
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
invoice_name = ""
|
invoice_name = ""
|
||||||
invoice_company = ""
|
invoice_company = ""
|
||||||
email_template = self.order.event.settings.mail_text_order_changed
|
email_template = order.event.settings.mail_text_order_changed
|
||||||
email_context = {
|
email_context = {
|
||||||
'event': self.order.event.name,
|
'event': order.event.name,
|
||||||
'url': build_absolute_uri(self.order.event, 'presale:event.order', kwargs={
|
'url': build_absolute_uri(self.order.event, 'presale:event.order', kwargs={
|
||||||
'order': self.order.code,
|
'order': order.code,
|
||||||
'secret': self.order.secret
|
'secret': order.secret
|
||||||
}),
|
}),
|
||||||
'invoice_name': invoice_name,
|
'invoice_name': invoice_name,
|
||||||
'invoice_company': invoice_company,
|
'invoice_company': invoice_company,
|
||||||
}
|
}
|
||||||
email_subject = _('Your order has been changed: %(code)s') % {'code': self.order.code}
|
email_subject = _('Your order has been changed: %(code)s') % {'code': order.code}
|
||||||
try:
|
try:
|
||||||
self.order.send_mail(
|
order.send_mail(
|
||||||
email_subject, email_template, email_context,
|
email_subject, email_template, email_context,
|
||||||
'pretix.event.order.email.order_changed', self.user
|
'pretix.event.order.email.order_changed', self.user
|
||||||
)
|
)
|
||||||
@@ -973,7 +1065,9 @@ class OrderChangeManager:
|
|||||||
self._clear_tickets_cache()
|
self._clear_tickets_cache()
|
||||||
self._check_paid_to_free()
|
self._check_paid_to_free()
|
||||||
if self.notify:
|
if self.notify:
|
||||||
self._notify_user()
|
self._notify_user(self.order)
|
||||||
|
if self.split_order:
|
||||||
|
self._notify_user(self.split_order)
|
||||||
|
|
||||||
def _clear_tickets_cache(self):
|
def _clear_tickets_cache(self):
|
||||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ order_placed = EventPluginSignal(
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
This signal is sent out every time an order is placed. The order object is given
|
This signal is sent out every time an order is placed. The order object is given
|
||||||
as the first argument.
|
as the first argument. This signal is *not* sent out if an order is created through
|
||||||
|
splitting an existing order, so you can not expect to see all orders by listening
|
||||||
|
to this signal.
|
||||||
|
|
||||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
"""
|
"""
|
||||||
@@ -148,7 +150,8 @@ order_paid = EventPluginSignal(
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
This signal is sent out every time an order is paid. The order object is given
|
This signal is sent out every time an order is paid. The order object is given
|
||||||
as the first argument.
|
as the first argument. This signal is *not* sent out if an order is marked as paid
|
||||||
|
because it an already-paid order has been splitted.
|
||||||
|
|
||||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -187,7 +187,8 @@ class OrderPositionChangeForm(forms.Form):
|
|||||||
('product', 'Change product'),
|
('product', 'Change product'),
|
||||||
('price', 'Change price'),
|
('price', 'Change price'),
|
||||||
('subevent', 'Change event date'),
|
('subevent', 'Change event date'),
|
||||||
('cancel', 'Remove product')
|
('cancel', 'Remove product'),
|
||||||
|
('split', 'Split into new order'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,21 @@ def _display_order_changed(event: Event, logentry: LogEntry):
|
|||||||
price=formats.localize(Decimal(data['price'])),
|
price=formats.localize(Decimal(data['price'])),
|
||||||
currency=event.currency
|
currency=event.currency
|
||||||
)
|
)
|
||||||
|
elif logentry.action_type == 'pretix.event.order.changed.split':
|
||||||
|
old_item = str(event.items.get(pk=data['old_item']))
|
||||||
|
if data['old_variation']:
|
||||||
|
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||||
|
return text + ' ' + _('Position #{posid} ({old_item}, {old_price} {currency}) split into new order: {order}').format(
|
||||||
|
old_item=old_item,
|
||||||
|
posid=data.get('positionid', '?'),
|
||||||
|
order=data['new_order'],
|
||||||
|
old_price=formats.localize(Decimal(data['old_price'])),
|
||||||
|
currency=event.currency
|
||||||
|
)
|
||||||
|
elif logentry.action_type == 'pretix.event.order.changed.split_from':
|
||||||
|
return _('This order has been created by splitting the order {order}').format(
|
||||||
|
order=data['original_order'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
||||||
|
|||||||
@@ -36,6 +36,12 @@
|
|||||||
If an invoice is attached to the order, a cancellation will be created together with a new invoice.
|
If an invoice is attached to the order, a cancellation will be created together with a new invoice.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
If you chose "split into new order" for multiple positions, they will be all split in one second order
|
||||||
|
together, not multiple orders.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
<div class="alert alert-warning"><strong>
|
<div class="alert alert-warning"><strong>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
Please use this tool carefully. Changes you make here are not reversible. Also, if you change an order
|
Please use this tool carefully. Changes you make here are not reversible. Also, if you change an order
|
||||||
@@ -118,6 +124,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label>
|
||||||
|
<input name="{{ position.form.prefix }}-operation" type="radio" value="split"
|
||||||
|
{% if position.form.operation.value == "split" %}checked="checked"{% endif %}>
|
||||||
|
{% trans "Split into new order" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label>
|
<label>
|
||||||
<input name="{{ position.form.prefix }}-operation" type="radio" value="cancel"
|
<input name="{{ position.form.prefix }}-operation" type="radio" value="cancel"
|
||||||
|
|||||||
@@ -593,6 +593,8 @@ class OrderChange(OrderView):
|
|||||||
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
|
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
|
||||||
elif p.form.cleaned_data['operation'] == 'cancel':
|
elif p.form.cleaned_data['operation'] == 'cancel':
|
||||||
ocm.cancel(p)
|
ocm.cancel(p)
|
||||||
|
elif p.form.cleaned_data['operation'] == 'split':
|
||||||
|
ocm.split(p)
|
||||||
|
|
||||||
except OrderError as e:
|
except OrderError as e:
|
||||||
p.custom_error = str(e)
|
p.custom_error = str(e)
|
||||||
|
|||||||
@@ -762,3 +762,307 @@ class OrderChangeManagerTests(TestCase):
|
|||||||
assert fee.value == prov.calculate_fee(self.order.total)
|
assert fee.value == prov.calculate_fee(self.order.total)
|
||||||
assert fee.tax_rate == Decimal('19.00')
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
assert fee.tax_value == Decimal('0.05')
|
assert fee.tax_value == Decimal('0.05')
|
||||||
|
|
||||||
|
def test_split_simple(self):
|
||||||
|
old_secret = self.op2.secret
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
assert self.order.total == Decimal('23.00')
|
||||||
|
assert self.order.positions.count() == 1
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
assert o2.total == Decimal('23.00')
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.code != self.order.code
|
||||||
|
assert o2.secret != self.order.secret
|
||||||
|
assert o2.datetime > self.order.datetime
|
||||||
|
assert self.op2.secret != old_secret
|
||||||
|
assert not self.order.invoices.exists()
|
||||||
|
assert not o2.invoices.exists()
|
||||||
|
|
||||||
|
def test_split_pending_payment_fees(self):
|
||||||
|
# Set payment fees
|
||||||
|
self.event.settings.set('tax_rate_default', self.tr19.pk)
|
||||||
|
prov = self.ocm._get_payment_provider()
|
||||||
|
prov.settings.set('_fee_percent', Decimal('2.00'))
|
||||||
|
prov.settings.set('_fee_abs', Decimal('1.00'))
|
||||||
|
prov.settings.set('_fee_reverse_calc', False)
|
||||||
|
self.ocm.change_price(self.op1, Decimal('23.00'))
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
assert self.order.total == Decimal('47.92')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('1.92')
|
||||||
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('24.46')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('1.46')
|
||||||
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
|
assert self.order.positions.count() == 1
|
||||||
|
assert self.order.fees.count() == 1
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
assert o2.total == Decimal('24.46')
|
||||||
|
fee = o2.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('1.46')
|
||||||
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.fees.count() == 1
|
||||||
|
|
||||||
|
def test_split_paid_no_payment_fees(self):
|
||||||
|
self.order.status = Order.STATUS_PAID
|
||||||
|
self.order.save()
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('23.00')
|
||||||
|
assert not self.order.fees.exists()
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
assert o2.total == Decimal('23.00')
|
||||||
|
assert o2.status == Order.STATUS_PAID
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.fees.count() == 0
|
||||||
|
|
||||||
|
def test_split_invoice_address(self):
|
||||||
|
ia = InvoiceAddress.objects.create(
|
||||||
|
order=self.order, is_business=True, vat_id='ATU1234567', vat_id_validated=True,
|
||||||
|
country=Country('AT'), company='Sample'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('23.00')
|
||||||
|
assert self.order.invoice_address == ia
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
o2.refresh_from_db()
|
||||||
|
ia = InvoiceAddress.objects.get(pk=ia.pk)
|
||||||
|
assert o2.total == Decimal('23.00')
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.invoice_address != ia
|
||||||
|
assert o2.invoice_address.company == 'Sample'
|
||||||
|
|
||||||
|
def test_split_reverse_charge(self):
|
||||||
|
ia = self._enable_reverse_charge()
|
||||||
|
|
||||||
|
# Set payment fees
|
||||||
|
self.event.settings.set('tax_rate_default', self.tr19.pk)
|
||||||
|
prov = self.ocm._get_payment_provider()
|
||||||
|
prov.settings.set('_fee_percent', Decimal('2.00'))
|
||||||
|
prov.settings.set('_fee_reverse_calc', False)
|
||||||
|
self.ocm.recalculate_taxes()
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
|
||||||
|
# Check if reverse charge is active
|
||||||
|
assert self.order.total == Decimal('43.86')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('0.86')
|
||||||
|
assert fee.tax_rate == Decimal('0.00')
|
||||||
|
self.op1.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
assert self.op1.price == Decimal('21.50')
|
||||||
|
assert self.op2.price == Decimal('21.50')
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('21.93')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('0.43')
|
||||||
|
assert fee.tax_rate == Decimal('0.00')
|
||||||
|
assert fee.tax_value == Decimal('0.00')
|
||||||
|
assert self.order.positions.count() == 1
|
||||||
|
assert self.order.fees.count() == 1
|
||||||
|
assert self.order.positions.first().price == Decimal('21.50')
|
||||||
|
assert self.order.positions.first().tax_value == Decimal('0.00')
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
print([p.price for p in o2.positions.all()], [p.value for p in o2.fees.all()])
|
||||||
|
assert o2.total == Decimal('21.93')
|
||||||
|
fee = o2.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('0.43')
|
||||||
|
assert fee.tax_rate == Decimal('0.00')
|
||||||
|
assert fee.tax_value == Decimal('0.00')
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.positions.first().price == Decimal('21.50')
|
||||||
|
assert o2.positions.first().tax_value == Decimal('0.00')
|
||||||
|
assert o2.fees.count() == 1
|
||||||
|
ia = InvoiceAddress.objects.get(pk=ia.pk)
|
||||||
|
assert o2.invoice_address != ia
|
||||||
|
assert o2.invoice_address.vat_id_validated is True
|
||||||
|
|
||||||
|
def test_split_other_fees(self):
|
||||||
|
# Check if reverse charge is active
|
||||||
|
self.order.fees.create(fee_type=OrderFee.FEE_TYPE_SHIPPING, tax_rule=self.tr19, value=Decimal('2.50'))
|
||||||
|
self.order.total += Decimal('2.50')
|
||||||
|
self.order.save()
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('25.50')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_SHIPPING)
|
||||||
|
assert fee.value == Decimal('2.50')
|
||||||
|
assert fee.tax_value == Decimal('0.40')
|
||||||
|
assert self.order.positions.count() == 1
|
||||||
|
assert self.order.fees.count() == 1
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
assert o2.total == Decimal('25.50')
|
||||||
|
fee = o2.fees.get(fee_type=OrderFee.FEE_TYPE_SHIPPING)
|
||||||
|
assert fee.value == Decimal('2.50')
|
||||||
|
assert fee.tax_value == Decimal('0.40')
|
||||||
|
assert o2.positions.count() == 1
|
||||||
|
assert o2.positions.first().price == Decimal('23.00')
|
||||||
|
assert o2.fees.count() == 1
|
||||||
|
|
||||||
|
def test_split_to_empty(self):
|
||||||
|
self.ocm.split(self.op1)
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
with self.assertRaises(OrderError):
|
||||||
|
self.ocm.commit()
|
||||||
|
|
||||||
|
def test_split_paid_payment_fees(self):
|
||||||
|
# Set payment fees
|
||||||
|
self.event.settings.set('tax_rate_default', self.tr19.pk)
|
||||||
|
prov = self.ocm._get_payment_provider()
|
||||||
|
prov.settings.set('_fee_percent', Decimal('2.00'))
|
||||||
|
prov.settings.set('_fee_abs', Decimal('1.00'))
|
||||||
|
prov.settings.set('_fee_reverse_calc', False)
|
||||||
|
self.ocm.change_price(self.op1, Decimal('23.00'))
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
assert self.order.total == Decimal('47.92')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('1.92')
|
||||||
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
|
|
||||||
|
self.order.status = Order.STATUS_PAID
|
||||||
|
self.order.save()
|
||||||
|
|
||||||
|
# Split
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
# First order
|
||||||
|
assert self.order.total == Decimal('24.92')
|
||||||
|
fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
||||||
|
assert fee.value == Decimal('1.92')
|
||||||
|
assert fee.tax_rate == Decimal('19.00')
|
||||||
|
assert self.order.positions.count() == 1
|
||||||
|
assert self.order.fees.count() == 1
|
||||||
|
|
||||||
|
# New order
|
||||||
|
assert self.op2.order != self.order
|
||||||
|
o2 = self.op2.order
|
||||||
|
assert o2.total == Decimal('23.00')
|
||||||
|
assert o2.fees.count() == 0
|
||||||
|
|
||||||
|
def test_split_invoice(self):
|
||||||
|
generate_invoice(self.order)
|
||||||
|
assert self.order.invoices.count() == 1
|
||||||
|
assert self.order.invoices.last().lines.count() == 2
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
o2 = self.op2.order
|
||||||
|
|
||||||
|
assert self.order.invoices.count() == 3
|
||||||
|
assert self.order.invoices.last().lines.count() == 1
|
||||||
|
assert o2.invoices.count() == 1
|
||||||
|
assert o2.invoices.last().lines.count() == 1
|
||||||
|
|
||||||
|
def test_split_to_free_invoice(self):
|
||||||
|
self.event.settings.invoice_include_free = False
|
||||||
|
self.ocm.change_price(self.op2, Decimal('0.00'))
|
||||||
|
self.ocm.commit()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
self.ocm._invoice_dirty = False
|
||||||
|
|
||||||
|
generate_invoice(self.order)
|
||||||
|
assert self.order.invoices.count() == 1
|
||||||
|
assert self.order.invoices.last().lines.count() == 1
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
o2 = self.op2.order
|
||||||
|
|
||||||
|
assert self.order.invoices.count() == 1
|
||||||
|
assert self.order.invoices.last().lines.count() == 1
|
||||||
|
assert o2.invoices.count() == 0
|
||||||
|
|
||||||
|
def test_split_to_original_free(self):
|
||||||
|
self.ocm.change_price(self.op2, Decimal('0.00'))
|
||||||
|
self.ocm.commit()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
self.ocm.split(self.op1)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
o2 = self.op1.order
|
||||||
|
|
||||||
|
assert self.order.total == Decimal('0.00')
|
||||||
|
assert self.order.status == Order.STATUS_PAID
|
||||||
|
assert o2.total == Decimal('23.00')
|
||||||
|
assert o2.status == Order.STATUS_PENDING
|
||||||
|
|
||||||
|
def test_split_to_new_free(self):
|
||||||
|
self.ocm.change_price(self.op2, Decimal('0.00'))
|
||||||
|
self.ocm.commit()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
|
||||||
|
self.ocm.split(self.op2)
|
||||||
|
self.ocm.commit()
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.op2.refresh_from_db()
|
||||||
|
o2 = self.op2.order
|
||||||
|
|
||||||
|
assert self.order.total == Decimal('23.00')
|
||||||
|
assert self.order.status == Order.STATUS_PENDING
|
||||||
|
assert o2.total == Decimal('0.00')
|
||||||
|
assert o2.status == Order.STATUS_PAID
|
||||||
|
|||||||
Reference in New Issue
Block a user