Test Failed
Push — main ( e3918a...309543 )
by Jochen
04:24
created

_render_template()   A

Complexity

Conditions 1

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 14
dl 0
loc 19
rs 9.7
c 0
b 0
f 0
nop 2
ccs 8
cts 8
cp 1
crap 1
1
"""
2
byceps.services.shop.order.email.service
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
Notification e-mails about shop orders
6
7
:Copyright: 2006-2021 Jochen Kupperschmidt
8
:License: Revised BSD (see `LICENSE` file for details)
9
"""
10
11 1
from __future__ import annotations
12 1
from dataclasses import dataclass
13 1
from typing import Iterator
14 1
15
from flask_babel import gettext
16 1
17 1
from .....services.email import service as email_service
18 1
from .....services.email.transfer.models import Message
19
from .....services.shop.order import service as order_service
20 1
from .....services.shop.order.transfer.models import Order, OrderID
21 1
from .....services.shop.shop import service as shop_service
22 1
from .....services.snippet import service as snippet_service
23 1
from .....services.snippet.service import SnippetNotFound
24 1
from .....services.snippet.transfer.models import Scope
25 1
from .....services.user import service as user_service
26 1
from .....services.user.transfer.models import User
27 1
from .....typing import BrandID
28 1
from .....util.datetime.timezone import utc_to_local_tz
29 1
from .....util.l10n import force_user_locale
30 1
from .....util.money import format_euro_amount
31 1
from .....util.templating import load_template
32 1
33
from ...shop.transfer.models import ShopID
34 1
35
36
@dataclass(frozen=True)
37 1
class OrderEmailData:
38
    order: Order
39 1
    brand_id: BrandID
40 1
    orderer: User
41 1
    orderer_screen_name: str
42 1
    orderer_email_address: str
43
44
45 1
def send_email_for_incoming_order_to_orderer(order_id: OrderID) -> None:
46 1
    data = _get_order_email_data(order_id)
47
48 1
    with force_user_locale(data.orderer):
49
        message = _assemble_email_for_incoming_order_to_orderer(data)
50 1
51
    _send_email(message)
52
53 1
54 1
def send_email_for_canceled_order_to_orderer(order_id: OrderID) -> None:
55
    data = _get_order_email_data(order_id)
56 1
57
    with force_user_locale(data.orderer):
58 1
        message = _assemble_email_for_canceled_order_to_orderer(data)
59
60
    _send_email(message)
61 1
62 1
63
def send_email_for_paid_order_to_orderer(order_id: OrderID) -> None:
64 1
    data = _get_order_email_data(order_id)
65
66 1
    with force_user_locale(data.orderer):
67
        message = _assemble_email_for_paid_order_to_orderer(data)
68
69 1
    _send_email(message)
70
71
72 1
def _assemble_email_for_incoming_order_to_orderer(
73
    data: OrderEmailData,
74 1
) -> Message:
75
    order = data.order
76
    order_number = order.order_number
77
78 1
    subject = gettext(
79 1
        'Your order (%(order_number)s) has been received.',
80 1
        order_number=order_number,
81 1
    )
82
83 1
    date_str = _get_order_date_str(order)
84
    indentation = '  '
85
    line_items = [
86
        '\n'.join(
87
            [
88
                indentation
89
                + gettext('Description')
90
                + ': '
91
                + line_item.description,
92 1
                indentation + gettext('Quantity') + ': ' + str(line_item.quantity),
93 1
                indentation
94
                + gettext('Unit price')
95 1
                + ': '
96 1
                + format_euro_amount(line_item.unit_price),
97
            ]
98
        )
99
        for line_item in sorted(order.line_items, key=lambda li: li.description)
100
    ]
101
    total_amount = (
102 1
        indentation
103
        + gettext('Total amount')
104
        + ': '
105 1
        + format_euro_amount(order.total_amount)
106
    )
107
    payment_instructions = _get_payment_instructions(order)
108
    paragraphs = [
109 1
        gettext(
110 1
            'thank you for your order %(order_number)s on %(order_date)s through our website.',
111 1
            order_number=order_number,
112
            order_date=date_str,
113 1
        ),
114
        gettext('You have ordered these items:'),
115
        *line_items,
116
        total_amount,
117
        payment_instructions,
118
    ]
119
    body = _assemble_body(data, paragraphs)
120
121
    recipient_address = data.orderer_email_address
122 1
123 1
    return _assemble_email_to_orderer(
124
        subject, body, data.brand_id, recipient_address
125
    )
126
127 1
128 1
def _get_payment_instructions(order: Order) -> str:
129 1
    fragment = _get_snippet_body(order.shop_id, 'email_payment_instructions')
130
131 1
    template = load_template(fragment)
132
    return template.render(
133
        order_id=order.id,
134
        order_number=order.order_number,
135
    )
136
137
138 View Code Duplication
def _assemble_email_for_canceled_order_to_orderer(
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
139
    data: OrderEmailData,
140 1
) -> Message:
141
    order = data.order
142 1
    order_number = order.order_number
143
144 1
    subject = '\u274c ' + gettext(
145 1
        'Your order (%(order_number)s) has been canceled.',
146 1
        order_number=order_number,
147 1
    )
148
149 1
    date_str = _get_order_date_str(order)
150
    cancelation_reason = order.cancelation_reason or ''
151
    paragraphs = [
152
        gettext(
153
            'your order %(order_number)s on %(order_date)s has been canceled by us for this reason:',
154
            order_number=order_number,
155
            order_date=date_str,
156
        ),
157 1
        cancelation_reason,
158
    ]
159 1
    body = _assemble_body(data, paragraphs)
160
161 1
    recipient_address = data.orderer_email_address
162
163
    return _assemble_email_to_orderer(
164
        subject, body, data.brand_id, recipient_address
165
    )
166
167
168 1 View Code Duplication
def _assemble_email_for_paid_order_to_orderer(data: OrderEmailData) -> Message:
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
169 1
    order = data.order
170
    order_number = order.order_number
171 1
172 1
    subject = '\u2705 ' + gettext(
173
        'Your order (%(order_number)s) has been paid.',
174
        order_number=order_number,
175 1
    )
176
177
    date_str = _get_order_date_str(order)
178
    paragraphs = [
179
        gettext(
180
            'thank you for your order %(order_number)s on %(order_date)s through our website.',
181
            order_number=order_number,
182
            order_date=date_str,
183 1
        ),
184 1
        gettext(
185 1
            'We have received your payment and have marked your order as paid.'
186 1
        ),
187
    ]
188 1
    body = _assemble_body(data, paragraphs)
189
190
    recipient_address = data.orderer_email_address
191 1
192 1
    return _assemble_email_to_orderer(
193
        subject, body, data.brand_id, recipient_address
194 1
    )
195
196
197
def _get_order_email_data(order_id: OrderID) -> OrderEmailData:
198 1
    """Collect data required for an order e-mail template."""
199
    order = order_service.get_order(order_id)
200
201 1
    shop = shop_service.get_shop(order.shop_id)
202
    orderer_id = order.placed_by_id
203
    orderer = user_service.get_user(orderer_id)
204 1
    screen_name = orderer.screen_name or 'UnknownUser'
205 1
    email_address = user_service.get_email_address(orderer_id)
206
207
    return OrderEmailData(
208
        order=order,
209
        brand_id=shop.brand_id,
210
        orderer=orderer,
211
        orderer_screen_name=screen_name,
212
        orderer_email_address=email_address,
213
    )
214 1
215
216 1
def _assemble_body(data: OrderEmailData, paragraphs: list[str]) -> str:
217 1
    """Assemble the plain text part of the email."""
218 1
    salutation = gettext(
219
        'Hello %(screen_name)s,', screen_name=data.orderer_screen_name
220 1
    )
221
    footer = _get_snippet_body(data.order.shop_id, 'email_footer')
222 1
223
    return '\n\n'.join([salutation] + paragraphs + [footer])
224
225 1
226 1
def _assemble_email_to_orderer(
227
    subject: str,
228
    body: str,
229
    brand_id: BrandID,
230
    recipient_address: str,
231
) -> Message:
232
    """Assemble an email message with the rendered template as its body."""
233
    config = email_service.get_config(brand_id)
234
    sender = config.sender
235
    recipients = [recipient_address]
236
237
    return Message(sender, recipients, subject, body)
238
239
240
def _get_order_date_str(order: Order) -> str:
241
    return utc_to_local_tz(order.created_at).strftime('%d.%m.%Y')
242
243
244
def _get_snippet_body(shop_id: ShopID, name: str) -> str:
245
    scope = Scope('shop', str(shop_id))
246
247
    version = snippet_service.find_current_version_of_snippet_with_name(
248
        scope, name
249
    )
250
251
    if not version:
252
        raise SnippetNotFound(scope, name)
253
254
    return version.body.strip()
255
256
257
def _send_email(message: Message) -> None:
258
    email_service.enqueue_message(message)
259