create_ticket_bundle_form()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 32
Code Lines 24

Duplication

Lines 32
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 3.3272

Importance

Changes 0
Metric Value
cc 2
eloc 24
nop 2
dl 32
loc 32
ccs 4
cts 13
cp 0.3076
crap 3.3272
rs 9.304
c 0
b 0
f 0
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
            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_, 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, 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(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 = erroneous_form if erroneous_form else ArticleUpdateForm(data=data)
531
    form.tax_rate.data = article.tax_rate * TAX_RATE_DISPLAY_FACTOR
532
533
    return {
534
        'article': article,
535
        'shop': shop,
536
        'brand': brand,
537
        'form': form,
538
    }
539
540
541
@blueprint.post('/<uuid:article_id>')
542
@permission_required('shop_article.update')
543
def update(article_id):
544
    """Update an article."""
545
    article = _get_article_or_404(article_id)
546
547
    shop = shop_service.get_shop(article.shop_id)
548
549
    form = ArticleUpdateForm(request.form)
550
    if not form.validate():
551
        return update_form(article_id, form)
552
553
    name = form.name.data.strip()
554
    price = Money(form.price_amount.data, shop.currency)
555 1
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
556 1
    available_from_utc = _assemble_datetime_utc(
557 1
        form.available_from_date.data, form.available_from_time.data
558 1
    )
559
    available_until_utc = _assemble_datetime_utc(
560
        form.available_until_date.data, form.available_until_time.data
561
    )
562
    total_quantity = form.total_quantity.data
563
    max_quantity_per_order = form.max_quantity_per_order.data
564
    not_directly_orderable = form.not_directly_orderable.data
565
    separate_order_required = form.separate_order_required.data
566
567
    article = article_service.update_article(
568
        article.id,
569
        name,
570
        price,
571
        tax_rate,
572
        available_from_utc,
573
        available_until_utc,
574
        total_quantity,
575
        max_quantity_per_order,
576
        not_directly_orderable,
577
        separate_order_required,
578
    )
579
580
    flash_success(
581
        gettext('Article "%(name)s" has been updated.', name=article.name)
582
    )
583 1
    return redirect_to('.view', article_id=article.id)
584 1
585 1
586
# -------------------------------------------------------------------- #
587
# article attachments
588
589
590
@blueprint.get('/<uuid:article_id>/attachments/create')
591
@permission_required('shop_article.update')
592
@templated
593
def attachment_create_form(article_id, erroneous_form=None):
594
    """Show form to attach an article to another article."""
595
    article = _get_article_or_404(article_id)
596
597
    shop = shop_service.get_shop(article.shop_id)
598
599
    brand = brand_service.get_brand(shop.brand_id)
600
601
    attachable_articles = article_service.get_attachable_articles(article.id)
602
603
    form = (
604
        erroneous_form
605
        if erroneous_form
606
        else ArticleAttachmentCreateForm(quantity=0)
607
    )
608
    form.set_article_to_attach_choices(attachable_articles)
609
610
    return {
611
        'article': article,
612
        'shop': shop,
613
        'brand': brand,
614 1
        'form': form,
615 1
    }
616 1
617 1
618
@blueprint.post('/<uuid:article_id>/attachments')
619
@permission_required('shop_article.update')
620
def attachment_create(article_id):
621
    """Attach an article to another article."""
622
    article = _get_article_or_404(article_id)
623
624
    attachable_articles = article_service.get_attachable_articles(article.id)
625
626
    form = ArticleAttachmentCreateForm(request.form)
627
    form.set_article_to_attach_choices(attachable_articles)
628
629
    if not form.validate():
630
        return attachment_create_form(article_id, form)
631
632
    article_to_attach_id = form.article_to_attach_id.data
633
    article_to_attach = article_service.get_article(article_to_attach_id)
634
    quantity = form.quantity.data
635
636
    article_service.attach_article(article_to_attach.id, quantity, article.id)
637
638
    flash_success(
639
        gettext(
640
            'Article "%(article_to_attach_item_number)s" has been attached %(quantity)s times to article "%(article_item_number)s".',
641
            article_to_attach_item_number=article_to_attach.item_number,
642 1
            quantity=quantity,
643 1
            article_item_number=article.item_number,
644 1
        )
645 1
    )
646
    return redirect_to('.view', article_id=article.id)
647
648
649
@blueprint.delete('/attachments/<uuid:article_id>')
650
@permission_required('shop_article.update')
651
@respond_no_content
652
def attachment_remove(article_id):
653
    """Remove the attachment link from one article to another."""
654
    attached_article = article_service.find_attached_article(article_id)
655
656
    if attached_article is None:
657
        abort(404)
658
659
    article = attached_article.article
660
    attached_to_article = attached_article.attached_to_article
661
662
    article_service.unattach_article(attached_article.id)
663
664
    flash_success(
665
        gettext(
666
            'Article "%(article_item_number)s" is no longer attached to article "%(attached_to_article_item_number)s".',
667 1
            article_item_number=article.item_number,
668 1
            attached_to_article_item_number=attached_to_article.item_number,
669 1
        )
670
    )
671
672
673
# -------------------------------------------------------------------- #
674
# actions
675
676
677
@blueprint.get('/<uuid:article_id>/actions/badge_awarding/create')
678
@permission_required('shop_article.update')
679
@templated
680
def action_create_form_for_badge_awarding(article_id, erroneous_form=None):
681
    """Show form to register a badge awarding action for the article."""
682
    article = _get_article_or_404(article_id)
683
684
    shop = shop_service.get_shop(article.shop_id)
685
    brand = brand_service.get_brand(shop.brand_id)
686
687
    badges = user_badge_service.get_all_badges()
688
689
    form = (
690
        erroneous_form if erroneous_form else RegisterBadgeAwardingActionForm()
691 1
    )
692 1
    form.set_badge_choices(badges)
693 1
694 1
    return {
695
        'article': article,
696
        'shop': shop,
697
        'brand': brand,
698
        'form': form,
699
    }
700
701
702
@blueprint.post('/<uuid:article_id>/actions/badge_awarding')
703
@permission_required('shop_article.update')
704
def action_create_for_badge_awarding(article_id):
705
    """Register a badge awarding action for the article."""
706
    article = _get_article_or_404(article_id)
707
708
    badges = user_badge_service.get_all_badges()
709
710
    form = RegisterBadgeAwardingActionForm(request.form)
711
    form.set_badge_choices(badges)
712
713
    if not form.validate():
714
        return action_create_form_for_badge_awarding(article_id, form)
715
716 1
    badge_id = form.badge_id.data
717 1
    badge = user_badge_service.get_badge(badge_id)
718 1
719
    order_action_registry_service.register_badge_awarding(article.id, badge.id)
720
721
    flash_success(gettext('Action has been added.'))
722
723
    return redirect_to('.view', article_id=article.id)
724
725
726
@blueprint.get('/<uuid:article_id>/actions/tickets_creation/create')
727
@permission_required('shop_article.update')
728
@templated
729
def action_create_form_for_tickets_creation(article_id, erroneous_form=None):
730
    """Show form to register a tickets creation action for the article."""
731
    article = _get_article_or_404(article_id)
732
733
    shop = shop_service.get_shop(article.shop_id)
734
    brand = brand_service.get_brand(shop.brand_id)
735
736
    form = (
737
        erroneous_form
738
        if erroneous_form
739
        else RegisterTicketsCreationActionForm()
740
    )
741
    form.set_category_choices(brand.id)
742
743 1
    return {
744 1
        'article': article,
745 1
        'shop': shop,
746 1
        'brand': brand,
747
        'form': form,
748
    }
749
750
751 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/tickets_creation')
752
@permission_required('shop_article.update')
753
def action_create_for_tickets_creation(article_id):
754
    """Register a tickets creation action for the article."""
755
    article = _get_article_or_404(article_id)
756
757
    shop = shop_service.get_shop(article.shop_id)
758
    brand = brand_service.get_brand(shop.brand_id)
759
760
    form = RegisterTicketsCreationActionForm(request.form)
761
    form.set_category_choices(brand.id)
762
763
    if not form.validate():
764
        return action_create_form_for_tickets_creation(article_id, form)
765
766
    category_id = form.category_id.data
767
    category = ticket_category_service.get_category(category_id)
768
769
    order_action_registry_service.register_tickets_creation(
770 1
        article.id, category.id
771 1
    )
772 1
773
    flash_success(gettext('Action has been added.'))
774
775
    return redirect_to('.view', article_id=article.id)
776
777
778
@blueprint.get('/<uuid:article_id>/actions/ticket_bundles_creation/create')
779
@permission_required('shop_article.update')
780
@templated
781
def action_create_form_for_ticket_bundles_creation(
782
    article_id, erroneous_form=None
783
):
784
    """Show form to register a ticket bundles creation action for the article."""
785
    article = _get_article_or_404(article_id)
786
787
    shop = shop_service.get_shop(article.shop_id)
788
    brand = brand_service.get_brand(shop.brand_id)
789
790
    form = (
791
        erroneous_form
792
        if erroneous_form
793
        else RegisterTicketBundlesCreationActionForm()
794
    )
795
    form.set_category_choices(brand.id)
796
797
    return {
798
        'article': article,
799 1
        'shop': shop,
800 1
        'brand': brand,
801 1
        'form': form,
802 1
    }
803
804
805 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/ticket_bundles_creation')
806
@permission_required('shop_article.update')
807
def action_create_for_ticket_bundles_creation(article_id):
808
    """Register a ticket bundles creation action for the article."""
809
    article = _get_article_or_404(article_id)
810
811
    shop = shop_service.get_shop(article.shop_id)
812
    brand = brand_service.get_brand(shop.brand_id)
813
814
    form = RegisterTicketBundlesCreationActionForm(request.form)
815
    form.set_category_choices(brand.id)
816
817
    if not form.validate():
818 1
        return action_create_form_for_ticket_bundles_creation(article_id, form)
819 1
820 1
    category_id = form.category_id.data
821 1
    category = ticket_category_service.get_category(category_id)
822
823
    ticket_quantity = form.ticket_quantity.data
824
825
    order_action_registry_service.register_ticket_bundles_creation(
826
        article.id, category.id, ticket_quantity
827
    )
828
829
    flash_success(gettext('Action has been added.'))
830
831
    return redirect_to('.view', article_id=article.id)
832
833
834
@blueprint.delete('/actions/<uuid:action_id>')
835
@permission_required('shop_article.update')
836
@respond_no_content
837
def action_remove(action_id):
838 1
    """Remove the action from the article."""
839 1
    action = order_action_service.find_action(action_id)
840 1
841
    if action is None:
842
        abort(404)
843
844
    order_action_service.delete_action(action.id)
845
846
    flash_success(gettext('Action has been removed.'))
847
848
849
# -------------------------------------------------------------------- #
850
# article number sequences
851
852
853
@blueprint.get('/number_sequences/for_shop/<shop_id>/create')
854
@permission_required('shop_article.create')
855
@templated
856
def create_number_sequence_form(shop_id, erroneous_form=None):
857
    """Show form to create an article number sequence."""
858
    shop = _get_shop_or_404(shop_id)
859
860
    brand = brand_service.get_brand(shop.brand_id)
861
862
    form = (
863
        erroneous_form if erroneous_form else ArticleNumberSequenceCreateForm()
864
    )
865
866
    return {
867
        'shop': shop,
868
        'brand': brand,
869
        'form': form,
870
    }
871
872
873 View Code Duplication
@blueprint.post('/number_sequences/for_shop/<shop_id>')
874
@permission_required('shop_article.create')
875
def create_number_sequence(shop_id):
876 1
    """Create an article number sequence."""
877
    shop = _get_shop_or_404(shop_id)
878
879
    form = ArticleNumberSequenceCreateForm(request.form)
880
    if not form.validate():
881
        return create_number_sequence_form(shop_id, form)
882
883
    prefix = form.prefix.data.strip()
884
885 1
    creation_result = article_sequence_service.create_article_number_sequence(
886
        shop.id, prefix
887
    )
888
    if creation_result.is_err():
889
        flash_error(
890
            gettext(
891
                'Article number sequence could not be created. '
892
                'Is prefix "%(prefix)s" already defined?',
893
                prefix=prefix,
894 1
            )
895
        )
896
        return create_number_sequence_form(shop.id, form)
897
898
    flash_success(
899
        gettext(
900
            'Article number sequence with prefix "%(prefix)s" has been created.',
901 1
            prefix=prefix,
902
        )
903
    )
904
    return redirect_to('.index_for_shop', shop_id=shop.id)
905
906
907
# -------------------------------------------------------------------- #
908
# helpers
909
910 1
911
def _get_shop_or_404(shop_id):
912
    shop = shop_service.find_shop(shop_id)
913
914
    if shop is None:
915
        abort(404)
916
917
    return shop
918
919
920
def _get_article_or_404(article_id) -> Article:
921
    article = article_service.find_article(article_id)
922
923
    if article is None:
924
        abort(404)
925
926
    return article
927
928
929
def _get_article_type_or_400(value: str) -> ArticleType:
930
    try:
931
        return ArticleType[value]
932
    except KeyError:
933
        abort(400, f'Unknown article type "{value}"')
934
935
936
def _get_active_article_number_sequences_for_shop(
937
    shop_id: ShopID,
938
) -> list[ArticleNumberSequence]:
939
    sequences = article_sequence_service.get_article_number_sequences_for_shop(
940
        shop_id
941
    )
942
    return [sequence for sequence in sequences if not sequence.archived]
943
944
945
def _assemble_datetime_utc(d: date, t: time) -> datetime | None:
946
    if not d or not t:
947
        return None
948
949
    local_dt = datetime.combine(d, t)
950
    return to_utc(local_dt)
951