Passed
Push — main ( 9b34b2...de173d )
by Jochen
06:30 queued 02:23
created

byceps/services/shop/order/action_service.py (1 issue)

1
"""
2
byceps.services.shop.order.action_service
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 typing import Callable, Sequence
11 1
from uuid import UUID
12
13 1
from ....database import db
14 1
from ....typing import UserID
15
16 1
from ..article.transfer.models import ArticleNumber
17
18 1
from .actions.award_badge import award_badge
19 1
from .actions.create_ticket_bundles import create_ticket_bundles
20 1
from .actions.create_tickets import create_tickets
21 1
from .actions.revoke_ticket_bundles import revoke_ticket_bundles
22 1
from .actions.revoke_tickets import revoke_tickets
23 1
from .dbmodels.order_action import OrderAction as DbOrderAction
24 1
from .transfer.models import Action, ActionParameters, Order, PaymentState
25
26
27 1
OrderActionType = Callable[[Order, ArticleNumber, int, ActionParameters], None]
28
29
30 1
PROCEDURES_BY_NAME = {
31
    'award_badge': award_badge,
32
    'create_ticket_bundles': create_ticket_bundles,
33
    'revoke_ticket_bundles': revoke_ticket_bundles,
34
    'create_tickets': create_tickets,
35
    'revoke_tickets': revoke_tickets,
36
}
37
38
39
# -------------------------------------------------------------------- #
40
# creation/removal
41
42
43 1
def create_action(
44
    article_number: ArticleNumber,
45
    payment_state: PaymentState,
46
    procedure_name: str,
47
    parameters: ActionParameters,
48
) -> None:
49
    """Create an order action."""
50 1
    action = DbOrderAction(
51
        article_number, payment_state, procedure_name, parameters
52
    )
53
54 1
    db.session.add(action)
55 1
    db.session.commit()
56
57
58 1
def delete_action(action_id: UUID) -> None:
59
    """Delete the order action."""
60
    db.session.query(OrderAction) \
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OrderAction does not seem to be defined.
Loading history...
61
        .filter_by(id=action_id) \
62
        .delete()
63
64
    db.session.commit()
65
66
67 1
def delete_actions_for_article(article_number: ArticleNumber) -> None:
68
    """Delete all order actions for an article."""
69 1
    db.session.query(DbOrderAction) \
70
        .filter_by(article_number=article_number) \
71
        .delete()
72
73 1
    db.session.commit()
74
75
76 1
def find_action(action_id: UUID) -> Optional[Action]:
77
    """Return the action with that ID, if found."""
78
    action = DbOrderAction.query.get(action_id)
79
80
    if action is None:
81
        return
82
83
    return _db_entity_to_action(action)
84
85
86
# -------------------------------------------------------------------- #
87
# retrieval
88
89
90 1
def get_actions_for_article(article_number: ArticleNumber) -> list[Action]:
91
    """Return the order actions defined for that article."""
92
    actions = DbOrderAction.query \
93
        .filter_by(article_number=article_number) \
94
        .all()
95
96
    return [_db_entity_to_action(action) for action in actions]
97
98
99 1
def _db_entity_to_action(action: DbOrderAction) -> Action:
100 1
    return Action(
101
        id=action.id,
102
        article_number=action.article_number,
103
        payment_state=action.payment_state,
104
        procedure_name=action.procedure,
105
        parameters=action.parameters,
106
    )
107
108
109
# -------------------------------------------------------------------- #
110
# execution
111
112
113 1
def execute_actions(
114
    order: Order, payment_state: PaymentState, initiator_id: UserID
115
) -> None:
116
    """Execute relevant actions for this order in its new payment state."""
117 1
    article_numbers = {item.article_number for item in order.items}
118
119 1
    if not article_numbers:
120 1
        return
121
122 1
    quantities_by_article_number = {
123
        item.article_number: item.quantity for item in order.items
124
    }
125
126 1
    actions = _get_actions(article_numbers, payment_state)
127
128 1
    for action in actions:
129 1
        article_quantity = quantities_by_article_number[action.article_number]
130
131 1
        _execute_procedure(order, action, article_quantity, initiator_id)
132
133
134 1
def _get_actions(
135
    article_numbers: set[ArticleNumber], payment_state: PaymentState
136
) -> Sequence[Action]:
137
    """Return the order actions for those article numbers."""
138 1
    actions = DbOrderAction.query \
139
        .filter(DbOrderAction.article_number.in_(article_numbers)) \
140
        .filter_by(_payment_state=payment_state.name) \
141
        .all()
142
143 1
    return [_db_entity_to_action(action) for action in actions]
144
145
146 1
def _execute_procedure(
147
    order: Order,
148
    action: Action,
149
    article_quantity: int,
150
    initiator_id: UserID,
151
) -> None:
152
    """Execute the procedure configured for that order action."""
153 1
    procedure = _get_procedure(action.procedure_name, action.article_number)
154
155 1
    procedure(
156
        order,
157
        action.article_number,
158
        article_quantity,
159
        initiator_id,
160
        action.parameters,
161
    )
162
163
164 1
def _get_procedure(name: str, article_number: ArticleNumber) -> OrderActionType:
165
    """Return procedure with that name, or raise an exception if the
166
    name is not registerd.
167
    """
168 1
    procedure = PROCEDURES_BY_NAME.get(name)
169
170 1
    if procedure is None:
171
        raise Exception(
172
            f"Unknown procedure '{name}' configured "
173
            f"for article number '{article_number}'."
174
        )
175
176
    return procedure
177