Add new field OrderRefund.comment

This commit is contained in:
Raphael Michel
2021-01-15 11:25:09 +01:00
parent 674d7673ce
commit f1cd46f6dc
17 changed files with 103 additions and 38 deletions

View File

@@ -502,7 +502,7 @@ class OrderRefundSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderRefund
fields = ('local_id', 'state', 'source', 'amount', 'payment', 'created', 'execution_date', 'provider')
fields = ('local_id', 'state', 'source', 'amount', 'payment', 'created', 'execution_date', 'comment', 'provider')
class OrderURLField(serializers.URLField):
@@ -1324,7 +1324,7 @@ class OrderRefundCreateSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderRefund
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info')
fields = ('state', 'source', 'amount', 'payment', 'execution_date', 'provider', 'info', 'comment')
def create(self, validated_data):
pid = validated_data.pop('payment', None)

View File

@@ -652,7 +652,7 @@ class PaymentListExporter(ListExporter):
headers = [
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
_('Status code'), _('Amount'), _('Payment method')
_('Status code'), _('Amount'), _('Payment method'), _('Comment')
]
yield headers
@@ -674,7 +674,8 @@ class PaymentListExporter(ListExporter):
obj.get_state_display(),
obj.state,
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
provider_names.get(obj.provider, obj.provider)
provider_names.get(obj.provider, obj.provider),
obj.comment if isinstance(obj, OrderRefund) else "",
]
yield row

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.11 on 2021-01-15 09:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0174_merge_20201222_1031'),
]
operations = [
migrations.AddField(
model_name='orderrefund',
name='comment',
field=models.TextField(null=True),
),
]

View File

@@ -1716,6 +1716,11 @@ class OrderRefund(models.Model):
max_length=255,
verbose_name=_("Payment provider")
)
comment = models.TextField(
verbose_name=_("Refund reason"),
help_text=_('May be shown to the end user or used e.g. as part of a payment reference.'),
null=True, blank=True
)
info = models.TextField(
verbose_name=_("Payment information"),
null=True, blank=True

View File

@@ -3,6 +3,7 @@ from decimal import Decimal
from django.db import transaction
from django.db.models import Count, Exists, IntegerField, OuterRef, Subquery
from django.utils.translation import gettext
from i18nfield.strings import LazyI18nString
from pretix.base.decimal import round_decimal
@@ -195,7 +196,8 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if auto_refund:
_try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True,
source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions)
giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
comment=gettext('Event canceled'))
finally:
if send:
_send_mail(o, send_subject, send_message, subevent, refund_amount, user, o.positions.all())
@@ -252,7 +254,8 @@ def cancel_event(self, event: Event, subevent: int, auto_refund: bool,
if auto_refund:
_try_auto_refund(o.pk, manual_refund=manual_refund, allow_partial=True,
source=OrderRefund.REFUND_SOURCE_ADMIN, refund_as_giftcard=refund_as_giftcard,
giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions)
giftcard_expires=giftcard_expires, giftcard_conditions=giftcard_conditions,
comment=gettext('Event canceled'))
if send:
_send_mail(o, send_subject, send_message, subevent, refund_amount, user, positions)

View File

@@ -2034,7 +2034,7 @@ _unset = object()
def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=OrderRefund.REFUND_SOURCE_BUYER,
refund_as_giftcard=False, giftcard_expires=_unset, giftcard_conditions=None):
refund_as_giftcard=False, giftcard_expires=_unset, giftcard_conditions=None, comment=None):
notify_admin = False
error = False
if isinstance(order, int):
@@ -2059,6 +2059,7 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
order=order,
payment=None,
source=source,
comment=comment,
state=OrderRefund.REFUND_STATE_CREATED,
execution_date=now(),
amount=can_auto_refund_sum,
@@ -2096,6 +2097,7 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
source=source,
state=OrderRefund.REFUND_STATE_CREATED,
amount=value,
comment=comment,
provider=p.provider
)
order.log_action('pretix.event.order.refund.created', {
@@ -2125,6 +2127,7 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
with transaction.atomic():
r = order.refunds.create(
source=source,
comment=comment,
state=OrderRefund.REFUND_STATE_CREATED,
amount=refund_amount - can_auto_refund_sum,
provider='manual'
@@ -2149,13 +2152,14 @@ def _try_auto_refund(order, manual_refund=False, allow_partial=False, source=Ord
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
@scopes_disabled()
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None,
device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False):
device=None, cancellation_fee=None, try_auto_refund=False, refund_as_giftcard=False, comment=None):
try:
try:
ret = _cancel_order(order, user, send_mail, api_token, device, oauth_application,
cancellation_fee)
if try_auto_refund:
_try_auto_refund(order, refund_as_giftcard=refund_as_giftcard)
_try_auto_refund(order, refund_as_giftcard=refund_as_giftcard,
comment=comment)
return ret
except LockTimeoutException:
self.retry()

View File

@@ -6,6 +6,7 @@
{% load rich_text %}
{% load safelink %}
{% load eventsignal %}
{% load l10n %}
{% load phone_format %}
{% block title %}
{% blocktrans trimmed with code=order.code %}
@@ -97,7 +98,10 @@
{% csrf_token %}
<input type="hidden" name="start-action" value="do_nothing">
<input type="hidden" name="start-mode" value="partial">
<input type="hidden" name="start-partial_amount" value="{{ overpaid }}">
{% localize off %}
<input type="hidden" name="start-partial_amount" value="{{ overpaid|floatformat:2 }}">
{% endlocalize %}
<input type="hidden" name="comment" value="{% trans "Refund for overpayment" %}">
<div class="alert alert-warning">
{% blocktrans trimmed with amount=overpaid|money:request.event.currency %}
This order is currently overpaid by {{ amount }}.
@@ -759,11 +763,19 @@
{% endif %}
</td>
</tr>
{% if r.html_info %}
{% if r.html_info or staff_session or r.comment %}
<tr>
<td colspan="1"></td>
<td colspan="7">
{{ r.html_info|safe }}
{% if r.comment %}
<dl class="dl-horizontal">
<dt>{% trans "Comment" %}</dt>
<dd>{{ r.comment }}</dd>
</dl>
{% endif %}
{% if r.html_info %}
{{ r.html_info|safe }}
{% endif %}
{% if staff_session %}
<p>
<a href="" class="btn btn-default btn-xs" data-expandrefund
@@ -775,17 +787,6 @@
{% endif %}
</td>
</tr>
{% elif staff_session %}
<tr>
<td colspan="1"></td>
<td colspan="7">
<a href="" class="btn btn-default btn-xs" data-expandrefund
data-id="{{ r.pk }}">
<span class="fa-eye fa fa-fw"></span>
{% trans "Inspect" %}
</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>

View File

@@ -162,6 +162,13 @@
<input type="hidden" name="start-mode" value="{{ start_form.cleaned_data.mode }}">
<input type="hidden" name="start-partial_amount" value="{{ partial_amount }}">
<div class="form-group">
<label class="control-label" for="id_comment">{% trans "Refund reason" %}</label>
<input type="text" name="comment" class="form-control" title="{% trans "May be shown to the end user or used e.g. as part of a payment reference." %}" id="id_comment"
value="{{ comment|default:"" }}">
<div class="help-block">{% trans "May be shown to the end user or used e.g. as part of a payment reference." %}</div>
</div>
<div class="row checkout-button-row">
<div class="col-md-4">
<a class="btn btn-block btn-default btn-lg"

View File

@@ -5,7 +5,7 @@ import os
import re
from datetime import datetime, time, timedelta
from decimal import Decimal, DecimalException
from urllib.parse import urlencode
from urllib.parse import quote, urlencode
import vat_moss.id
from django.conf import settings
@@ -759,6 +759,7 @@ class OrderRefundView(OrderView):
def choose_form(self):
payments = list(self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED))
comment = self.request.POST.get("comment") or self.request.GET.get("comment") or None
if self.start_form.cleaned_data.get('mode') == 'full':
full_refund = self.order.payment_refund_sum
else:
@@ -800,6 +801,7 @@ class OrderRefundView(OrderView):
else OrderRefund.REFUND_STATE_CREATED
),
amount=manual_value,
comment=comment,
provider='manual'
))
@@ -827,6 +829,7 @@ class OrderRefundView(OrderView):
execution_date=now(),
amount=giftcard_value,
provider='giftcard',
comment=comment,
info=json.dumps({
'gift_card': giftcard.pk
})
@@ -857,6 +860,7 @@ class OrderRefundView(OrderView):
execution_date=now(),
amount=offsetting_value,
provider='offsetting',
comment=comment,
info=json.dumps({
'orders': [order.code]
})
@@ -891,6 +895,7 @@ class OrderRefundView(OrderView):
source=OrderRefund.REFUND_SOURCE_ADMIN,
state=OrderRefund.REFUND_STATE_CREATED,
amount=value,
comment=comment,
provider=p.provider
))
@@ -968,6 +973,7 @@ class OrderRefundView(OrderView):
'payments': payments,
'remainder': to_refund,
'order': self.order,
'comment': comment,
'giftcard_proposal': giftcard_proposal,
'partial_amount': (
self.request.POST.get('start-partial_amount') if self.request.method == 'POST'
@@ -1098,14 +1104,16 @@ class OrderTransition(OrderView):
if self.order.pending_sum < 0:
messages.success(self.request, _('The order has been canceled. You can now select how you want to '
'transfer the money back to the user.'))
return redirect(reverse('control:event.order.refunds.start', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'code': self.order.code
}) + '?start-action=do_nothing&start-mode=partial&start-partial_amount={}&giftcard={}'.format(
round_decimal(self.order.pending_sum * -1),
'true' if self.req and self.req.refund_as_giftcard else 'false'
))
with language(self.order.locale):
return redirect(reverse('control:event.order.refunds.start', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'code': self.order.code
}) + '?start-action=do_nothing&start-mode=partial&start-partial_amount={}&giftcard={}&comment={}'.format(
round_decimal(self.order.pending_sum * -1),
'true' if self.req and self.req.refund_as_giftcard else 'false',
quote(gettext('Order canceled'))
))
messages.success(self.request, _('The order has been canceled.'))
elif self.order.status == Order.STATUS_PENDING and to == 'e':

View File

@@ -22,7 +22,7 @@ def get_refund_export_csv(refund_export: RefundExport):
output = StreamWriter(byte_data)
writer = csv.writer(output)
writer.writerow([_("Payer"), "IBAN", "BIC", _("Amount"), _("Currency"), _("Code")])
writer.writerow([_("Payer"), "IBAN", "BIC", _("Amount"), _("Currency"), _("Code"), _("Comment")])
for row in refund_export.rows_data:
bic = ''
if row.get('bic'):
@@ -39,6 +39,7 @@ def get_refund_export_csv(refund_export: RefundExport):
localize(Decimal(row['amount'])),
refund_export.currency,
row['id'],
row.get('comment') or '',
])
filename = _get_filename(refund_export) + ".csv"
@@ -68,7 +69,7 @@ def build_sepa_xml(refund_export: RefundExport, account_holder, iban, bic):
"IBAN": row["iban"],
"amount": int(Decimal(row['amount']) * 100), # in euro-cents
"execution_date": datetime.date.today(),
"description": f"{_('Refund')} {refund_export.entity_slug} {row['id']}",
"description": f"{_('Refund')} {refund_export.entity_slug} {row['id']} {row.get('comment') or ''}".strip()[:140],
}
if row.get('bic'):
try:

View File

@@ -20,6 +20,7 @@ from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import change_payment_provider
from pretix.base.services.tasks import TransactionAwareTask
from pretix.celery_app import app
from .models import BankImportJob, BankTransaction
logger = logging.getLogger(__name__)

View File

@@ -602,6 +602,7 @@ def _unite_transaction_rows(transaction_rows):
"id": ", ".join(sorted(set(r['id'] for r in rows))),
"payer": ", ".join(sorted(set(r['payer'] for r in rows))),
"amount": sum(r['amount'] for r in rows),
"comment": ", ".join(r['comment'] for r in rows if r.get('comment')) or None,
})
return united_transactions_rows
@@ -649,6 +650,7 @@ class RefundExportListView(ListView):
transaction_rows.append({
"amount": refund.amount,
"id": refund.full_id,
"comment": refund.comment,
**{key: data.get(key) for key in ("payer", "iban", "bic")}
})
refund.done(user=self.request.user)

View File

@@ -1,7 +1,7 @@
import copy
import tempfile
from collections import OrderedDict, defaultdict
from datetime import date, datetime, timedelta, time
from datetime import date, datetime, time, timedelta
from decimal import Decimal
import pytz

View File

@@ -18,7 +18,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import TemplateView, View
@@ -849,7 +849,9 @@ class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
self.order.log_action('pretix.event.order.refund.requested')
return self.success(None)
else:
return self.do(self.order.pk, cancellation_fee=fee, try_auto_refund=True, refund_as_giftcard=giftcard)
comment = gettext('Canceled by customer')
return self.do(self.order.pk, cancellation_fee=fee, try_auto_refund=True, refund_as_giftcard=giftcard,
comment=comment)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)

View File

@@ -201,6 +201,7 @@ TEST_REFUNDS_RES = [
"source": "admin",
"created": "2017-12-01T10:00:00Z",
"execution_date": "2017-12-01T10:00:00Z",
"comment": None,
"provider": "stripe",
"state": "done",
"amount": "23.00"

View File

@@ -124,6 +124,7 @@ def test_unite_transaction_rows():
'iban': 'DE12345678901234567890',
'bic': 'HARKE9000',
'id': "ROLLA-R-1",
'comment': None,
'amount': Decimal("42.23"),
},
{
@@ -131,6 +132,7 @@ def test_unite_transaction_rows():
'iban': 'DE111111111111111111111',
'bic': 'ikswez2020',
'id': "PARTY-R-1",
'comment': None,
'amount': Decimal("6.50"),
}
], key=_row_key_func)
@@ -143,6 +145,7 @@ def test_unite_transaction_rows():
'iban': 'DE12345678901234567890',
'bic': 'HARKE9000',
'id': "ROLLA-R-1",
'comment': None,
'amount': Decimal("7.77"),
},
{
@@ -150,6 +153,7 @@ def test_unite_transaction_rows():
'iban': 'DE111111111111111111111',
'bic': 'ikswez2020',
'id': "PARTY-R-2",
'comment': None,
'amount': Decimal("13.50"),
}
], key=_row_key_func)
@@ -160,6 +164,7 @@ def test_unite_transaction_rows():
'iban': 'DE12345678901234567890',
'bic': 'HARKE9000',
'id': "ROLLA-R-1",
'comment': None,
'amount': Decimal("50.00"),
},
{
@@ -167,5 +172,6 @@ def test_unite_transaction_rows():
'iban': 'DE111111111111111111111',
'bic': 'ikswez2020',
'id': 'PARTY-R-1, PARTY-R-2',
'comment': None,
'amount': Decimal('20.00'),
}], key=_row_key_func)