1 | """ |
||
2 | byceps.blueprints.admin.shop.article.views |
||
3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||
4 | |||
5 | :Copyright: 2006-2021 Jochen Kupperschmidt |
||
6 | :License: Revised BSD (see `LICENSE` file for details) |
||
7 | """ |
||
8 | |||
9 | 1 | from __future__ import annotations |
|
10 | 1 | from datetime import datetime |
|
11 | 1 | from decimal import Decimal |
|
12 | |||
13 | 1 | from flask import abort, request |
|
14 | 1 | from flask_babel import gettext |
|
15 | |||
16 | 1 | from .....services.brand import service as brand_service |
|
17 | 1 | from .....services.party import service as party_service |
|
18 | 1 | from .....services.party.transfer.models import Party |
|
19 | 1 | from .....services.shop.article import ( |
|
20 | 1 | sequence_service as article_sequence_service, |
|
21 | service as article_service, |
||
22 | ) |
||
23 | from .....services.shop.article.transfer.models import ( |
||
24 | 1 | ArticleType, |
|
25 | get_article_type_label, |
||
26 | ) |
||
27 | from .....services.shop.order import ( |
||
28 | 1 | action_registry_service, |
|
29 | action_service, |
||
30 | ordered_articles_service, |
||
31 | service as order_service, |
||
32 | ) |
||
33 | from .....services.shop.order.transfer.models import PaymentState |
||
34 | 1 | from .....services.shop.shop import service as shop_service |
|
35 | 1 | from .....services.ticketing import category_service as ticket_category_service |
|
36 | 1 | from .....services.ticketing.transfer.models import TicketCategory |
|
37 | 1 | from .....services.user import service as user_service |
|
38 | 1 | from .....services.user_badge import badge_service |
|
39 | 1 | from .....typing import BrandID |
|
40 | 1 | from .....util.framework.blueprint import create_blueprint |
|
41 | 1 | from .....util.framework.flash import flash_error, flash_success |
|
42 | 1 | from .....util.framework.templating import templated |
|
43 | 1 | from .....util.templatefilters import local_tz_to_utc, utc_to_local_tz |
|
44 | 1 | from .....util.views import permission_required, redirect_to, respond_no_content |
|
45 | 1 | ||
46 | from .forms import ( |
||
47 | 1 | ArticleCreateForm, |
|
48 | ArticleUpdateForm, |
||
49 | ArticleAttachmentCreateForm, |
||
50 | ArticleNumberSequenceCreateForm, |
||
51 | RegisterBadgeAwardingActionForm, |
||
52 | RegisterTicketBundlesCreationActionForm, |
||
53 | RegisterTicketsCreationActionForm, |
||
54 | ) |
||
55 | |||
56 | |||
57 | blueprint = create_blueprint('shop_article_admin', __name__) |
||
58 | 1 | ||
59 | |||
60 | TAX_RATE_DISPLAY_FACTOR = Decimal('100') |
||
61 | 1 | ||
62 | |||
63 | @blueprint.get('/for_shop/<shop_id>', defaults={'page': 1}) |
||
64 | 1 | @blueprint.get('/for_shop/<shop_id>/pages/<int:page>') |
|
65 | 1 | @permission_required('shop_article.view') |
|
66 | 1 | @templated |
|
67 | 1 | def index_for_shop(shop_id, page): |
|
68 | """List articles for that shop.""" |
||
69 | shop = _get_shop_or_404(shop_id) |
||
70 | |||
71 | brand = brand_service.get_brand(shop.brand_id) |
||
72 | |||
73 | per_page = request.args.get('per_page', type=int, default=15) |
||
74 | articles = article_service.get_articles_for_shop_paginated( |
||
75 | shop.id, page, per_page |
||
76 | ) |
||
77 | |||
78 | return { |
||
79 | 'shop': shop, |
||
80 | 'brand': brand, |
||
81 | 'articles': articles, |
||
82 | } |
||
83 | |||
84 | |||
85 | @blueprint.get('/<uuid:article_id>') |
||
86 | 1 | @permission_required('shop_article.view') |
|
87 | 1 | @templated |
|
88 | 1 | def view(article_id): |
|
89 | """Show a single article.""" |
||
90 | article = article_service.find_article_with_details(article_id) |
||
91 | if article is None: |
||
92 | abort(404) |
||
93 | |||
94 | shop = shop_service.get_shop(article.shop_id) |
||
95 | |||
96 | brand = brand_service.get_brand(shop.brand_id) |
||
97 | |||
98 | type_label = get_article_type_label(article.type_) |
||
99 | |||
100 | totals = ordered_articles_service.count_ordered_articles( |
||
101 | article.item_number |
||
102 | ) |
||
103 | |||
104 | actions = action_service.get_actions_for_article(article.item_number) |
||
105 | actions.sort(key=lambda a: a.payment_state.name, reverse=True) |
||
106 | |||
107 | return { |
||
108 | 'article': article, |
||
109 | 'shop': shop, |
||
110 | 'brand': brand, |
||
111 | 'type_label': type_label, |
||
112 | 'totals': totals, |
||
113 | 'PaymentState': PaymentState, |
||
114 | 'actions': actions, |
||
115 | } |
||
116 | |||
117 | |||
118 | @blueprint.get('/<uuid:article_id>/ordered') |
||
119 | 1 | @permission_required('shop_article.view') |
|
120 | 1 | @templated |
|
121 | 1 | def view_ordered(article_id): |
|
122 | """List the people that have ordered this article, and the |
||
123 | corresponding quantities. |
||
124 | """ |
||
125 | article = _get_article_or_404(article_id) |
||
126 | |||
127 | shop = shop_service.get_shop(article.shop_id) |
||
128 | |||
129 | brand = brand_service.get_brand(shop.brand_id) |
||
130 | |||
131 | line_items = ordered_articles_service.get_line_items_for_article( |
||
132 | article.item_number |
||
133 | ) |
||
134 | |||
135 | quantity_total = sum(item.quantity for item in line_items) |
||
136 | |||
137 | order_numbers = {item.order_number for item in line_items} |
||
138 | orders = order_service.find_orders_by_order_numbers(order_numbers) |
||
139 | orders_by_order_numbers = {order.order_number: order for order in orders} |
||
140 | |||
141 | user_ids = {order.placed_by_id for order in orders} |
||
142 | users = user_service.get_users(user_ids, include_avatars=True) |
||
143 | users_by_id = user_service.index_users_by_id(users) |
||
144 | |||
145 | def transform(line_item): |
||
146 | quantity = line_item.quantity |
||
147 | order = orders_by_order_numbers[line_item.order_number] |
||
148 | user = users_by_id[order.placed_by_id] |
||
149 | |||
150 | return quantity, order, user |
||
151 | |||
152 | quantities_orders_users = list(map(transform, line_items)) |
||
153 | |||
154 | return { |
||
155 | 'article': article, |
||
156 | 'shop': shop, |
||
157 | 'brand': brand, |
||
158 | 'quantity_total': quantity_total, |
||
159 | 'quantities_orders_users': quantities_orders_users, |
||
160 | 'now': datetime.utcnow(), |
||
161 | } |
||
162 | |||
163 | |||
164 | # -------------------------------------------------------------------- # |
||
165 | # create |
||
166 | |||
167 | |||
168 | @blueprint.get('/for_shop/<shop_id>/create') |
||
169 | 1 | @permission_required('shop_article.create') |
|
170 | 1 | @templated |
|
171 | 1 | def create_form(shop_id, erroneous_form=None): |
|
172 | 1 | """Show form to create an article.""" |
|
173 | shop = _get_shop_or_404(shop_id) |
||
174 | |||
175 | brand = brand_service.get_brand(shop.brand_id) |
||
176 | |||
177 | article_number_sequences = ( |
||
178 | article_sequence_service.get_article_number_sequences_for_shop(shop.id) |
||
179 | ) |
||
180 | article_number_sequence_available = bool(article_number_sequences) |
||
181 | |||
182 | form = ( |
||
183 | erroneous_form |
||
184 | if erroneous_form |
||
185 | else ArticleCreateForm(price=Decimal('0.0'), tax_rate=Decimal('19.0')) |
||
186 | ) |
||
187 | form.set_article_number_sequence_choices(article_number_sequences) |
||
188 | |||
189 | return { |
||
190 | 'shop': shop, |
||
191 | 'brand': brand, |
||
192 | 'article_number_sequence_available': article_number_sequence_available, |
||
193 | 'form': form, |
||
194 | } |
||
195 | |||
196 | |||
197 | @blueprint.post('/for_shop/<shop_id>') |
||
198 | 1 | @permission_required('shop_article.create') |
|
199 | 1 | def create(shop_id): |
|
200 | """Create an article.""" |
||
201 | shop = _get_shop_or_404(shop_id) |
||
202 | |||
203 | form = ArticleCreateForm(request.form) |
||
204 | if not form.validate(): |
||
205 | return create_form(shop_id, form) |
||
206 | |||
207 | article_number_sequences = ( |
||
208 | article_sequence_service.get_article_number_sequences_for_shop(shop.id) |
||
209 | ) |
||
210 | if not article_number_sequences: |
||
211 | flash_error( |
||
212 | gettext('No article number sequences are defined for this shop.') |
||
213 | ) |
||
214 | return create_form(shop_id, form) |
||
215 | |||
216 | form.set_article_number_sequence_choices(article_number_sequences) |
||
217 | if not form.validate(): |
||
218 | return create_form(shop_id, form) |
||
219 | |||
220 | article_number_sequence_id = form.article_number_sequence_id.data |
||
221 | if not article_number_sequence_id: |
||
222 | flash_error(gettext('No valid article number sequence was specified.')) |
||
223 | return create_form(shop_id, form) |
||
224 | |||
225 | article_number_sequence = ( |
||
226 | article_sequence_service.get_article_number_sequence( |
||
227 | article_number_sequence_id |
||
228 | ) |
||
229 | ) |
||
230 | if article_number_sequence.shop_id != shop.id: |
||
231 | flash_error(gettext('No valid article number sequence was specified.')) |
||
232 | return create_form(shop_id, form) |
||
233 | |||
234 | try: |
||
235 | item_number = article_sequence_service.generate_article_number( |
||
236 | article_number_sequence.id |
||
237 | ) |
||
238 | except article_sequence_service.ArticleNumberGenerationFailed as e: |
||
239 | abort(500, e.message) |
||
240 | |||
241 | type_ = ArticleType[form.type_.data] |
||
242 | description = form.description.data.strip() |
||
243 | price = form.price.data |
||
244 | tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR |
||
245 | total_quantity = form.total_quantity.data |
||
246 | quantity = total_quantity |
||
247 | max_quantity_per_order = form.max_quantity_per_order.data |
||
248 | processing_required = type_ == ArticleType.physical |
||
249 | |||
250 | article = article_service.create_article( |
||
251 | shop.id, |
||
252 | item_number, |
||
253 | type_, |
||
254 | description, |
||
255 | price, |
||
256 | tax_rate, |
||
257 | total_quantity, |
||
258 | max_quantity_per_order, |
||
259 | processing_required, |
||
260 | ) |
||
261 | |||
262 | flash_success( |
||
263 | gettext( |
||
264 | 'Article "%(item_number)s" has been created.', |
||
265 | item_number=article.item_number, |
||
266 | ) |
||
267 | ) |
||
268 | return redirect_to('.view', article_id=article.id) |
||
269 | |||
270 | |||
271 | # -------------------------------------------------------------------- # |
||
272 | # update |
||
273 | |||
274 | |||
275 | @blueprint.get('/<uuid:article_id>/update') |
||
276 | 1 | @permission_required('shop_article.update') |
|
277 | 1 | @templated |
|
278 | 1 | def update_form(article_id, erroneous_form=None): |
|
279 | 1 | """Show form to update an article.""" |
|
280 | article = _get_article_or_404(article_id) |
||
281 | |||
282 | shop = shop_service.get_shop(article.shop_id) |
||
283 | |||
284 | brand = brand_service.get_brand(shop.brand_id) |
||
285 | |||
286 | if article.available_from: |
||
287 | article.available_from = utc_to_local_tz(article.available_from) |
||
288 | if article.available_until: |
||
289 | article.available_until = utc_to_local_tz(article.available_until) |
||
290 | |||
291 | form = ( |
||
292 | erroneous_form |
||
293 | if erroneous_form |
||
294 | else ArticleUpdateForm( |
||
295 | obj=article, |
||
296 | available_from_date=article.available_from.date() |
||
297 | if article.available_from |
||
298 | else None, |
||
299 | available_from_time=article.available_from.time() |
||
300 | if article.available_from |
||
301 | else None, |
||
302 | available_until_date=article.available_until.date() |
||
303 | if article.available_until |
||
304 | else None, |
||
305 | available_until_time=article.available_until.time() |
||
306 | if article.available_until |
||
307 | else None, |
||
308 | ) |
||
309 | ) |
||
310 | form.tax_rate.data = article.tax_rate * TAX_RATE_DISPLAY_FACTOR |
||
311 | |||
312 | return { |
||
313 | 'article': article, |
||
314 | 'shop': shop, |
||
315 | 'brand': brand, |
||
316 | 'form': form, |
||
317 | } |
||
318 | |||
319 | |||
320 | @blueprint.post('/<uuid:article_id>') |
||
321 | 1 | @permission_required('shop_article.update') |
|
322 | 1 | def update(article_id): |
|
323 | """Update an article.""" |
||
324 | article = _get_article_or_404(article_id) |
||
325 | |||
326 | form = ArticleUpdateForm(request.form) |
||
327 | if not form.validate(): |
||
328 | return update_form(article_id, form) |
||
329 | |||
330 | description = form.description.data.strip() |
||
331 | price = form.price.data |
||
332 | tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR |
||
333 | if form.available_from_date.data and form.available_from_time.data: |
||
334 | available_from = local_tz_to_utc( |
||
335 | datetime.combine( |
||
336 | form.available_from_date.data, form.available_from_time.data |
||
337 | ) |
||
338 | ) |
||
339 | else: |
||
340 | available_from = None |
||
341 | if form.available_until_date.data and form.available_until_time.data: |
||
342 | available_until = local_tz_to_utc( |
||
343 | datetime.combine( |
||
344 | form.available_until_date.data, form.available_until_time.data |
||
345 | ) |
||
346 | ) |
||
347 | else: |
||
348 | available_until = None |
||
349 | total_quantity = form.total_quantity.data |
||
350 | max_quantity_per_order = form.max_quantity_per_order.data |
||
351 | not_directly_orderable = form.not_directly_orderable.data |
||
352 | separate_order_required = form.separate_order_required.data |
||
353 | |||
354 | article = article_service.update_article( |
||
355 | article.id, |
||
356 | description, |
||
357 | price, |
||
358 | tax_rate, |
||
359 | available_from, |
||
360 | available_until, |
||
361 | total_quantity, |
||
362 | max_quantity_per_order, |
||
363 | not_directly_orderable, |
||
364 | separate_order_required, |
||
365 | ) |
||
366 | |||
367 | flash_success( |
||
368 | gettext( |
||
369 | 'Article "%(description)s" has been updated.', |
||
370 | description=article.description, |
||
371 | ) |
||
372 | ) |
||
373 | return redirect_to('.view', article_id=article.id) |
||
374 | |||
375 | |||
376 | # -------------------------------------------------------------------- # |
||
377 | # article attachments |
||
378 | |||
379 | |||
380 | @blueprint.get('/<uuid:article_id>/attachments/create') |
||
381 | 1 | @permission_required('shop_article.update') |
|
382 | 1 | @templated |
|
383 | 1 | def attachment_create_form(article_id, erroneous_form=None): |
|
384 | 1 | """Show form to attach an article to another article.""" |
|
385 | article = _get_article_or_404(article_id) |
||
386 | |||
387 | shop = shop_service.get_shop(article.shop_id) |
||
388 | |||
389 | brand = brand_service.get_brand(shop.brand_id) |
||
390 | |||
391 | attachable_articles = article_service.get_attachable_articles(article.id) |
||
392 | |||
393 | form = ( |
||
394 | erroneous_form |
||
395 | if erroneous_form |
||
396 | else ArticleAttachmentCreateForm(quantity=0) |
||
397 | ) |
||
398 | form.set_article_to_attach_choices(attachable_articles) |
||
399 | |||
400 | return { |
||
401 | 'article': article, |
||
402 | 'shop': shop, |
||
403 | 'brand': brand, |
||
404 | 'form': form, |
||
405 | } |
||
406 | |||
407 | |||
408 | @blueprint.post('/<uuid:article_id>/attachments') |
||
409 | 1 | @permission_required('shop_article.update') |
|
410 | 1 | def attachment_create(article_id): |
|
411 | """Attach an article to another article.""" |
||
412 | article = _get_article_or_404(article_id) |
||
413 | |||
414 | attachable_articles = article_service.get_attachable_articles(article.id) |
||
415 | |||
416 | form = ArticleAttachmentCreateForm(request.form) |
||
417 | form.set_article_to_attach_choices(attachable_articles) |
||
418 | |||
419 | if not form.validate(): |
||
420 | return attachment_create_form(article_id, form) |
||
421 | |||
422 | article_to_attach_id = form.article_to_attach_id.data |
||
423 | article_to_attach = article_service.get_article(article_to_attach_id) |
||
424 | quantity = form.quantity.data |
||
425 | |||
426 | article_service.attach_article( |
||
427 | article_to_attach.item_number, quantity, article.item_number |
||
428 | ) |
||
429 | |||
430 | flash_success( |
||
431 | gettext( |
||
432 | 'Article "%(article_to_attach_item_number)s" has been attached %(quantity)s times to article "%(article_item_number)s".', |
||
433 | article_to_attach_item_number=article_to_attach.item_number, |
||
434 | quantity=quantity, |
||
435 | article_item_number=article.item_number, |
||
436 | ) |
||
437 | ) |
||
438 | return redirect_to('.view', article_id=article.id) |
||
439 | |||
440 | |||
441 | @blueprint.delete('/attachments/<uuid:article_id>') |
||
442 | 1 | @permission_required('shop_article.update') |
|
443 | 1 | @respond_no_content |
|
444 | 1 | def attachment_remove(article_id): |
|
445 | """Remove the attachment link from one article to another.""" |
||
446 | attached_article = article_service.find_attached_article(article_id) |
||
447 | |||
448 | if attached_article is None: |
||
449 | abort(404) |
||
450 | |||
451 | article = attached_article.article |
||
452 | attached_to_article = attached_article.attached_to_article |
||
453 | |||
454 | article_service.unattach_article(attached_article.id) |
||
455 | |||
456 | flash_success( |
||
457 | gettext( |
||
458 | 'Article "%(article_item_number)s" is no longer attached to article "%(attached_to_article_item_number)s".', |
||
459 | article_item_number=article.item_number, |
||
460 | attached_to_article_item_number=attached_to_article.item_number, |
||
461 | ) |
||
462 | ) |
||
463 | |||
464 | |||
465 | # -------------------------------------------------------------------- # |
||
466 | # actions |
||
467 | |||
468 | |||
469 | @blueprint.get('/<uuid:article_id>/actions/badge_awarding/create') |
||
470 | 1 | @permission_required('shop_article.update') |
|
471 | 1 | @templated |
|
472 | 1 | def action_create_form_for_badge_awarding(article_id, erroneous_form=None): |
|
473 | 1 | """Show form to register a badge awarding action for the article.""" |
|
474 | article = _get_article_or_404(article_id) |
||
475 | |||
476 | shop = shop_service.get_shop(article.shop_id) |
||
477 | brand = brand_service.get_brand(shop.brand_id) |
||
478 | |||
479 | badges = badge_service.get_all_badges() |
||
480 | |||
481 | form = ( |
||
482 | erroneous_form if erroneous_form else RegisterBadgeAwardingActionForm() |
||
483 | ) |
||
484 | form.set_badge_choices(badges) |
||
485 | |||
486 | return { |
||
487 | 'article': article, |
||
488 | 'shop': shop, |
||
489 | 'brand': brand, |
||
490 | 'form': form, |
||
491 | } |
||
492 | |||
493 | |||
494 | @blueprint.post('/<uuid:article_id>/actions/badge_awarding') |
||
495 | 1 | @permission_required('shop_article.update') |
|
496 | 1 | def action_create_for_badge_awarding(article_id): |
|
497 | """Register a badge awarding action for the article.""" |
||
498 | article = _get_article_or_404(article_id) |
||
499 | |||
500 | badges = badge_service.get_all_badges() |
||
501 | |||
502 | form = RegisterBadgeAwardingActionForm(request.form) |
||
503 | form.set_badge_choices(badges) |
||
504 | |||
505 | if not form.validate(): |
||
506 | return action_create_form_for_badge_awarding(article_id, form) |
||
507 | |||
508 | badge_id = form.badge_id.data |
||
509 | badge = badge_service.get_badge(badge_id) |
||
510 | |||
511 | action_registry_service.register_badge_awarding( |
||
512 | article.item_number, badge.id |
||
513 | ) |
||
514 | |||
515 | flash_success(gettext('Action has been added.')) |
||
516 | |||
517 | return redirect_to('.view', article_id=article.id) |
||
518 | |||
519 | |||
520 | @blueprint.get('/<uuid:article_id>/actions/tickets_creation/create') |
||
521 | 1 | @permission_required('shop_article.update') |
|
522 | 1 | @templated |
|
523 | 1 | def action_create_form_for_tickets_creation(article_id, erroneous_form=None): |
|
524 | 1 | """Show form to register a tickets creation action for the article.""" |
|
525 | article = _get_article_or_404(article_id) |
||
526 | |||
527 | shop = shop_service.get_shop(article.shop_id) |
||
528 | brand = brand_service.get_brand(shop.brand_id) |
||
529 | |||
530 | form = ( |
||
531 | erroneous_form |
||
532 | if erroneous_form |
||
533 | else RegisterTicketsCreationActionForm() |
||
534 | ) |
||
535 | form.set_category_choices(_get_categories_with_parties(brand.id)) |
||
536 | |||
537 | return { |
||
538 | 'article': article, |
||
539 | 'shop': shop, |
||
540 | 'brand': brand, |
||
541 | 'form': form, |
||
542 | } |
||
543 | |||
544 | |||
545 | View Code Duplication | @blueprint.post('/<uuid:article_id>/actions/tickets_creation') |
|
546 | 1 | @permission_required('shop_article.update') |
|
547 | 1 | def action_create_for_tickets_creation(article_id): |
|
548 | """Register a tickets creation action for the article.""" |
||
549 | article = _get_article_or_404(article_id) |
||
550 | |||
551 | shop = shop_service.get_shop(article.shop_id) |
||
552 | brand = brand_service.get_brand(shop.brand_id) |
||
553 | |||
554 | form = RegisterTicketsCreationActionForm(request.form) |
||
555 | form.set_category_choices(_get_categories_with_parties(brand.id)) |
||
556 | |||
557 | if not form.validate(): |
||
558 | return action_create_form_for_tickets_creation(article_id, form) |
||
559 | |||
560 | category_id = form.category_id.data |
||
561 | category = ticket_category_service.find_category(category_id) |
||
562 | if category is None: |
||
563 | raise ValueError(f'Unknown category ID "{category_id}"') |
||
564 | |||
565 | action_registry_service.register_tickets_creation( |
||
566 | article.item_number, category.id |
||
567 | ) |
||
568 | |||
569 | flash_success(gettext('Action has been added.')) |
||
570 | |||
571 | return redirect_to('.view', article_id=article.id) |
||
572 | |||
573 | |||
574 | @blueprint.get('/<uuid:article_id>/actions/ticket_bundles_creation/create') |
||
575 | 1 | @permission_required('shop_article.update') |
|
576 | 1 | @templated |
|
577 | 1 | def action_create_form_for_ticket_bundles_creation( |
|
578 | 1 | article_id, erroneous_form=None |
|
579 | ): |
||
580 | """Show form to register a ticket bundles creation action for the article.""" |
||
581 | article = _get_article_or_404(article_id) |
||
582 | |||
583 | shop = shop_service.get_shop(article.shop_id) |
||
584 | brand = brand_service.get_brand(shop.brand_id) |
||
585 | |||
586 | form = ( |
||
587 | erroneous_form |
||
588 | if erroneous_form |
||
589 | else RegisterTicketBundlesCreationActionForm() |
||
590 | ) |
||
591 | form.set_category_choices(_get_categories_with_parties(brand.id)) |
||
592 | |||
593 | return { |
||
594 | 'article': article, |
||
595 | 'shop': shop, |
||
596 | 'brand': brand, |
||
597 | 'form': form, |
||
598 | } |
||
599 | |||
600 | |||
601 | View Code Duplication | @blueprint.post('/<uuid:article_id>/actions/ticket_bundles_creation') |
|
602 | 1 | @permission_required('shop_article.update') |
|
603 | 1 | def action_create_for_ticket_bundles_creation(article_id): |
|
604 | """Register a ticket bundles creation action for the article.""" |
||
605 | article = _get_article_or_404(article_id) |
||
606 | |||
607 | shop = shop_service.get_shop(article.shop_id) |
||
608 | brand = brand_service.get_brand(shop.brand_id) |
||
609 | |||
610 | form = RegisterTicketBundlesCreationActionForm(request.form) |
||
611 | form.set_category_choices(_get_categories_with_parties(brand.id)) |
||
612 | |||
613 | if not form.validate(): |
||
614 | return action_create_form_for_ticket_bundles_creation(article_id, form) |
||
615 | |||
616 | category_id = form.category_id.data |
||
617 | category = ticket_category_service.find_category(category_id) |
||
618 | if category is None: |
||
619 | raise ValueError(f'Unknown category ID "{category_id}"') |
||
620 | |||
621 | ticket_quantity = form.ticket_quantity.data |
||
622 | |||
623 | action_registry_service.register_ticket_bundles_creation( |
||
624 | article.item_number, category.id, ticket_quantity |
||
625 | ) |
||
626 | |||
627 | flash_success(gettext('Action has been added.')) |
||
628 | |||
629 | return redirect_to('.view', article_id=article.id) |
||
630 | |||
631 | |||
632 | def _get_categories_with_parties( |
||
633 | 1 | brand_id: BrandID, |
|
634 | ) -> set[tuple[TicketCategory, Party]]: |
||
635 | return { |
||
636 | (category, party) |
||
637 | for party in party_service.get_active_parties(brand_id) |
||
638 | for category in ticket_category_service.get_categories_for_party( |
||
639 | party.id |
||
640 | ) |
||
641 | } |
||
642 | |||
643 | |||
644 | @blueprint.delete('/actions/<uuid:action_id>') |
||
645 | 1 | @permission_required('shop_article.update') |
|
646 | 1 | @respond_no_content |
|
647 | 1 | def action_remove(action_id): |
|
648 | """Remove the action from the article.""" |
||
649 | action = action_service.find_action(action_id) |
||
650 | |||
651 | if action is None: |
||
652 | abort(404) |
||
653 | |||
654 | action_service.delete_action(action.id) |
||
655 | |||
656 | flash_success(gettext('Action has been removed.')) |
||
657 | |||
658 | |||
659 | # -------------------------------------------------------------------- # |
||
660 | # article number sequences |
||
661 | |||
662 | |||
663 | @blueprint.get('/number_sequences/for_shop/<shop_id>/create') |
||
664 | 1 | @permission_required('shop_article.create') |
|
665 | 1 | @templated |
|
666 | 1 | def create_number_sequence_form(shop_id, erroneous_form=None): |
|
667 | 1 | """Show form to create an article number sequence.""" |
|
668 | shop = _get_shop_or_404(shop_id) |
||
669 | |||
670 | brand = brand_service.get_brand(shop.brand_id) |
||
671 | |||
672 | form = ( |
||
673 | erroneous_form if erroneous_form else ArticleNumberSequenceCreateForm() |
||
674 | ) |
||
675 | |||
676 | return { |
||
677 | 'shop': shop, |
||
678 | 'brand': brand, |
||
679 | 'form': form, |
||
680 | } |
||
681 | |||
682 | |||
683 | View Code Duplication | @blueprint.post('/number_sequences/for_shop/<shop_id>') |
|
1 ignored issue
–
show
Duplication
introduced
by
![]() |
|||
684 | 1 | @permission_required('shop_article.create') |
|
685 | 1 | def create_number_sequence(shop_id): |
|
686 | """Create an article number sequence.""" |
||
687 | shop = _get_shop_or_404(shop_id) |
||
688 | |||
689 | form = ArticleNumberSequenceCreateForm(request.form) |
||
690 | if not form.validate(): |
||
691 | return create_number_sequence_form(shop_id, form) |
||
692 | |||
693 | prefix = form.prefix.data.strip() |
||
694 | |||
695 | sequence_id = article_sequence_service.create_article_number_sequence( |
||
696 | shop.id, prefix |
||
697 | ) |
||
698 | if sequence_id is None: |
||
699 | flash_error( |
||
700 | gettext( |
||
701 | 'Article number sequence could not be created. ' |
||
702 | 'Is prefix "%(prefix)s" already defined?', |
||
703 | prefix=prefix, |
||
704 | ) |
||
705 | ) |
||
706 | return create_number_sequence_form(shop.id, form) |
||
707 | |||
708 | flash_success( |
||
709 | gettext( |
||
710 | 'Article number sequence with prefix "%(prefix)s" has been created.', |
||
711 | prefix=prefix, |
||
712 | ) |
||
713 | ) |
||
714 | return redirect_to('.index_for_shop', shop_id=shop.id) |
||
715 | |||
716 | |||
717 | # -------------------------------------------------------------------- # |
||
718 | # helpers |
||
719 | |||
720 | |||
721 | def _get_shop_or_404(shop_id): |
||
722 | 1 | shop = shop_service.find_shop(shop_id) |
|
723 | |||
724 | if shop is None: |
||
725 | abort(404) |
||
726 | |||
727 | return shop |
||
728 | |||
729 | |||
730 | def _get_article_or_404(article_id): |
||
731 | 1 | article = article_service.find_db_article(article_id) |
|
732 | |||
733 | if article is None: |
||
734 | abort(404) |
||
735 | |||
736 | return article |
||
737 |