Completed
Branch main (de2ccb)
by Jochen
108:27 queued 104:49
created

create_number_sequence()   A

Complexity

Conditions 3

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 8.4533

Importance

Changes 0
Metric Value
cc 3
eloc 17
nop 1
dl 0
loc 26
ccs 2
cts 13
cp 0.1538
crap 8.4533
rs 9.55
c 0
b 0
f 0
1
"""
2
byceps.blueprints.admin.shop.article.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2020 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9 1
from datetime import datetime
10 1
from decimal import Decimal
11
12 1
from flask import abort, request
13
14 1
from .....services.shop.article import (
15
    sequence_service as article_sequence_service,
16
    service as article_service,
17
)
18 1
from .....services.shop.order import (
19
    action_service as order_action_service,
20
    ordered_articles_service,
21
    service as order_service,
22
)
23 1
from .....services.shop.order.transfer.models import PaymentState
24 1
from .....services.shop.shop import service as shop_service
25 1
from .....services.user import service as user_service
26 1
from .....util.framework.blueprint import create_blueprint
27 1
from .....util.framework.flash import flash_error, flash_success
28 1
from .....util.framework.templating import templated
29 1
from .....util.templatefilters import local_tz_to_utc, utc_to_local_tz
30 1
from .....util.views import redirect_to, respond_no_content
31
32 1
from ....common.authorization.decorators import permission_required
33 1
from ....common.authorization.registry import permission_registry
34
35 1
from .authorization import ShopArticlePermission
36 1
from .forms import (
37
    ArticleCreateForm,
38
    ArticleUpdateForm,
39
    ArticleAttachmentCreateForm,
40
    ArticleNumberSequenceCreateForm,
41
)
42
43
44 1
blueprint = create_blueprint('shop_article_admin', __name__)
45
46
47 1
permission_registry.register_enum(ShopArticlePermission)
48
49
50 1
TAX_RATE_DISPLAY_FACTOR = Decimal('100')
51
52
53 1
@blueprint.route('/for_shop/<shop_id>', defaults={'page': 1})
54 1
@blueprint.route('/for_shop/<shop_id>/pages/<int:page>')
55 1
@permission_required(ShopArticlePermission.view)
56 1
@templated
57
def index_for_shop(shop_id, page):
58
    """List articles for that shop."""
59
    shop = _get_shop_or_404(shop_id)
60
61
    per_page = request.args.get('per_page', type=int, default=15)
62
    articles = article_service.get_articles_for_shop_paginated(
63
        shop.id, page, per_page
64
    )
65
66
    return {
67
        'shop': shop,
68
        'articles': articles,
69
    }
70
71
72 1
@blueprint.route('/<uuid:article_id>')
73 1
@permission_required(ShopArticlePermission.view)
74 1
@templated
75
def view(article_id):
76
    """Show a single article."""
77
    article = article_service.find_article_with_details(article_id)
78
    if article is None:
79
        abort(404)
80
81
    shop = shop_service.get_shop(article.shop_id)
82
83
    totals = ordered_articles_service.count_ordered_articles(
84
        article.item_number
85
    )
86
87
    actions = order_action_service.get_actions_for_article(article.item_number)
88
    actions.sort(key=lambda a: a.payment_state.name, reverse=True)
89
90
    return {
91
        'article': article,
92
        'shop': shop,
93
        'totals': totals,
94
        'PaymentState': PaymentState,
95
        'actions': actions,
96
    }
97
98
99 1
@blueprint.route('/<uuid:article_id>/ordered')
100 1
@permission_required(ShopArticlePermission.view)
101 1
@templated
102
def view_ordered(article_id):
103
    """List the people that have ordered this article, and the
104
    corresponding quantities.
105
    """
106
    article = _get_article_or_404(article_id)
107
108
    shop = shop_service.get_shop(article.shop_id)
109
110
    order_items = ordered_articles_service.get_order_items_for_article(
111
        article.item_number
112
    )
113
114
    quantity_total = sum(item.quantity for item in order_items)
115
116
    order_numbers = {item.order_number for item in order_items}
117
    orders = order_service.find_orders_by_order_numbers(order_numbers)
118
    orders_by_order_numbers = {order.order_number: order for order in orders}
119
120
    user_ids = {order.placed_by_id for order in orders}
121
    users = user_service.find_users(user_ids, include_avatars=True)
122
    users_by_id = {user.id: user for user in users}
123
124
    def transform(order_item):
125
        quantity = order_item.quantity
126
        order = orders_by_order_numbers[order_item.order_number]
127
        user = users_by_id[order.placed_by_id]
128
129
        return quantity, order, user
130
131
    quantities_orders_users = list(map(transform, order_items))
132
133
    return {
134
        'article': article,
135
        'shop': shop,
136
        'quantity_total': quantity_total,
137
        'quantities_orders_users': quantities_orders_users,
138
        'now': datetime.utcnow(),
139
    }
140
141
142
# -------------------------------------------------------------------- #
143
# create
144
145
146 1
@blueprint.route('/for_shop/<shop_id>/create')
147 1
@permission_required(ShopArticlePermission.create)
148 1
@templated
149 1
def create_form(shop_id, erroneous_form=None):
150
    """Show form to create an article."""
151
    shop = _get_shop_or_404(shop_id)
152
153
    article_number_sequences = (
154
        article_sequence_service.find_article_number_sequences_for_shop(shop.id)
155
    )
156
    article_number_sequence_available = bool(article_number_sequences)
157
158
    form = (
159
        erroneous_form
160
        if erroneous_form
161
        else ArticleCreateForm(price=Decimal('0.0'), tax_rate=Decimal('19.0'))
162
    )
163
    form.set_article_number_sequence_choices(article_number_sequences)
164
165
    return {
166
        'shop': shop,
167
        'article_number_sequence_available': article_number_sequence_available,
168
        'form': form,
169
    }
170
171
172 1
@blueprint.route('/for_shop/<shop_id>', methods=['POST'])
173 1
@permission_required(ShopArticlePermission.create)
174
def create(shop_id):
175
    """Create an article."""
176
    shop = _get_shop_or_404(shop_id)
177
178
    form = ArticleCreateForm(request.form)
179
180
    article_number_sequences = (
181
        article_sequence_service.find_article_number_sequences_for_shop(shop.id)
182
    )
183
    if not article_number_sequences:
184
        flash_error(
185
            f'Für diesen Shop sind keine Artikelnummer-Sequenzen definiert.'
186
        )
187
        return create_form(shop_id, form)
188
189
    form.set_article_number_sequence_choices(article_number_sequences)
190
    if not form.validate():
191
        return create_form(shop_id, form)
192
193
    article_number_sequence_id = form.article_number_sequence_id.data
194
    if not article_number_sequence_id:
195
        flash_error(f'Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
196
        return create_form(shop_id, form)
197
198
    article_number_sequence = (
199
        article_sequence_service.find_article_number_sequence(
200
            article_number_sequence_id
201
        )
202
    )
203
    if (article_number_sequence is None) or (
204
        article_number_sequence.shop_id != shop.id
205
    ):
206
        flash_error(f'Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
207
        return create_form(shop_id, form)
208
209
    try:
210
        item_number = article_sequence_service.generate_article_number(
211
            article_number_sequence.id
212
        )
213
    except article_sequence_service.ArticleNumberGenerationFailed as e:
214
        abort(500, e.message)
215
216
    description = form.description.data.strip()
217
    price = form.price.data
218
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
219
    total_quantity = form.total_quantity.data
220
    quantity = total_quantity
221
    max_quantity_per_order = form.max_quantity_per_order.data
222
223
    article = article_service.create_article(
224
        shop.id,
225
        item_number,
226
        description,
227
        price,
228
        tax_rate,
229
        total_quantity,
230
        max_quantity_per_order,
231
    )
232
233
    flash_success(f'Der Artikel "{article.item_number}" wurde angelegt.')
234
    return redirect_to('.view', article_id=article.id)
235
236
237
# -------------------------------------------------------------------- #
238
# update
239
240
241 1
@blueprint.route('/<uuid:article_id>/update')
242 1
@permission_required(ShopArticlePermission.update)
243 1
@templated
244 1
def update_form(article_id, erroneous_form=None):
245
    """Show form to update an article."""
246
    article = _get_article_or_404(article_id)
247
248
    shop = shop_service.get_shop(article.shop_id)
249
250
    if article.available_from:
251
        article.available_from = utc_to_local_tz(article.available_from)
252
    if article.available_until:
253
        article.available_until = utc_to_local_tz(article.available_until)
254
255
    form = erroneous_form if erroneous_form else ArticleUpdateForm(obj=article)
256
    form.tax_rate.data = article.tax_rate * TAX_RATE_DISPLAY_FACTOR
257
258
    return {
259
        'article': article,
260
        'shop': shop,
261
        'form': form,
262
    }
263
264
265 1
@blueprint.route('/<uuid:article_id>', methods=['POST'])
266 1
@permission_required(ShopArticlePermission.update)
267
def update(article_id):
268
    """Update an article."""
269
    article = _get_article_or_404(article_id)
270
271
    form = ArticleUpdateForm(request.form)
272
    if not form.validate():
273
        return update_form(article_id, form)
274
275
    description = form.description.data.strip()
276
    price = form.price.data
277
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
278
    available_from = form.available_from.data
279
    available_until = form.available_until.data
280
    total_quantity = form.total_quantity.data
281
    max_quantity_per_order = form.max_quantity_per_order.data
282
    not_directly_orderable = form.not_directly_orderable.data
283
    requires_separate_order = form.requires_separate_order.data
284
    shipping_required = form.shipping_required.data
285
286
    if available_from:
287
        available_from = local_tz_to_utc(available_from)
288
    if available_until:
289
        available_until = local_tz_to_utc(available_until)
290
291
    article = article_service.update_article(
292
        article.id,
293
        description,
294
        price,
295
        tax_rate,
296
        available_from,
297
        available_until,
298
        total_quantity,
299
        max_quantity_per_order,
300
        not_directly_orderable,
301
        requires_separate_order,
302
        shipping_required,
303
    )
304
305
    flash_success(f'Der Artikel "{article.description}" wurde aktualisiert.')
306
    return redirect_to('.view', article_id=article.id)
307
308
309
# -------------------------------------------------------------------- #
310
# article attachments
311
312
313 1
@blueprint.route('/<uuid:article_id>/attachments/create')
314 1
@permission_required(ShopArticlePermission.update)
315 1
@templated
316 1
def attachment_create_form(article_id, erroneous_form=None):
317
    """Show form to attach an article to another article."""
318
    article = _get_article_or_404(article_id)
319
320
    shop = shop_service.get_shop(article.shop_id)
321
322
    attachable_articles = article_service.get_attachable_articles(article.id)
323
324
    form = (
325
        erroneous_form
326
        if erroneous_form
327
        else ArticleAttachmentCreateForm(quantity=0)
328
    )
329
    form.set_article_to_attach_choices(attachable_articles)
330
331
    return {
332
        'article': article,
333
        'shop': shop,
334
        'form': form,
335
    }
336
337
338 1
@blueprint.route('/<uuid:article_id>/attachments', methods=['POST'])
339 1
@permission_required(ShopArticlePermission.update)
340
def attachment_create(article_id):
341
    """Attach an article to another article."""
342
    article = _get_article_or_404(article_id)
343
344
    attachable_articles = article_service.get_attachable_articles(article.id)
345
346
    form = ArticleAttachmentCreateForm(request.form)
347
    form.set_article_to_attach_choices(attachable_articles)
348
349
    if not form.validate():
350
        return attachment_create_form(article_id, form)
351
352
    article_to_attach_id = form.article_to_attach_id.data
353
    article_to_attach = article_service.get_article(article_to_attach_id)
354
    quantity = form.quantity.data
355
356
    article_service.attach_article(
357
        article_to_attach.item_number, quantity, article.item_number
358
    )
359
360
    flash_success(
361
        f'Der Artikel "{article_to_attach.item_number}" '
362
        f'wurde {quantity:d} mal an den Artikel "{article.item_number}" '
363
        'angehängt.'
364
    )
365
    return redirect_to('.view', article_id=article.id)
366
367
368 1
@blueprint.route('/attachments/<uuid:article_id>', methods=['DELETE'])
369 1
@permission_required(ShopArticlePermission.update)
370 1
@respond_no_content
371
def attachment_remove(article_id):
372
    """Remove the attachment link from one article to another."""
373
    attached_article = article_service.find_attached_article(article_id)
374
375
    if attached_article is None:
376
        abort(404)
377
378
    article = attached_article.article
379
    attached_to_article = attached_article.attached_to_article
380
381
    article_service.unattach_article(attached_article.id)
382
383
    flash_success(
384
        f'Artikel "{article.item_number}" ist nun nicht mehr '
385
        f'an Artikel "{attached_to_article.item_number}" angehängt.'
386
    )
387
388
389
# -------------------------------------------------------------------- #
390
# article number sequences
391
392
393 1
@blueprint.route('/number_sequences/for_shop/<shop_id>/create')
394 1
@permission_required(ShopArticlePermission.create)
395 1
@templated
396 1
def create_number_sequence_form(shop_id, erroneous_form=None):
397
    """Show form to create an article number sequence."""
398
    shop = _get_shop_or_404(shop_id)
399
400
    form = (
401
        erroneous_form if erroneous_form else ArticleNumberSequenceCreateForm()
402
    )
403
404
    return {
405
        'shop': shop,
406
        'form': form,
407
    }
408
409
410 1
@blueprint.route('/number_sequences/for_shop/<shop_id>', methods=['POST'])
411 1
@permission_required(ShopArticlePermission.create)
412
def create_number_sequence(shop_id):
413
    """Create an article number sequence."""
414
    shop = _get_shop_or_404(shop_id)
415
416
    form = ArticleNumberSequenceCreateForm(request.form)
417
    if not form.validate():
418
        return create_number_sequence_form(shop_id, form)
419
420
    prefix = form.prefix.data.strip()
421
422
    sequence_id = article_sequence_service.create_article_number_sequence(
423
        shop.id, prefix
424
    )
425
    if sequence_id is None:
426
        flash_error(
427
            'Die Artikelnummer-Sequenz konnte nicht angelegt werden. '
428
            f'Ist das Präfix "{prefix}" bereits definiert?'
429
        )
430
        return create_number_sequence_form(shop.id, form)
431
432
    flash_success(
433
        f'Die Artikelnummer-Sequenz mit dem Präfix "{prefix}" wurde angelegt.'
434
    )
435
    return redirect_to('.index_for_shop', shop_id=shop.id)
436
437
438
# -------------------------------------------------------------------- #
439
# helpers
440
441
442 1
def _get_shop_or_404(shop_id):
443
    shop = shop_service.find_shop(shop_id)
444
445
    if shop is None:
446
        abort(404)
447
448
    return shop
449
450
451 1
def _get_article_or_404(article_id):
452
    article = article_service.find_db_article(article_id)
453
454
    if article is None:
455
        abort(404)
456
457
    return article
458