_get_article_or_404()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nop 1
ccs 0
cts 0
cp 0
crap 6
1
"""
2
byceps.blueprints.admin.shop.article.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2014-2024 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
import dataclasses
10
from datetime import date, datetime, time
11 1
from decimal import Decimal
12 1
13 1
from flask import abort, request
14
from flask_babel import gettext, to_user_timezone, to_utc
15 1
from moneyed import Money
16 1
17 1
from byceps.services.brand import brand_service
18
from byceps.services.party import party_service
19 1
from byceps.services.shop.article import (
20 1
    article_sequence_service,
21 1
    article_service,
22
)
23
from byceps.services.shop.article.models import (
24
    Article,
25 1
    ArticleNumber,
26
    ArticleNumberSequence,
27
    ArticleType,
28
    get_article_type_label,
29
)
30
from byceps.services.shop.order import (
31 1
    order_action_registry_service,
32
    order_action_service,
33
    ordered_articles_service,
34
)
35
from byceps.services.shop.order.models.order import Order, PaymentState
36 1
from byceps.services.shop.shop import shop_service
37 1
from byceps.services.shop.shop.models import ShopID
38 1
from byceps.services.ticketing import ticket_category_service
39 1
from byceps.services.user_badge import user_badge_service
40 1
from byceps.util.framework.blueprint import create_blueprint
41 1
from byceps.util.framework.flash import flash_error, flash_success
42 1
from byceps.util.framework.templating import templated
43 1
from byceps.util.views import (
44 1
    permission_required,
45
    redirect_to,
46
    respond_no_content,
47
)
48
49
from .forms import (
50 1
    ArticleAttachmentCreateForm,
51
    ArticleCreateForm,
52
    ArticleNumberSequenceCreateForm,
53
    ArticleUpdateForm,
54
    RegisterBadgeAwardingActionForm,
55
    RegisterTicketBundlesCreationActionForm,
56
    RegisterTicketsCreationActionForm,
57
    TicketArticleCreateForm,
58
    TicketBundleArticleCreateForm,
59
)
60
61
62
blueprint = create_blueprint('shop_article_admin', __name__)
63 1
64
65
TAX_RATE_DISPLAY_FACTOR = Decimal('100')
66 1
67
68
@blueprint.get('/for_shop/<shop_id>', defaults={'page': 1})
69 1
@blueprint.get('/for_shop/<shop_id>/pages/<int:page>')
70 1
@permission_required('shop_article.view')
71 1
@templated
72 1
def index_for_shop(shop_id, page):
73 1
    """List articles for that shop."""
74
    shop = _get_shop_or_404(shop_id)
75
76
    brand = brand_service.get_brand(shop.brand_id)
77
78
    per_page = request.args.get('per_page', type=int, default=15)
79
80
    search_term = request.args.get('search_term', default='').strip()
81
82
    articles = article_service.get_articles_for_shop_paginated(
83
        shop.id,
84
        page,
85
        per_page,
86
        search_term=search_term,
87
    )
88
89
    # Inherit order of enum members.
90
    article_type_labels_by_type = {
91
        type_: get_article_type_label(type_) for type_ in ArticleType
92
    }
93
94
    totals_by_article_number = {
95
        article.item_number: ordered_articles_service.count_ordered_articles(
96
            article.id
97
        )
98
        for article in articles.items
99
    }
100
101
    return {
102
        'shop': shop,
103
        'brand': brand,
104
        'articles': articles,
105
        'article_type_labels_by_type': article_type_labels_by_type,
106
        'totals_by_article_number': totals_by_article_number,
107
        'PaymentState': PaymentState,
108
        'per_page': per_page,
109
        'search_term': search_term,
110
    }
111
112
113
@blueprint.get('/<uuid:article_id>')
114 1
@permission_required('shop_article.view')
115 1
@templated
116 1
def view(article_id):
117 1
    """Show a single article."""
118
    article = article_service.find_article_with_details(article_id)
119
    if article is None:
120
        abort(404)
121
122
    shop = shop_service.get_shop(article.shop_id)
123
124
    brand = brand_service.get_brand(shop.brand_id)
125
126
    type_label = get_article_type_label(article.type_)
127
128
    if article.type_ in (ArticleType.ticket, ArticleType.ticket_bundle):
129
        ticket_category = ticket_category_service.find_category(
130
            article.type_params['ticket_category_id']
131
        )
132
        if ticket_category is not None:
133
            ticket_party = party_service.get_party(ticket_category.party_id)
134
        else:
135
            ticket_party = None
136
    else:
137
        ticket_party = None
138
        ticket_category = None
139
140
    totals = ordered_articles_service.count_ordered_articles(article.id)
141
142
    actions = order_action_service.get_actions_for_article(article.id)
143
    actions.sort(key=lambda a: a.payment_state.name, reverse=True)
144
145
    return {
146
        'article': article,
147
        'shop': shop,
148
        'brand': brand,
149
        'type_label': type_label,
150
        'ticket_category': ticket_category,
151
        'ticket_party': ticket_party,
152
        'totals': totals,
153
        'PaymentState': PaymentState,
154
        'actions': actions,
155
    }
156
157
158 View Code Duplication
@blueprint.get('/<uuid:article_id>/orders')
159 1
@permission_required('shop_article.view')
160 1
@templated
161 1
def view_orders(article_id):
162 1
    """List the orders for this article, and the corresponding quantities."""
163
    article = _get_article_or_404(article_id)
164
165
    shop = shop_service.get_shop(article.shop_id)
166
    brand = brand_service.get_brand(shop.brand_id)
167
168
    orders = ordered_articles_service.get_orders_including_article(article.id)
169
170
    def transform(order: Order) -> tuple[Order, int]:
171
        quantity = sum(
172
            line_item.quantity
173
            for line_item in order.line_items
174
            if line_item.article_id == article.id
175
        )
176
177
        return order, quantity
178
179
    orders_with_quantities = list(map(transform, orders))
180
181
    quantity_total = sum(quantity for _, quantity in orders_with_quantities)
182
183
    return {
184
        'article': article,
185
        'shop': shop,
186
        'brand': brand,
187
        'quantity_total': quantity_total,
188
        'orders_with_quantities': orders_with_quantities,
189
        'now': datetime.utcnow(),
190
    }
191
192
193 View Code Duplication
@blueprint.get('/<uuid:article_id>/purchases')
194 1
@permission_required('shop_article.view')
195 1
@templated
196 1
def view_purchases(article_id):
197 1
    """List the purchases for this article, and the corresponding quantities."""
198
    article = _get_article_or_404(article_id)
199
200
    shop = shop_service.get_shop(article.shop_id)
201
    brand = brand_service.get_brand(shop.brand_id)
202
203
    orders = ordered_articles_service.get_orders_including_article(
204
        article.id, only_payment_state=PaymentState.paid
205
    )
206
207
    def transform(order: Order) -> tuple[Order, int]:
208
        quantity = sum(
209
            line_item.quantity
210
            for line_item in order.line_items
211
            if line_item.article_id == article.id
212
        )
213
214
        return order, quantity
215
216
    orders_with_quantities = list(map(transform, orders))
217
218
    quantity_total = sum(quantity for _, quantity in orders_with_quantities)
219
220
    return {
221
        'article': article,
222
        'shop': shop,
223
        'brand': brand,
224
        'quantity_total': quantity_total,
225
        'orders_with_quantities': orders_with_quantities,
226
        'now': datetime.utcnow(),
227
    }
228
229
230
# -------------------------------------------------------------------- #
231
# create
232
233
234 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/<type_name>')
235 1
@permission_required('shop_article.create')
236 1
@templated
237 1
def create_form(shop_id, type_name, erroneous_form=None):
238 1
    """Show form to create an article."""
239
    shop = _get_shop_or_404(shop_id)
240
    type_ = _get_article_type_or_400(type_name)
241
242
    brand = brand_service.get_brand(shop.brand_id)
243
244
    article_number_sequences = _get_active_article_number_sequences_for_shop(
245
        shop.id
246
    )
247
    article_number_sequence_available = bool(article_number_sequences)
248
249
    form = (
250
        erroneous_form
251
        if erroneous_form
252
        else ArticleCreateForm(
253
            shop.id, price_amount=Decimal('0.0'), tax_rate=Decimal('19.0')
254
        )
255
    )
256
    form.set_article_number_sequence_choices(article_number_sequences)
257
258
    return {
259
        'shop': shop,
260
        'brand': brand,
261
        'article_type_name': type_.name,
262
        'article_type_label': get_article_type_label(type_),
263
        'article_number_sequence_available': article_number_sequence_available,
264
        'form': form,
265
    }
266
267
268 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/ticket')
269 1
@permission_required('shop_article.create')
270 1
@templated
271 1
def create_ticket_form(shop_id, erroneous_form=None):
272 1
    """Show form to create a ticket article."""
273
    shop = _get_shop_or_404(shop_id)
274
    type_ = ArticleType.ticket
275
276
    brand = brand_service.get_brand(shop.brand_id)
277
278
    article_number_sequences = _get_active_article_number_sequences_for_shop(
279
        shop.id
280
    )
281
    article_number_sequence_available = bool(article_number_sequences)
282
283
    form = (
284
        erroneous_form
285
        if erroneous_form
286
        else TicketArticleCreateForm(
287
            price_amount=Decimal('0.0'), tax_rate=Decimal('19.0')
288
        )
289
    )
290
    form.set_article_number_sequence_choices(article_number_sequences)
291
    form.set_ticket_category_choices(brand.id)
292
293
    return {
294
        'shop': shop,
295
        'brand': brand,
296
        'article_type_name': type_.name,
297
        'article_type_label': get_article_type_label(type_),
298
        'article_number_sequence_available': article_number_sequence_available,
299
        'form': form,
300
    }
301
302
303 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/ticket_bundle')
304 1
@permission_required('shop_article.create')
305 1
@templated
306 1
def create_ticket_bundle_form(shop_id, erroneous_form=None):
307 1
    """Show form to create a ticket bundle article."""
308
    shop = _get_shop_or_404(shop_id)
309
    type_ = ArticleType.ticket_bundle
310
311
    brand = brand_service.get_brand(shop.brand_id)
312
313
    article_number_sequences = _get_active_article_number_sequences_for_shop(
314
        shop.id
315
    )
316
    article_number_sequence_available = bool(article_number_sequences)
317
318
    form = (
319
        erroneous_form
320
        if erroneous_form
321
        else TicketBundleArticleCreateForm(
322
            price_amount=Decimal('0.0'), tax_rate=Decimal('19.0')
323
        )
324
    )
325
    form.set_article_number_sequence_choices(article_number_sequences)
326
    form.set_ticket_category_choices(brand.id)
327
328
    return {
329
        'shop': shop,
330
        'brand': brand,
331
        'article_type_name': type_.name,
332
        'article_type_label': get_article_type_label(type_),
333
        'article_number_sequence_available': article_number_sequence_available,
334
        'form': form,
335
    }
336
337
338
@blueprint.post('/for_shop/<shop_id>/<type_name>')
339 1
@permission_required('shop_article.create')
340 1
def create(shop_id, type_name):
341 1
    """Create an article."""
342
    shop = _get_shop_or_404(shop_id)
343
    type_ = _get_article_type_or_400(type_name)
344
345
    form = _get_create_form(type_, shop.id, request)
346
347
    article_number_sequences = _get_active_article_number_sequences_for_shop(
348
        shop.id
349
    )
350
    if not article_number_sequences:
351
        flash_error(
352
            gettext('No article number sequences are defined for this shop.')
353
        )
354
        return create_form(shop_id, type_.name, form)
355
356
    form.set_article_number_sequence_choices(article_number_sequences)
357
    if type_ in (ArticleType.ticket, ArticleType.ticket_bundle):
358
        form.set_ticket_category_choices(shop.brand_id)
359
360
    if not form.validate():
361
        return create_form(shop_id, type_.name, form)
362
363
    article_number_sequence_id = form.article_number_sequence_id.data
364
    if not article_number_sequence_id:
365
        flash_error(gettext('No valid article number sequence was specified.'))
366
        return create_form(shop_id, type_.name, form)
367
368
    article_number_sequence = (
369
        article_sequence_service.get_article_number_sequence(
370
            article_number_sequence_id
371
        )
372
    )
373
    if article_number_sequence.shop_id != shop.id:
374
        flash_error(gettext('No valid article number sequence was specified.'))
375
        return create_form(shop_id, type_.name, form)
376
377
    item_number = _get_item_number(article_number_sequence.id)
378
379
    name = form.name.data.strip()
380
    price = Money(form.price_amount.data, shop.currency)
381
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
382
    available_from_utc = _assemble_datetime_utc(
383
        form.available_from_date.data, form.available_from_time.data
384
    )
385
    available_until_utc = _assemble_datetime_utc(
386
        form.available_until_date.data, form.available_until_time.data
387
    )
388
    total_quantity = form.total_quantity.data
389
    max_quantity_per_order = form.max_quantity_per_order.data
390
    not_directly_orderable = form.not_directly_orderable.data
391
    separate_order_required = form.separate_order_required.data
392
393
    article = _create_article(
394
        type_,
395
        shop.id,
396
        item_number,
397
        name,
398
        price,
399
        tax_rate,
400
        total_quantity,
401
        max_quantity_per_order,
402
        form,
403
        available_from_utc,
404
        available_until_utc,
405
        not_directly_orderable,
406
        separate_order_required,
407
    )
408
409
    flash_success(
410
        gettext(
411
            'Article "%(item_number)s" has been created.',
412
            item_number=article.item_number,
413
        )
414
    )
415
    return redirect_to('.view', article_id=article.id)
416
417
418
def _get_create_form(type_: ArticleType, shop_id: ShopID, request):
419
    if type_ == ArticleType.ticket:
420
        return TicketArticleCreateForm(request.form)
421
    elif type_ == ArticleType.ticket_bundle:
422
        return TicketBundleArticleCreateForm(request.form)
423
    else:
424
        return ArticleCreateForm(shop_id, request.form)
425
426
427
def _get_item_number(article_number_sequence_id) -> ArticleNumber:
428
    generation_result = article_sequence_service.generate_article_number(
429
        article_number_sequence_id
430
    )
431
432
    if generation_result.is_err():
433
        abort(500, generation_result.unwrap_err())
434
435
    return generation_result.unwrap()
436
437
438
def _create_article(
439
    type_: ArticleType,
440
    shop_id: ShopID,
441
    item_number: ArticleNumber,
442
    name: str,
443
    price: Money,
444
    tax_rate: Decimal,
445
    total_quantity: int,
446
    max_quantity_per_order: int,
447
    form,
448
    available_from: datetime | None = None,
449
    available_until: datetime | None = None,
450
    not_directly_orderable: bool = False,
451
    separate_order_required: bool = False,
452
):
453
    if type_ == ArticleType.ticket:
454
        return article_service.create_ticket_article(
455
            shop_id,
456
            item_number,
457
            name,
458
            price,
459
            tax_rate,
460
            total_quantity,
461
            max_quantity_per_order,
462
            form.ticket_category_id.data,
463
            available_from=available_from,
464
            available_until=available_until,
465
            not_directly_orderable=not_directly_orderable,
466
            separate_order_required=separate_order_required,
467
        )
468
    elif type_ == ArticleType.ticket_bundle:
469
        return article_service.create_ticket_bundle_article(
470 1
            shop_id,
471 1
            item_number,
472 1
            name,
473 1
            price,
474
            tax_rate,
475
            total_quantity,
476
            max_quantity_per_order,
477
            form.ticket_category_id.data,
478
            form.ticket_quantity.data,
479
            available_from=available_from,
480
            available_until=available_until,
481
            not_directly_orderable=not_directly_orderable,
482
            separate_order_required=separate_order_required,
483
        )
484
    else:
485
        processing_required = type_ == ArticleType.physical
486
487
        return article_service.create_article(
488
            shop_id,
489
            item_number,
490
            type_,
491
            name,
492
            price,
493
            tax_rate,
494
            total_quantity,
495
            max_quantity_per_order,
496
            processing_required,
497
            available_from=available_from,
498
            available_until=available_until,
499
            not_directly_orderable=not_directly_orderable,
500
            separate_order_required=separate_order_required,
501
        )
502
503 1
504 1
# -------------------------------------------------------------------- #
505 1
# update
506
507
508
@blueprint.get('/<uuid:article_id>/update')
509
@permission_required('shop_article.update')
510
@templated
511
def update_form(article_id, erroneous_form=None):
512
    """Show form to update an article."""
513
    article = _get_article_or_404(article_id)
514
515
    shop = shop_service.get_shop(article.shop_id)
516
517
    brand = brand_service.get_brand(shop.brand_id)
518
519
    data = dataclasses.asdict(article)
520
    data['price_amount'] = article.price.amount
521
    if article.available_from:
522
        available_from_local = to_user_timezone(article.available_from)
523
        data['available_from_date'] = available_from_local.date()
524
        data['available_from_time'] = available_from_local.time()
525
    if article.available_until:
526
        available_until_local = to_user_timezone(article.available_until)
527
        data['available_until_date'] = available_until_local.date()
528
        data['available_until_time'] = available_until_local.time()
529
530
    form = (
531
        erroneous_form
532
        if erroneous_form
533
        else ArticleUpdateForm(shop.id, article.name, data=data)
534
    )
535
    form.tax_rate.data = article.tax_rate * TAX_RATE_DISPLAY_FACTOR
536
537
    return {
538
        'article': article,
539
        'shop': shop,
540
        'brand': brand,
541
        'form': form,
542
    }
543
544
545
@blueprint.post('/<uuid:article_id>')
546
@permission_required('shop_article.update')
547
def update(article_id):
548
    """Update an article."""
549
    article = _get_article_or_404(article_id)
550
551
    shop = shop_service.get_shop(article.shop_id)
552
553
    form = ArticleUpdateForm(shop.id, article.name, request.form)
554
    if not form.validate():
555 1
        return update_form(article_id, form)
556 1
557 1
    name = form.name.data.strip()
558 1
    price = Money(form.price_amount.data, shop.currency)
559
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
560
    available_from_utc = _assemble_datetime_utc(
561
        form.available_from_date.data, form.available_from_time.data
562
    )
563
    available_until_utc = _assemble_datetime_utc(
564
        form.available_until_date.data, form.available_until_time.data
565
    )
566
    total_quantity = form.total_quantity.data
567
    max_quantity_per_order = form.max_quantity_per_order.data
568
    not_directly_orderable = form.not_directly_orderable.data
569
    separate_order_required = form.separate_order_required.data
570
571
    article = article_service.update_article(
572
        article.id,
573
        name,
574
        price,
575
        tax_rate,
576
        available_from_utc,
577
        available_until_utc,
578
        total_quantity,
579
        max_quantity_per_order,
580
        not_directly_orderable,
581
        separate_order_required,
582
    )
583 1
584 1
    flash_success(
585 1
        gettext('Article "%(name)s" has been updated.', name=article.name)
586
    )
587
    return redirect_to('.view', article_id=article.id)
588
589
590
# -------------------------------------------------------------------- #
591
# article attachments
592
593
594
@blueprint.get('/<uuid:article_id>/attachments/create')
595
@permission_required('shop_article.update')
596
@templated
597
def attachment_create_form(article_id, erroneous_form=None):
598
    """Show form to attach an article to another article."""
599
    article = _get_article_or_404(article_id)
600
601
    shop = shop_service.get_shop(article.shop_id)
602
603
    brand = brand_service.get_brand(shop.brand_id)
604
605
    attachable_articles = article_service.get_attachable_articles(article.id)
606
607
    form = (
608
        erroneous_form
609
        if erroneous_form
610
        else ArticleAttachmentCreateForm(quantity=0)
611
    )
612
    form.set_article_to_attach_choices(attachable_articles)
613
614 1
    return {
615 1
        'article': article,
616 1
        'shop': shop,
617 1
        'brand': brand,
618
        'form': form,
619
    }
620
621
622
@blueprint.post('/<uuid:article_id>/attachments')
623
@permission_required('shop_article.update')
624
def attachment_create(article_id):
625
    """Attach an article to another article."""
626
    article = _get_article_or_404(article_id)
627
628
    attachable_articles = article_service.get_attachable_articles(article.id)
629
630
    form = ArticleAttachmentCreateForm(request.form)
631
    form.set_article_to_attach_choices(attachable_articles)
632
633
    if not form.validate():
634
        return attachment_create_form(article_id, form)
635
636
    article_to_attach_id = form.article_to_attach_id.data
637
    article_to_attach = article_service.get_article(article_to_attach_id)
638
    quantity = form.quantity.data
639
640
    article_service.attach_article(article_to_attach.id, quantity, article.id)
641
642 1
    flash_success(
643 1
        gettext(
644 1
            'Article "%(article_to_attach_item_number)s" has been attached %(quantity)s times to article "%(article_item_number)s".',
645 1
            article_to_attach_item_number=article_to_attach.item_number,
646
            quantity=quantity,
647
            article_item_number=article.item_number,
648
        )
649
    )
650
    return redirect_to('.view', article_id=article.id)
651
652
653
@blueprint.delete('/attachments/<uuid:article_id>')
654
@permission_required('shop_article.update')
655
@respond_no_content
656
def attachment_remove(article_id):
657
    """Remove the attachment link from one article to another."""
658
    attached_article = article_service.find_attached_article(article_id)
659
660
    if attached_article is None:
661
        abort(404)
662
663
    article = attached_article.article
664
    attached_to_article = attached_article.attached_to_article
665
666
    article_service.unattach_article(attached_article.id)
667 1
668 1
    flash_success(
669 1
        gettext(
670
            'Article "%(article_item_number)s" is no longer attached to article "%(attached_to_article_item_number)s".',
671
            article_item_number=article.item_number,
672
            attached_to_article_item_number=attached_to_article.item_number,
673
        )
674
    )
675
676
677
# -------------------------------------------------------------------- #
678
# actions
679
680
681
@blueprint.get('/<uuid:article_id>/actions/badge_awarding/create')
682
@permission_required('shop_article.update')
683
@templated
684
def action_create_form_for_badge_awarding(article_id, erroneous_form=None):
685
    """Show form to register a badge awarding action for the article."""
686
    article = _get_article_or_404(article_id)
687
688
    shop = shop_service.get_shop(article.shop_id)
689
    brand = brand_service.get_brand(shop.brand_id)
690
691 1
    badges = user_badge_service.get_all_badges()
692 1
693 1
    form = (
694 1
        erroneous_form if erroneous_form else RegisterBadgeAwardingActionForm()
695
    )
696
    form.set_badge_choices(badges)
697
698
    return {
699
        'article': article,
700
        'shop': shop,
701
        'brand': brand,
702
        'form': form,
703
    }
704
705
706
@blueprint.post('/<uuid:article_id>/actions/badge_awarding')
707
@permission_required('shop_article.update')
708
def action_create_for_badge_awarding(article_id):
709
    """Register a badge awarding action for the article."""
710
    article = _get_article_or_404(article_id)
711
712
    badges = user_badge_service.get_all_badges()
713
714
    form = RegisterBadgeAwardingActionForm(request.form)
715
    form.set_badge_choices(badges)
716 1
717 1
    if not form.validate():
718 1
        return action_create_form_for_badge_awarding(article_id, form)
719
720
    badge_id = form.badge_id.data
721
    badge = user_badge_service.get_badge(badge_id)
722
723
    order_action_registry_service.register_badge_awarding(article.id, badge.id)
724
725
    flash_success(gettext('Action has been added.'))
726
727
    return redirect_to('.view', article_id=article.id)
728
729
730
@blueprint.get('/<uuid:article_id>/actions/tickets_creation/create')
731
@permission_required('shop_article.update')
732
@templated
733
def action_create_form_for_tickets_creation(article_id, erroneous_form=None):
734
    """Show form to register a tickets creation action for the article."""
735
    article = _get_article_or_404(article_id)
736
737
    shop = shop_service.get_shop(article.shop_id)
738
    brand = brand_service.get_brand(shop.brand_id)
739
740
    form = (
741
        erroneous_form
742
        if erroneous_form
743 1
        else RegisterTicketsCreationActionForm()
744 1
    )
745 1
    form.set_category_choices(brand.id)
746 1
747
    return {
748
        'article': article,
749
        'shop': shop,
750
        'brand': brand,
751
        'form': form,
752
    }
753
754
755 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/tickets_creation')
756
@permission_required('shop_article.update')
757
def action_create_for_tickets_creation(article_id):
758
    """Register a tickets creation action for the article."""
759
    article = _get_article_or_404(article_id)
760
761
    shop = shop_service.get_shop(article.shop_id)
762
    brand = brand_service.get_brand(shop.brand_id)
763
764
    form = RegisterTicketsCreationActionForm(request.form)
765
    form.set_category_choices(brand.id)
766
767
    if not form.validate():
768
        return action_create_form_for_tickets_creation(article_id, form)
769
770 1
    category_id = form.category_id.data
771 1
    category = ticket_category_service.get_category(category_id)
772 1
773
    order_action_registry_service.register_tickets_creation(
774
        article.id, category.id
775
    )
776
777
    flash_success(gettext('Action has been added.'))
778
779
    return redirect_to('.view', article_id=article.id)
780
781
782
@blueprint.get('/<uuid:article_id>/actions/ticket_bundles_creation/create')
783
@permission_required('shop_article.update')
784
@templated
785
def action_create_form_for_ticket_bundles_creation(
786
    article_id, erroneous_form=None
787
):
788
    """Show form to register a ticket bundles creation action for the article."""
789
    article = _get_article_or_404(article_id)
790
791
    shop = shop_service.get_shop(article.shop_id)
792
    brand = brand_service.get_brand(shop.brand_id)
793
794
    form = (
795
        erroneous_form
796
        if erroneous_form
797
        else RegisterTicketBundlesCreationActionForm()
798
    )
799 1
    form.set_category_choices(brand.id)
800 1
801 1
    return {
802 1
        'article': article,
803
        'shop': shop,
804
        'brand': brand,
805
        'form': form,
806
    }
807
808
809 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/ticket_bundles_creation')
810
@permission_required('shop_article.update')
811
def action_create_for_ticket_bundles_creation(article_id):
812
    """Register a ticket bundles creation action for the article."""
813
    article = _get_article_or_404(article_id)
814
815
    shop = shop_service.get_shop(article.shop_id)
816
    brand = brand_service.get_brand(shop.brand_id)
817
818 1
    form = RegisterTicketBundlesCreationActionForm(request.form)
819 1
    form.set_category_choices(brand.id)
820 1
821 1
    if not form.validate():
822
        return action_create_form_for_ticket_bundles_creation(article_id, form)
823
824
    category_id = form.category_id.data
825
    category = ticket_category_service.get_category(category_id)
826
827
    ticket_quantity = form.ticket_quantity.data
828
829
    order_action_registry_service.register_ticket_bundles_creation(
830
        article.id, category.id, ticket_quantity
831
    )
832
833
    flash_success(gettext('Action has been added.'))
834
835
    return redirect_to('.view', article_id=article.id)
836
837
838 1
@blueprint.delete('/actions/<uuid:action_id>')
839 1
@permission_required('shop_article.update')
840 1
@respond_no_content
841
def action_remove(action_id):
842
    """Remove the action from the article."""
843
    action = order_action_service.find_action(action_id)
844
845
    if action is None:
846
        abort(404)
847
848
    order_action_service.delete_action(action.id)
849
850
    flash_success(gettext('Action has been removed.'))
851
852
853
# -------------------------------------------------------------------- #
854
# article number sequences
855
856
857
@blueprint.get('/number_sequences/for_shop/<shop_id>/create')
858
@permission_required('shop_article.create')
859
@templated
860
def create_number_sequence_form(shop_id, erroneous_form=None):
861
    """Show form to create an article number sequence."""
862
    shop = _get_shop_or_404(shop_id)
863
864
    brand = brand_service.get_brand(shop.brand_id)
865
866
    form = (
867
        erroneous_form if erroneous_form else ArticleNumberSequenceCreateForm()
868
    )
869
870
    return {
871
        'shop': shop,
872
        'brand': brand,
873
        'form': form,
874
    }
875
876 1
877 View Code Duplication
@blueprint.post('/number_sequences/for_shop/<shop_id>')
878
@permission_required('shop_article.create')
879
def create_number_sequence(shop_id):
880
    """Create an article number sequence."""
881
    shop = _get_shop_or_404(shop_id)
882
883
    form = ArticleNumberSequenceCreateForm(request.form)
884
    if not form.validate():
885 1
        return create_number_sequence_form(shop_id, form)
886
887
    prefix = form.prefix.data.strip()
888
889
    creation_result = article_sequence_service.create_article_number_sequence(
890
        shop.id, prefix
891
    )
892
    if creation_result.is_err():
893
        flash_error(
894 1
            gettext(
895
                'Article number sequence could not be created. '
896
                'Is prefix "%(prefix)s" already defined?',
897
                prefix=prefix,
898
            )
899
        )
900
        return create_number_sequence_form(shop.id, form)
901 1
902
    flash_success(
903
        gettext(
904
            'Article number sequence with prefix "%(prefix)s" has been created.',
905
            prefix=prefix,
906
        )
907
    )
908
    return redirect_to('.index_for_shop', shop_id=shop.id)
909
910 1
911
# -------------------------------------------------------------------- #
912
# helpers
913
914
915
def _get_shop_or_404(shop_id):
916
    shop = shop_service.find_shop(shop_id)
917
918
    if shop is None:
919
        abort(404)
920
921
    return shop
922
923
924
def _get_article_or_404(article_id) -> Article:
925
    article = article_service.find_article(article_id)
926
927
    if article is None:
928
        abort(404)
929
930
    return article
931
932
933
def _get_article_type_or_400(value: str) -> ArticleType:
934
    try:
935
        return ArticleType[value]
936
    except KeyError:
937
        abort(400, f'Unknown article type "{value}"')
938
939
940
def _get_active_article_number_sequences_for_shop(
941
    shop_id: ShopID,
942
) -> list[ArticleNumberSequence]:
943
    sequences = article_sequence_service.get_article_number_sequences_for_shop(
944
        shop_id
945
    )
946
    return [sequence for sequence in sequences if not sequence.archived]
947
948
949
def _assemble_datetime_utc(d: date, t: time) -> datetime | None:
950
    if not d or not t:
951
        return None
952
953
    local_dt = datetime.combine(d, t)
954
    return to_utc(local_dt)
955