Passed
Push — main ( 68ee7a...c0bc2e )
by Jochen
04:34
created

byceps.blueprints.admin.shop.order.views   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 520
Duplicated Lines 0 %

Test Coverage

Coverage 50.72%

Importance

Changes 0
Metric Value
eloc 303
dl 0
loc 520
ccs 105
cts 207
cp 0.5072
rs 9.44
c 0
b 0
f 0
wmc 37

19 Functions

Rating   Name   Duplication   Size   Complexity  
A unset_shipped_flag() 0 14 1
A set_invoiced_flag() 0 14 1
A cancel() 0 41 4
A add_note() 0 17 2
A export() 0 11 2
A _find_order_payment_method_label() 0 2 1
A add_note_form() 0 18 2
A view() 0 32 2
A _get_order_or_404() 0 7 2
A _get_shop_or_404() 0 7 2
A create_number_sequence_form() 0 15 2
A resend_email_for_incoming_order_to_orderer() 0 21 1
A create_number_sequence() 0 32 3
A cancel_form() 0 27 3
A set_shipped_flag() 0 14 1
A unset_invoiced_flag() 0 14 1
B index_for_shop() 0 51 1
A mark_as_paid() 0 28 3
A mark_as_paid_form() 0 22 3
1
"""
2
byceps.blueprints.admin.shop.order.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from flask import abort, g, request, Response
10 1
from flask_babel import gettext
11
12 1
from .....services.brand import service as brand_service
13 1
from .....services.shop.order import (
14
    event_service as order_event_service,
15
    sequence_service as order_sequence_service,
16
    service as order_service,
17
)
18 1
from .....services.shop.order.email import service as order_email_service
19 1
from .....services.shop.order.export import service as order_export_service
20 1
from .....services.shop.order.transfer.models import PaymentMethod, PaymentState
21 1
from .....services.shop.shop import service as shop_service
22 1
from .....services.ticketing import ticket_service
23 1
from .....services.user import service as user_service
24 1
from .....signals import shop as shop_signals
25 1
from .....util.authorization import register_permission_enum
26 1
from .....util.framework.blueprint import create_blueprint
27 1
from .....util.framework.flash import flash_error, flash_notice, flash_success
28 1
from .....util.framework.templating import templated
29 1
from .....util.views import permission_required, redirect_to, respond_no_content
30
31 1
from ..shop.authorization import ShopPermission
32 1
from .authorization import ShopOrderPermission
33 1
from .forms import (
34
    AddNoteForm,
35
    CancelForm,
36
    MarkAsPaidForm,
37
    OrderNumberSequenceCreateForm,
38
)
39 1
from .models import OrderStateFilter
40 1
from . import service
41
42
43 1
blueprint = create_blueprint('shop_order_admin', __name__)
44
45
46 1
register_permission_enum(ShopOrderPermission)
47
48
49 1
@blueprint.get('/for_shop/<shop_id>', defaults={'page': 1})
50 1
@blueprint.get('/for_shop/<shop_id>/pages/<int:page>')
51 1
@permission_required(ShopOrderPermission.view)
52 1
@templated
53
def index_for_shop(shop_id, page):
54
    """List orders for that shop."""
55
    shop = _get_shop_or_404(shop_id)
56
57
    brand = brand_service.get_brand(shop.brand_id)
58
59
    per_page = request.args.get('per_page', type=int, default=15)
60
61
    search_term = request.args.get('search_term', default='').strip()
62
63
    only_payment_state = request.args.get(
64
        'only_payment_state', type=PaymentState.__members__.get
65
    )
66
67
    def _str_to_bool(value):
68
        valid_values = {
69
            'true': True,
70
            'false': False,
71
        }
72
        return valid_values.get(value, False)
73
74
    only_shipped = request.args.get('only_shipped', type=_str_to_bool)
75
76
    order_state_filter = OrderStateFilter.find(only_payment_state, only_shipped)
77
78
    orders = order_service.get_orders_for_shop_paginated(
79
        shop.id,
80
        page,
81
        per_page,
82
        search_term=search_term,
83
        only_payment_state=only_payment_state,
84
        only_shipped=only_shipped,
85
    )
86
87
    orders.items = list(service.extend_order_tuples_with_orderer(orders.items))
88
89
    return {
90
        'shop': shop,
91
        'brand': brand,
92
        'search_term': search_term,
93
        'PaymentState': PaymentState,
94
        'only_payment_state': only_payment_state,
95
        'only_shipped': only_shipped,
96
        'OrderStateFilter': OrderStateFilter,
97
        'order_state_filter': order_state_filter,
98
        'orders': orders,
99
        'render_order_payment_method': _find_order_payment_method_label,
100
    }
101
102
103 1
@blueprint.get('/<uuid:order_id>')
104 1
@permission_required(ShopOrderPermission.view)
105 1
@templated
106
def view(order_id):
107
    """Show a single order."""
108
    order = order_service.find_order_with_details(order_id)
109
    if order is None:
110
        abort(404)
111
112
    placed_by = user_service.get_user(order.placed_by_id, include_avatar=True)
113
114
    shop = shop_service.get_shop(order.shop_id)
115
116
    brand = brand_service.get_brand(shop.brand_id)
117
118
    articles_by_item_number = service.get_articles_by_item_number(order)
119
120
    events = service.get_events(order.id)
121
122
    tickets = ticket_service.find_tickets_created_by_order(order.order_number)
123
124
    return {
125
        'shop': shop,
126
        'brand': brand,
127
        'order': order,
128
        'placed_by': placed_by,
129
        'articles_by_item_number': articles_by_item_number,
130
        'events': events,
131
        'PaymentMethod': PaymentMethod,
132
        'PaymentState': PaymentState,
133
        'tickets': tickets,
134
        'render_order_payment_method': _find_order_payment_method_label,
135
    }
136
137
138
# -------------------------------------------------------------------- #
139
# export
140
141
142 1
@blueprint.get('/<uuid:order_id>/export')
143 1
@permission_required(ShopOrderPermission.view)
144
def export(order_id):
145
    """Export the order as an XML document."""
146 1
    xml_export = order_export_service.export_order_as_xml(order_id)
147
148 1
    if xml_export is None:
149 1
        abort(404)
150
151 1
    return Response(
152
        xml_export['content'], content_type=xml_export['content_type']
153
    )
154
155
156
# -------------------------------------------------------------------- #
157
# notes
158
159
160 1
@blueprint.get('/<uuid:order_id>/notes/create')
161 1
@permission_required(ShopOrderPermission.update)
162 1
@templated
163 1
def add_note_form(order_id, erroneous_form=None):
164
    """Show form to add a note to the order."""
165
    order = _get_order_or_404(order_id)
166
167
    shop = shop_service.get_shop(order.shop_id)
168
169
    brand = brand_service.get_brand(shop.brand_id)
170
171
    form = erroneous_form if erroneous_form else AddNoteForm()
172
173
    return {
174
        'shop': shop,
175
        'brand': brand,
176
        'order': order,
177
        'form': form,
178
    }
179
180
181 1
@blueprint.post('/<uuid:order_id>/notes')
182 1
@permission_required(ShopOrderPermission.update)
183
def add_note(order_id):
184
    """Add a note to the order."""
185
    order = _get_order_or_404(order_id)
186
187
    form = AddNoteForm(request.form)
188
    if not form.validate():
189
        return add_note_form(order_id, form)
190
191
    text = form.text.data.strip()
192
193
    event = order_service.add_note(order.id, g.user.id, text)
194
195
    flash_success(gettext('Note has been added.'))
196
197
    return redirect_to('.view', order_id=order.id)
198
199
200
# -------------------------------------------------------------------- #
201
# flags
202
203
204 1
@blueprint.post('/<uuid:order_id>/flags/invoiced')
205 1
@permission_required(ShopOrderPermission.update)
206 1
@respond_no_content
207
def set_invoiced_flag(order_id):
208
    """Mark the order as invoiced."""
209
    order = _get_order_or_404(order_id)
210
    initiator_id = g.user.id
211
212
    order_service.set_invoiced_flag(order.id, initiator_id)
213
214
    flash_success(
215
        gettext(
216
            'Order %(order_number)s has been marked as invoiced.',
217
            order_number=order.order_number,
218
        )
219
    )
220
221
222 1
@blueprint.delete('/<uuid:order_id>/flags/invoiced')
223 1
@permission_required(ShopOrderPermission.update)
224 1
@respond_no_content
225
def unset_invoiced_flag(order_id):
226
    """Mark the order as not invoiced."""
227
    order = _get_order_or_404(order_id)
228
    initiator_id = g.user.id
229
230
    order_service.unset_invoiced_flag(order.id, initiator_id)
231
232
    flash_success(
233
        gettext(
234
            'Order %(order_number)s has been marked as not invoiced.',
235
            order_number=order.order_number,
236
        )
237
    )
238
239
240 1
@blueprint.post('/<uuid:order_id>/flags/shipped')
241 1
@permission_required(ShopOrderPermission.update)
242 1
@respond_no_content
243
def set_shipped_flag(order_id):
244
    """Mark the order as shipped."""
245
    order = _get_order_or_404(order_id)
246
    initiator_id = g.user.id
247
248
    order_service.set_shipped_flag(order.id, initiator_id)
249
250
    flash_success(
251
        gettext(
252
            'Order %(order_number)s has been marked as shipped.',
253
            order_number=order.order_number,
254
        )
255
    )
256
257
258 1
@blueprint.delete('/<uuid:order_id>/flags/shipped')
259 1
@permission_required(ShopOrderPermission.update)
260 1
@respond_no_content
261
def unset_shipped_flag(order_id):
262
    """Mark the order as not shipped."""
263
    order = _get_order_or_404(order_id)
264
    initiator_id = g.user.id
265
266
    order_service.unset_shipped_flag(order.id, initiator_id)
267
268
    flash_success(
269
        gettext(
270
            'Order %(order_number)s has been marked as not shipped.',
271
            order_number=order.order_number,
272
        )
273
    )
274
275
276
# -------------------------------------------------------------------- #
277
# cancel
278
279
280 1
@blueprint.get('/<uuid:order_id>/cancel')
281 1
@permission_required(ShopOrderPermission.cancel)
282 1
@templated
283 1
def cancel_form(order_id, erroneous_form=None):
284
    """Show form to cancel an order."""
285
    order = _get_order_or_404(order_id)
286
287
    if order.is_canceled:
288
        flash_error(
289
            gettext(
290
                'The order has already been canceled. '
291
                'The payment state cannot be changed anymore.'
292
            )
293
        )
294
        return redirect_to('.view', order_id=order.id)
295
296
    shop = shop_service.get_shop(order.shop_id)
297
298
    brand = brand_service.get_brand(shop.brand_id)
299
300
    form = erroneous_form if erroneous_form else CancelForm()
301
302
    return {
303
        'shop': shop,
304
        'brand': brand,
305
        'order': order,
306
        'form': form,
307
    }
308
309
310 1
@blueprint.post('/<uuid:order_id>/cancel')
311 1
@permission_required(ShopOrderPermission.cancel)
312
def cancel(order_id):
313
    """Set the payment state of a single order to 'canceled' and
314
    release the respective article quantities.
315
    """
316 1
    order = _get_order_or_404(order_id)
317
318 1
    form = CancelForm(request.form)
319 1
    if not form.validate():
320
        return cancel_form(order_id, form)
321
322 1
    reason = form.reason.data.strip()
323 1
    send_email = form.send_email.data
324
325 1
    try:
326 1
        event = order_service.cancel_order(order.id, g.user.id, reason)
327
    except order_service.OrderAlreadyCanceled:
328
        flash_error(
329
            gettext(
330
                'The order has already been canceled. '
331
                'The payment state cannot be changed anymore.'
332
            )
333
        )
334
        return redirect_to('.view', order_id=order.id)
335
336 1
    flash_success(
337
        gettext(
338
            'The order has been canceled and the corresponding articles '
339
            'have been made available again.'
340
        )
341
    )
342
343 1
    if send_email:
344 1
        order_email_service.send_email_for_canceled_order_to_orderer(order.id)
345
    else:
346 1
        flash_notice(gettext('No email has been sent to the orderer.'))
347
348 1
    shop_signals.order_canceled.send(None, event=event)
349
350 1
    return redirect_to('.view', order_id=order.id)
351
352
353
# -------------------------------------------------------------------- #
354
# mark as paid
355
356
357 1
@blueprint.get('/<uuid:order_id>/mark_as_paid')
358 1
@permission_required(ShopOrderPermission.mark_as_paid)
359 1
@templated
360 1
def mark_as_paid_form(order_id, erroneous_form=None):
361
    """Show form to mark an order as paid."""
362
    order = _get_order_or_404(order_id)
363
364
    if order.is_paid:
365
        flash_error(gettext('Order is already marked as paid.'))
366
        return redirect_to('.view', order_id=order.id)
367
368
    shop = shop_service.get_shop(order.shop_id)
369
370
    brand = brand_service.get_brand(shop.brand_id)
371
372
    form = erroneous_form if erroneous_form else MarkAsPaidForm()
373
374
    return {
375
        'shop': shop,
376
        'brand': brand,
377
        'order': order,
378
        'form': form,
379
    }
380
381
382 1
@blueprint.post('/<uuid:order_id>/mark_as_paid')
383 1
@permission_required(ShopOrderPermission.mark_as_paid)
384
def mark_as_paid(order_id):
385
    """Set the payment state of a single order to 'paid'."""
386 1
    order = _get_order_or_404(order_id)
387
388 1
    form = MarkAsPaidForm(request.form)
389 1
    if not form.validate():
390
        return mark_as_paid_form(order_id, form)
391
392 1
    payment_method = PaymentMethod[form.payment_method.data]
393 1
    updated_by_id = g.user.id
394
395 1
    try:
396 1
        event = order_service.mark_order_as_paid(
397
            order.id, payment_method, updated_by_id
398
        )
399
    except order_service.OrderAlreadyMarkedAsPaid:
400
        flash_error(gettext('Order is already marked as paid.'))
401
        return redirect_to('.view', order_id=order.id)
402
403 1
    flash_success(gettext('Order has been marked as paid.'))
404
405 1
    order_email_service.send_email_for_paid_order_to_orderer(order.id)
406
407 1
    shop_signals.order_paid.send(None, event=event)
408
409 1
    return redirect_to('.view', order_id=order.id)
410
411
412
# -------------------------------------------------------------------- #
413
# email
414
415
416 1
@blueprint.post('/<uuid:order_id>/resend_incoming_order_email')
417 1
@permission_required(ShopOrderPermission.update)
418 1
@respond_no_content
419
def resend_email_for_incoming_order_to_orderer(order_id):
420
    """Resend the e-mail to the orderer to confirm that the order was placed."""
421
    order = _get_order_or_404(order_id)
422
423
    initiator_id = g.user.id
424
425
    order_email_service.send_email_for_incoming_order_to_orderer(order.id)
426
427
    order_event_service.create_event(
428
        'order-placed-confirmation-email-resent',
429
        order.id,
430
        {
431
            'initiator_id': str(initiator_id),
432
        },
433
    )
434
435
    flash_success(
436
        gettext('Email confirmation for placed order has been sent again.')
437
    )
438
439
440
# -------------------------------------------------------------------- #
441
# order number sequences
442
443
444 1
@blueprint.get('/number_sequences/for_shop/<shop_id>/create')
445 1
@permission_required(ShopPermission.update)
446 1
@templated
447 1
def create_number_sequence_form(shop_id, erroneous_form=None):
448
    """Show form to create an order number sequence."""
449
    shop = _get_shop_or_404(shop_id)
450
451
    brand = brand_service.get_brand(shop.brand_id)
452
453
    form = erroneous_form if erroneous_form else OrderNumberSequenceCreateForm()
454
455
    return {
456
        'shop': shop,
457
        'brand': brand,
458
        'form': form,
459
    }
460
461
462 1
@blueprint.post('/number_sequences/for_shop/<shop_id>')
463 1
@permission_required(ShopPermission.update)
464
def create_number_sequence(shop_id):
465
    """Create an order number sequence."""
466
    shop = _get_shop_or_404(shop_id)
467
468
    form = OrderNumberSequenceCreateForm(request.form)
469
    if not form.validate():
470
        return create_number_sequence_form(shop_id, form)
471
472
    prefix = form.prefix.data.strip()
473
474
    sequence_id = order_sequence_service.create_order_number_sequence(
475
        shop.id, prefix
476
    )
477
    if sequence_id is None:
478
        flash_error(
479
            gettext(
480
                'Order number sequence could not be created. '
481
                'Is the prefix "%(prefix)s" already defined?',
482
                prefix=prefix,
483
            )
484
        )
485
        return create_number_sequence_form(shop.id, form)
486
487
    flash_success(
488
        gettext(
489
            'Order number sequence with prefix "%(prefix)s" has been created.',
490
            prefix=prefix,
491
        )
492
    )
493
    return redirect_to('.index_for_shop', shop_id=shop.id)
494
495
496
# -------------------------------------------------------------------- #
497
# helpers
498
499
500 1
def _get_shop_or_404(shop_id):
501
    shop = shop_service.find_shop(shop_id)
502
503
    if shop is None:
504
        abort(404)
505
506
    return shop
507
508
509 1
def _get_order_or_404(order_id):
510 1
    order = order_service.find_order(order_id)
511
512 1
    if order is None:
513
        abort(404)
514
515 1
    return order
516
517
518 1
def _find_order_payment_method_label(payment_method):
519
    return order_service.find_payment_method_label(payment_method)
520