We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Email is not completely translated
The main part of the email("You're receiving this e-mail...") is not translated. The parts after are properly translated. However, the text in text/plain is completely correctly translated. I've tried it with different languages (german and russian).

However, the text in text/plain is completely correctly translated:
```
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit



Hi!

Sie bekommen diese E-Mail weil Sie oder jemand anders ein Passwort für Ihren Benutzeraccount auf localhost:8000 angefordert hat.
Sie können diese Nachricht ignorieren, wenn sie es nicht selber angefordert haben.
Klicken Sie unten um Ihr Passwort zurückzusetzen.


http://localhost:8000/de/account/password/reset/MQ/5ez-e13a9786c6c548232f8b/


Dies ist eine automatisch generierte E-Mail, bitte antworten Sie nicht auf diese E-Mail-Adresse.
Mit freundlichen Grüßen, Saleor e-commerce
```

### Steps to reproduce the problem
1.  go to password reset page (e.g. /de/account/password/reset/)
2. provide email and click on send

### What I expected to happen
a fully translated email

### Screenshots
<img width="712" alt="email" src="https://user-images.githubusercontent.com/15652645/77230292-7c6b6900-6b93-11ea-94ae-c7c1be7587d2.png">

**System information**
Operating system: macOS Catalina & Windows 10


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `saleor/order/utils.py`
Content:
```
1 from functools import wraps
2 
3 from django.conf import settings
4 from django.db import transaction
5 from django.utils import timezone
6 from prices import Money, TaxedMoney
7 
8 from ..account.models import User
9 from ..core.taxes import zero_money
10 from ..core.weight import zero_weight
11 from ..discount.models import NotApplicable, Voucher, VoucherType
12 from ..discount.utils import get_products_voucher_discount, validate_voucher_in_order
13 from ..extensions.manager import get_extensions_manager
14 from ..order import OrderStatus
15 from ..order.models import Order, OrderLine
16 from ..product.utils.digital_products import get_default_digital_content_settings
17 from ..shipping.models import ShippingMethod
18 from ..warehouse.availability import check_stock_quantity
19 from ..warehouse.management import allocate_stock, deallocate_stock, increase_stock
20 from . import events
21 
22 
23 def get_order_country(order: Order) -> str:
24     """Return country to which order will be shipped."""
25     address = order.billing_address
26     if order.is_shipping_required():
27         address = order.shipping_address
28     if address is None:
29         return settings.DEFAULT_COUNTRY
30     return address.country.code
31 
32 
33 def order_line_needs_automatic_fulfillment(line: OrderLine) -> bool:
34     """Check if given line is digital and should be automatically fulfilled."""
35     digital_content_settings = get_default_digital_content_settings()
36     default_automatic_fulfillment = digital_content_settings["automatic_fulfillment"]
37     content = line.variant.digital_content if line.variant else None
38     if not content:
39         return False
40     if default_automatic_fulfillment and content.use_default_settings:
41         return True
42     if content.automatic_fulfillment:
43         return True
44     return False
45 
46 
47 def order_needs_automatic_fullfilment(order: Order) -> bool:
48     """Check if order has digital products which should be automatically fulfilled."""
49     for line in order.lines.digital():
50         if order_line_needs_automatic_fulfillment(line):
51             return True
52     return False
53 
54 
55 def update_voucher_discount(func):
56     """Recalculate order discount amount based on order voucher."""
57 
58     @wraps(func)
59     def decorator(*args, **kwargs):
60         if kwargs.pop("update_voucher_discount", True):
61             order = args[0]
62             try:
63                 discount = get_voucher_discount_for_order(order)
64             except NotApplicable:
65                 discount = zero_money(order.currency)
66             order.discount = discount
67         return func(*args, **kwargs)
68 
69     return decorator
70 
71 
72 @update_voucher_discount
73 def recalculate_order(order: Order, **kwargs):
74     """Recalculate and assign total price of order.
75 
76     Total price is a sum of items in order and order shipping price minus
77     discount amount.
78 
79     Voucher discount amount is recalculated by default. To avoid this, pass
80     update_voucher_discount argument set to False.
81     """
82     # avoid using prefetched order lines
83     lines = [OrderLine.objects.get(pk=line.pk) for line in order]
84     prices = [line.get_total() for line in lines]
85     total = sum(prices, order.shipping_price)
86     # discount amount can't be greater than order total
87     order.discount_amount = min(order.discount_amount, total.gross.amount)
88     if order.discount:
89         total -= order.discount
90     order.total = total
91     order.save(
92         update_fields=[
93             "discount_amount",
94             "total_net_amount",
95             "total_gross_amount",
96             "currency",
97         ]
98     )
99     recalculate_order_weight(order)
100 
101 
102 def recalculate_order_weight(order):
103     """Recalculate order weights."""
104     weight = zero_weight()
105     for line in order:
106         if line.variant:
107             weight += line.variant.get_weight() * line.quantity
108     order.weight = weight
109     order.save(update_fields=["weight"])
110 
111 
112 def update_order_prices(order, discounts):
113     """Update prices in order with given discounts and proper taxes."""
114     manager = get_extensions_manager()
115     for line in order:  # type: OrderLine
116         if line.variant:
117             unit_price = line.variant.get_price(discounts)
118             unit_price = TaxedMoney(unit_price, unit_price)
119             line.unit_price = unit_price
120             line.save(
121                 update_fields=[
122                     "currency",
123                     "unit_price_net_amount",
124                     "unit_price_gross_amount",
125                 ]
126             )
127 
128             price = manager.calculate_order_line_unit(line)
129             if price != line.unit_price:
130                 line.unit_price = price
131                 if price.tax and price.net:
132                     line.tax_rate = price.tax / price.net
133                 line.save()
134 
135     if order.shipping_method:
136         order.shipping_price = manager.calculate_order_shipping(order)
137         order.save(
138             update_fields=[
139                 "shipping_price_net_amount",
140                 "shipping_price_gross_amount",
141                 "currency",
142             ]
143         )
144 
145     recalculate_order(order)
146 
147 
148 def update_order_status(order):
149     """Update order status depending on fulfillments."""
150     quantity_fulfilled = order.quantity_fulfilled
151     total_quantity = order.get_total_quantity()
152 
153     if quantity_fulfilled <= 0:
154         status = OrderStatus.UNFULFILLED
155     elif quantity_fulfilled < total_quantity:
156         status = OrderStatus.PARTIALLY_FULFILLED
157     else:
158         status = OrderStatus.FULFILLED
159 
160     if status != order.status:
161         order.status = status
162         order.save(update_fields=["status"])
163 
164 
165 @transaction.atomic
166 def add_variant_to_order(
167     order,
168     variant,
169     quantity,
170     discounts=None,
171     allow_overselling=False,
172     track_inventory=True,
173 ):
174     """Add total_quantity of variant to order.
175 
176     Returns an order line the variant was added to.
177 
178     By default, raises InsufficientStock exception if  quantity could not be
179     fulfilled. This can be disabled by setting `allow_overselling` to True.
180     """
181     country = get_order_country(order)
182     if not allow_overselling:
183         check_stock_quantity(variant, country, quantity)
184 
185     try:
186         line = order.lines.get(variant=variant)
187         line.quantity += quantity
188         line.save(update_fields=["quantity"])
189     except OrderLine.DoesNotExist:
190         unit_price = variant.get_price(discounts)
191         unit_price = TaxedMoney(net=unit_price, gross=unit_price)
192         product = variant.product
193         product_name = str(product)
194         variant_name = str(variant)
195         translated_product_name = str(product.translated)
196         translated_variant_name = str(variant.translated)
197         if translated_product_name == product_name:
198             translated_product_name = ""
199         if translated_variant_name == variant_name:
200             translated_variant_name = ""
201         line = order.lines.create(
202             product_name=product_name,
203             variant_name=variant_name,
204             translated_product_name=translated_product_name,
205             translated_variant_name=translated_variant_name,
206             product_sku=variant.sku,
207             is_shipping_required=variant.is_shipping_required(),
208             quantity=quantity,
209             unit_price=unit_price,
210             variant=variant,
211         )
212         manager = get_extensions_manager()
213         unit_price = manager.calculate_order_line_unit(line)
214         line.unit_price = unit_price
215         line.tax_rate = unit_price.tax / unit_price.net
216         line.save(
217             update_fields=[
218                 "currency",
219                 "unit_price_net_amount",
220                 "unit_price_gross_amount",
221                 "tax_rate",
222             ]
223         )
224 
225     if variant.track_inventory and track_inventory:
226         allocate_stock(variant, country, quantity)
227     return line
228 
229 
230 def add_gift_card_to_order(order, gift_card, total_price_left):
231     """Add gift card to order.
232 
233     Return a total price left after applying the gift cards.
234     """
235     if total_price_left > zero_money(total_price_left.currency):
236         order.gift_cards.add(gift_card)
237         if total_price_left < gift_card.current_balance:
238             gift_card.current_balance = gift_card.current_balance - total_price_left
239             total_price_left = zero_money(total_price_left.currency)
240         else:
241             total_price_left = total_price_left - gift_card.current_balance
242             gift_card.current_balance_amount = 0
243         gift_card.last_used_on = timezone.now()
244         gift_card.save(update_fields=["current_balance_amount", "last_used_on"])
245     return total_price_left
246 
247 
248 def change_order_line_quantity(user, line, old_quantity, new_quantity):
249     """Change the quantity of ordered items in a order line."""
250     if new_quantity:
251         line.quantity = new_quantity
252         line.save(update_fields=["quantity"])
253     else:
254         delete_order_line(line)
255 
256     quantity_diff = old_quantity - new_quantity
257 
258     # Create the removal event
259     if quantity_diff > 0:
260         events.draft_order_removed_products_event(
261             order=line.order, user=user, order_lines=[(quantity_diff, line)]
262         )
263     elif quantity_diff < 0:
264         events.draft_order_added_products_event(
265             order=line.order, user=user, order_lines=[(quantity_diff * -1, line)]
266         )
267 
268 
269 def delete_order_line(line):
270     """Delete an order line from an order."""
271     line.delete()
272 
273 
274 def restock_order_lines(order):
275     """Return ordered products to corresponding stocks."""
276     country = get_order_country(order)
277 
278     for line in order:
279         if line.variant and line.variant.track_inventory:
280             if line.quantity_unfulfilled > 0:
281                 deallocate_stock(line.variant, country, line.quantity_unfulfilled)
282             if line.quantity_fulfilled > 0:
283                 increase_stock(line.variant, country, line.quantity_fulfilled)
284 
285         if line.quantity_fulfilled > 0:
286             line.quantity_fulfilled = 0
287             line.save(update_fields=["quantity_fulfilled"])
288 
289 
290 def restock_fulfillment_lines(fulfillment):
291     """Return fulfilled products to corresponding stocks."""
292     country = get_order_country(fulfillment.order)
293     for line in fulfillment:
294         if line.order_line.variant and line.order_line.variant.track_inventory:
295             increase_stock(
296                 line.order_line.variant, country, line.quantity, allocate=True
297             )
298 
299 
300 def sum_order_totals(qs):
301     zero = Money(0, currency=settings.DEFAULT_CURRENCY)
302     taxed_zero = TaxedMoney(zero, zero)
303     return sum([order.total for order in qs], taxed_zero)
304 
305 
306 def get_valid_shipping_methods_for_order(order: Order):
307     return ShippingMethod.objects.applicable_shipping_methods_for_instance(
308         order, price=order.get_subtotal().gross
309     )
310 
311 
312 def get_products_voucher_discount_for_order(voucher: Voucher) -> Money:
313     """Calculate products discount value for a voucher, depending on its type."""
314     prices = None
315     if not prices:
316         msg = "This offer is only valid for selected items."
317         raise NotApplicable(msg)
318     return get_products_voucher_discount(voucher, prices)
319 
320 
321 def get_voucher_discount_for_order(order: Order) -> Money:
322     """Calculate discount value depending on voucher and discount types.
323 
324     Raise NotApplicable if voucher of given type cannot be applied.
325     """
326     if not order.voucher:
327         return zero_money(order.currency)
328     validate_voucher_in_order(order)
329     subtotal = order.get_subtotal()
330     if order.voucher.type == VoucherType.ENTIRE_ORDER:
331         return order.voucher.get_discount_amount_for(subtotal.gross)
332     if order.voucher.type == VoucherType.SHIPPING:
333         return order.voucher.get_discount_amount_for(order.shipping_price)
334     if order.voucher.type == VoucherType.SPECIFIC_PRODUCT:
335         return get_products_voucher_discount_for_order(order.voucher)
336     raise NotImplementedError("Unknown discount type")
337 
338 
339 def match_orders_with_new_user(user: User) -> None:
340     Order.objects.confirmed().filter(user_email=user.email, user=None).update(user=user)
341
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/saleor/order/utils.py b/saleor/order/utils.py
--- a/saleor/order/utils.py
+++ b/saleor/order/utils.py
@@ -1,9 +1,15 @@
 from functools import wraps
+from uuid import uuid4
 
 from django.conf import settings
+from django.contrib.staticfiles import finders as static_finders
+from django.core.files.base import ContentFile
+from django.core.files.storage import default_storage
 from django.db import transaction
+from django.template.loader import get_template
 from django.utils import timezone
 from prices import Money, TaxedMoney
+from weasyprint import HTML
 
 from ..account.models import User
 from ..core.taxes import zero_money
@@ -338,3 +344,12 @@
 
 def match_orders_with_new_user(user: User) -> None:
     Order.objects.confirmed().filter(user_email=user.email, user=None).update(user=user)
+
+
+def generate_invoice_pdf_for_order(invoice):
+    logo_path = static_finders.find("images/logo-light.svg")
+    rendered_template = get_template("invoice.html").render(
+        {"invoice": invoice, "order": invoice.order, "logo_path": f"file://{logo_path}"}
+    )
+    content_file = ContentFile(HTML(string=rendered_template).write_pdf())
+    return default_storage.save(f"{uuid4()}.pdf", content_file)

{"golden_diff": "diff --git a/saleor/order/utils.py b/saleor/order/utils.py\n--- a/saleor/order/utils.py\n+++ b/saleor/order/utils.py\n@@ -1,9 +1,15 @@\n from functools import wraps\n+from uuid import uuid4\n \n from django.conf import settings\n+from django.contrib.staticfiles import finders as static_finders\n+from django.core.files.base import ContentFile\n+from django.core.files.storage import default_storage\n from django.db import transaction\n+from django.template.loader import get_template\n from django.utils import timezone\n from prices import Money, TaxedMoney\n+from weasyprint import HTML\n \n from ..account.models import User\n from ..core.taxes import zero_money\n@@ -338,3 +344,12 @@\n \n def match_orders_with_new_user(user: User) -> None:\n     Order.objects.confirmed().filter(user_email=user.email, user=None).update(user=user)\n+\n+\n+def generate_invoice_pdf_for_order(invoice):\n+    logo_path = static_finders.find(\"images/logo-light.svg\")\n+    rendered_template = get_template(\"invoice.html\").render(\n+        {\"invoice\": invoice, \"order\": invoice.order, \"logo_path\": f\"file://{logo_path}\"}\n+    )\n+    content_file = ContentFile(HTML(string=rendered_template).write_pdf())\n+    return default_storage.save(f\"{uuid4()}.pdf\", content_file)\n", "issue": "Email is not completely translated\nThe main part of the email(\"You're receiving this e-mail...\") is not translated. The parts after are properly translated. However, the text in text/plain is completely correctly translated. I've tried it with different languages (german and russian).\r\n\r\nHowever, the text in text/plain is completely correctly translated:\r\n```\r\nContent-Type: text/plain; charset=\"utf-8\"\r\nMIME-Version: 1.0\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\n\r\nHi!\r\n\r\nSie bekommen diese E-Mail weil Sie oder jemand anders ein Passwort f\u00fcr Ihren Benutzeraccount auf localhost:8000 angefordert hat.\r\nSie k\u00f6nnen diese Nachricht ignorieren, wenn sie es nicht selber angefordert haben.\r\nKlicken Sie unten um Ihr Passwort zur\u00fcckzusetzen.\r\n\r\n\r\nhttp://localhost:8000/de/account/password/reset/MQ/5ez-e13a9786c6c548232f8b/\r\n\r\n\r\nDies ist eine automatisch generierte E-Mail, bitte antworten Sie nicht auf diese E-Mail-Adresse.\r\nMit freundlichen Gr\u00fc\u00dfen, Saleor e-commerce\r\n```\r\n\r\n### Steps to reproduce the problem\r\n1.  go to password reset page (e.g. /de/account/password/reset/)\r\n2. provide email and click on send\r\n\r\n### What I expected to happen\r\na fully translated email\r\n\r\n### Screenshots\r\n<img width=\"712\" alt=\"email\" src=\"https://user-images.githubusercontent.com/15652645/77230292-7c6b6900-6b93-11ea-94ae-c7c1be7587d2.png\">\r\n\r\n**System information**\r\nOperating system: macOS Catalina & Windows 10\r\n\n", "before_files": [{"content": "from functools import wraps\n\nfrom django.conf import settings\nfrom django.db import transaction\nfrom django.utils import timezone\nfrom prices import Money, TaxedMoney\n\nfrom ..account.models import User\nfrom ..core.taxes import zero_money\nfrom ..core.weight import zero_weight\nfrom ..discount.models import NotApplicable, Voucher, VoucherType\nfrom ..discount.utils import get_products_voucher_discount, validate_voucher_in_order\nfrom ..extensions.manager import get_extensions_manager\nfrom ..order import OrderStatus\nfrom ..order.models import Order, OrderLine\nfrom ..product.utils.digital_products import get_default_digital_content_settings\nfrom ..shipping.models import ShippingMethod\nfrom ..warehouse.availability import check_stock_quantity\nfrom ..warehouse.management import allocate_stock, deallocate_stock, increase_stock\nfrom . import events\n\n\ndef get_order_country(order: Order) -> str:\n    \"\"\"Return country to which order will be shipped.\"\"\"\n    address = order.billing_address\n    if order.is_shipping_required():\n        address = order.shipping_address\n    if address is None:\n        return settings.DEFAULT_COUNTRY\n    return address.country.code\n\n\ndef order_line_needs_automatic_fulfillment(line: OrderLine) -> bool:\n    \"\"\"Check if given line is digital and should be automatically fulfilled.\"\"\"\n    digital_content_settings = get_default_digital_content_settings()\n    default_automatic_fulfillment = digital_content_settings[\"automatic_fulfillment\"]\n    content = line.variant.digital_content if line.variant else None\n    if not content:\n        return False\n    if default_automatic_fulfillment and content.use_default_settings:\n        return True\n    if content.automatic_fulfillment:\n        return True\n    return False\n\n\ndef order_needs_automatic_fullfilment(order: Order) -> bool:\n    \"\"\"Check if order has digital products which should be automatically fulfilled.\"\"\"\n    for line in order.lines.digital():\n        if order_line_needs_automatic_fulfillment(line):\n            return True\n    return False\n\n\ndef update_voucher_discount(func):\n    \"\"\"Recalculate order discount amount based on order voucher.\"\"\"\n\n    @wraps(func)\n    def decorator(*args, **kwargs):\n        if kwargs.pop(\"update_voucher_discount\", True):\n            order = args[0]\n            try:\n                discount = get_voucher_discount_for_order(order)\n            except NotApplicable:\n                discount = zero_money(order.currency)\n            order.discount = discount\n        return func(*args, **kwargs)\n\n    return decorator\n\n\n@update_voucher_discount\ndef recalculate_order(order: Order, **kwargs):\n    \"\"\"Recalculate and assign total price of order.\n\n    Total price is a sum of items in order and order shipping price minus\n    discount amount.\n\n    Voucher discount amount is recalculated by default. To avoid this, pass\n    update_voucher_discount argument set to False.\n    \"\"\"\n    # avoid using prefetched order lines\n    lines = [OrderLine.objects.get(pk=line.pk) for line in order]\n    prices = [line.get_total() for line in lines]\n    total = sum(prices, order.shipping_price)\n    # discount amount can't be greater than order total\n    order.discount_amount = min(order.discount_amount, total.gross.amount)\n    if order.discount:\n        total -= order.discount\n    order.total = total\n    order.save(\n        update_fields=[\n            \"discount_amount\",\n            \"total_net_amount\",\n            \"total_gross_amount\",\n            \"currency\",\n        ]\n    )\n    recalculate_order_weight(order)\n\n\ndef recalculate_order_weight(order):\n    \"\"\"Recalculate order weights.\"\"\"\n    weight = zero_weight()\n    for line in order:\n        if line.variant:\n            weight += line.variant.get_weight() * line.quantity\n    order.weight = weight\n    order.save(update_fields=[\"weight\"])\n\n\ndef update_order_prices(order, discounts):\n    \"\"\"Update prices in order with given discounts and proper taxes.\"\"\"\n    manager = get_extensions_manager()\n    for line in order:  # type: OrderLine\n        if line.variant:\n            unit_price = line.variant.get_price(discounts)\n            unit_price = TaxedMoney(unit_price, unit_price)\n            line.unit_price = unit_price\n            line.save(\n                update_fields=[\n                    \"currency\",\n                    \"unit_price_net_amount\",\n                    \"unit_price_gross_amount\",\n                ]\n            )\n\n            price = manager.calculate_order_line_unit(line)\n            if price != line.unit_price:\n                line.unit_price = price\n                if price.tax and price.net:\n                    line.tax_rate = price.tax / price.net\n                line.save()\n\n    if order.shipping_method:\n        order.shipping_price = manager.calculate_order_shipping(order)\n        order.save(\n            update_fields=[\n                \"shipping_price_net_amount\",\n                \"shipping_price_gross_amount\",\n                \"currency\",\n            ]\n        )\n\n    recalculate_order(order)\n\n\ndef update_order_status(order):\n    \"\"\"Update order status depending on fulfillments.\"\"\"\n    quantity_fulfilled = order.quantity_fulfilled\n    total_quantity = order.get_total_quantity()\n\n    if quantity_fulfilled <= 0:\n        status = OrderStatus.UNFULFILLED\n    elif quantity_fulfilled < total_quantity:\n        status = OrderStatus.PARTIALLY_FULFILLED\n    else:\n        status = OrderStatus.FULFILLED\n\n    if status != order.status:\n        order.status = status\n        order.save(update_fields=[\"status\"])\n\n\n@transaction.atomic\ndef add_variant_to_order(\n    order,\n    variant,\n    quantity,\n    discounts=None,\n    allow_overselling=False,\n    track_inventory=True,\n):\n    \"\"\"Add total_quantity of variant to order.\n\n    Returns an order line the variant was added to.\n\n    By default, raises InsufficientStock exception if  quantity could not be\n    fulfilled. This can be disabled by setting `allow_overselling` to True.\n    \"\"\"\n    country = get_order_country(order)\n    if not allow_overselling:\n        check_stock_quantity(variant, country, quantity)\n\n    try:\n        line = order.lines.get(variant=variant)\n        line.quantity += quantity\n        line.save(update_fields=[\"quantity\"])\n    except OrderLine.DoesNotExist:\n        unit_price = variant.get_price(discounts)\n        unit_price = TaxedMoney(net=unit_price, gross=unit_price)\n        product = variant.product\n        product_name = str(product)\n        variant_name = str(variant)\n        translated_product_name = str(product.translated)\n        translated_variant_name = str(variant.translated)\n        if translated_product_name == product_name:\n            translated_product_name = \"\"\n        if translated_variant_name == variant_name:\n            translated_variant_name = \"\"\n        line = order.lines.create(\n            product_name=product_name,\n            variant_name=variant_name,\n            translated_product_name=translated_product_name,\n            translated_variant_name=translated_variant_name,\n            product_sku=variant.sku,\n            is_shipping_required=variant.is_shipping_required(),\n            quantity=quantity,\n            unit_price=unit_price,\n            variant=variant,\n        )\n        manager = get_extensions_manager()\n        unit_price = manager.calculate_order_line_unit(line)\n        line.unit_price = unit_price\n        line.tax_rate = unit_price.tax / unit_price.net\n        line.save(\n            update_fields=[\n                \"currency\",\n                \"unit_price_net_amount\",\n                \"unit_price_gross_amount\",\n                \"tax_rate\",\n            ]\n        )\n\n    if variant.track_inventory and track_inventory:\n        allocate_stock(variant, country, quantity)\n    return line\n\n\ndef add_gift_card_to_order(order, gift_card, total_price_left):\n    \"\"\"Add gift card to order.\n\n    Return a total price left after applying the gift cards.\n    \"\"\"\n    if total_price_left > zero_money(total_price_left.currency):\n        order.gift_cards.add(gift_card)\n        if total_price_left < gift_card.current_balance:\n            gift_card.current_balance = gift_card.current_balance - total_price_left\n            total_price_left = zero_money(total_price_left.currency)\n        else:\n            total_price_left = total_price_left - gift_card.current_balance\n            gift_card.current_balance_amount = 0\n        gift_card.last_used_on = timezone.now()\n        gift_card.save(update_fields=[\"current_balance_amount\", \"last_used_on\"])\n    return total_price_left\n\n\ndef change_order_line_quantity(user, line, old_quantity, new_quantity):\n    \"\"\"Change the quantity of ordered items in a order line.\"\"\"\n    if new_quantity:\n        line.quantity = new_quantity\n        line.save(update_fields=[\"quantity\"])\n    else:\n        delete_order_line(line)\n\n    quantity_diff = old_quantity - new_quantity\n\n    # Create the removal event\n    if quantity_diff > 0:\n        events.draft_order_removed_products_event(\n            order=line.order, user=user, order_lines=[(quantity_diff, line)]\n        )\n    elif quantity_diff < 0:\n        events.draft_order_added_products_event(\n            order=line.order, user=user, order_lines=[(quantity_diff * -1, line)]\n        )\n\n\ndef delete_order_line(line):\n    \"\"\"Delete an order line from an order.\"\"\"\n    line.delete()\n\n\ndef restock_order_lines(order):\n    \"\"\"Return ordered products to corresponding stocks.\"\"\"\n    country = get_order_country(order)\n\n    for line in order:\n        if line.variant and line.variant.track_inventory:\n            if line.quantity_unfulfilled > 0:\n                deallocate_stock(line.variant, country, line.quantity_unfulfilled)\n            if line.quantity_fulfilled > 0:\n                increase_stock(line.variant, country, line.quantity_fulfilled)\n\n        if line.quantity_fulfilled > 0:\n            line.quantity_fulfilled = 0\n            line.save(update_fields=[\"quantity_fulfilled\"])\n\n\ndef restock_fulfillment_lines(fulfillment):\n    \"\"\"Return fulfilled products to corresponding stocks.\"\"\"\n    country = get_order_country(fulfillment.order)\n    for line in fulfillment:\n        if line.order_line.variant and line.order_line.variant.track_inventory:\n            increase_stock(\n                line.order_line.variant, country, line.quantity, allocate=True\n            )\n\n\ndef sum_order_totals(qs):\n    zero = Money(0, currency=settings.DEFAULT_CURRENCY)\n    taxed_zero = TaxedMoney(zero, zero)\n    return sum([order.total for order in qs], taxed_zero)\n\n\ndef get_valid_shipping_methods_for_order(order: Order):\n    return ShippingMethod.objects.applicable_shipping_methods_for_instance(\n        order, price=order.get_subtotal().gross\n    )\n\n\ndef get_products_voucher_discount_for_order(voucher: Voucher) -> Money:\n    \"\"\"Calculate products discount value for a voucher, depending on its type.\"\"\"\n    prices = None\n    if not prices:\n        msg = \"This offer is only valid for selected items.\"\n        raise NotApplicable(msg)\n    return get_products_voucher_discount(voucher, prices)\n\n\ndef get_voucher_discount_for_order(order: Order) -> Money:\n    \"\"\"Calculate discount value depending on voucher and discount types.\n\n    Raise NotApplicable if voucher of given type cannot be applied.\n    \"\"\"\n    if not order.voucher:\n        return zero_money(order.currency)\n    validate_voucher_in_order(order)\n    subtotal = order.get_subtotal()\n    if order.voucher.type == VoucherType.ENTIRE_ORDER:\n        return order.voucher.get_discount_amount_for(subtotal.gross)\n    if order.voucher.type == VoucherType.SHIPPING:\n        return order.voucher.get_discount_amount_for(order.shipping_price)\n    if order.voucher.type == VoucherType.SPECIFIC_PRODUCT:\n        return get_products_voucher_discount_for_order(order.voucher)\n    raise NotImplementedError(\"Unknown discount type\")\n\n\ndef match_orders_with_new_user(user: User) -> None:\n    Order.objects.confirmed().filter(user_email=user.email, user=None).update(user=user)\n", "path": "saleor/order/utils.py"}], "after_files": [{"content": "from functools import wraps\nfrom uuid import uuid4\n\nfrom django.conf import settings\nfrom django.contrib.staticfiles import finders as static_finders\nfrom django.core.files.base import ContentFile\nfrom django.core.files.storage import default_storage\nfrom django.db import transaction\nfrom django.template.loader import get_template\nfrom django.utils import timezone\nfrom prices import Money, TaxedMoney\nfrom weasyprint import HTML\n\nfrom ..account.models import User\nfrom ..core.taxes import zero_money\nfrom ..core.weight import zero_weight\nfrom ..discount.models import NotApplicable, Voucher, VoucherType\nfrom ..discount.utils import get_products_voucher_discount, validate_voucher_in_order\nfrom ..extensions.manager import get_extensions_manager\nfrom ..order import OrderStatus\nfrom ..order.models import Order, OrderLine\nfrom ..product.utils.digital_products import get_default_digital_content_settings\nfrom ..shipping.models import ShippingMethod\nfrom ..warehouse.availability import check_stock_quantity\nfrom ..warehouse.management import allocate_stock, deallocate_stock, increase_stock\nfrom . import events\n\n\ndef get_order_country(order: Order) -> str:\n    \"\"\"Return country to which order will be shipped.\"\"\"\n    address = order.billing_address\n    if order.is_shipping_required():\n        address = order.shipping_address\n    if address is None:\n        return settings.DEFAULT_COUNTRY\n    return address.country.code\n\n\ndef order_line_needs_automatic_fulfillment(line: OrderLine) -> bool:\n    \"\"\"Check if given line is digital and should be automatically fulfilled.\"\"\"\n    digital_content_settings = get_default_digital_content_settings()\n    default_automatic_fulfillment = digital_content_settings[\"automatic_fulfillment\"]\n    content = line.variant.digital_content if line.variant else None\n    if not content:\n        return False\n    if default_automatic_fulfillment and content.use_default_settings:\n        return True\n    if content.automatic_fulfillment:\n        return True\n    return False\n\n\ndef order_needs_automatic_fullfilment(order: Order) -> bool:\n    \"\"\"Check if order has digital products which should be automatically fulfilled.\"\"\"\n    for line in order.lines.digital():\n        if order_line_needs_automatic_fulfillment(line):\n            return True\n    return False\n\n\ndef update_voucher_discount(func):\n    \"\"\"Recalculate order discount amount based on order voucher.\"\"\"\n\n    @wraps(func)\n    def decorator(*args, **kwargs):\n        if kwargs.pop(\"update_voucher_discount\", True):\n            order = args[0]\n            try:\n                discount = get_voucher_discount_for_order(order)\n            except NotApplicable:\n                discount = zero_money(order.currency)\n            order.discount = discount\n        return func(*args, **kwargs)\n\n    return decorator\n\n\n@update_voucher_discount\ndef recalculate_order(order: Order, **kwargs):\n    \"\"\"Recalculate and assign total price of order.\n\n    Total price is a sum of items in order and order shipping price minus\n    discount amount.\n\n    Voucher discount amount is recalculated by default. To avoid this, pass\n    update_voucher_discount argument set to False.\n    \"\"\"\n    # avoid using prefetched order lines\n    lines = [OrderLine.objects.get(pk=line.pk) for line in order]\n    prices = [line.get_total() for line in lines]\n    total = sum(prices, order.shipping_price)\n    # discount amount can't be greater than order total\n    order.discount_amount = min(order.discount_amount, total.gross.amount)\n    if order.discount:\n        total -= order.discount\n    order.total = total\n    order.save(\n        update_fields=[\n            \"discount_amount\",\n            \"total_net_amount\",\n            \"total_gross_amount\",\n            \"currency\",\n        ]\n    )\n    recalculate_order_weight(order)\n\n\ndef recalculate_order_weight(order):\n    \"\"\"Recalculate order weights.\"\"\"\n    weight = zero_weight()\n    for line in order:\n        if line.variant:\n            weight += line.variant.get_weight() * line.quantity\n    order.weight = weight\n    order.save(update_fields=[\"weight\"])\n\n\ndef update_order_prices(order, discounts):\n    \"\"\"Update prices in order with given discounts and proper taxes.\"\"\"\n    manager = get_extensions_manager()\n    for line in order:  # type: OrderLine\n        if line.variant:\n            unit_price = line.variant.get_price(discounts)\n            unit_price = TaxedMoney(unit_price, unit_price)\n            line.unit_price = unit_price\n            line.save(\n                update_fields=[\n                    \"currency\",\n                    \"unit_price_net_amount\",\n                    \"unit_price_gross_amount\",\n                ]\n            )\n\n            price = manager.calculate_order_line_unit(line)\n            if price != line.unit_price:\n                line.unit_price = price\n                if price.tax and price.net:\n                    line.tax_rate = price.tax / price.net\n                line.save()\n\n    if order.shipping_method:\n        order.shipping_price = manager.calculate_order_shipping(order)\n        order.save(\n            update_fields=[\n                \"shipping_price_net_amount\",\n                \"shipping_price_gross_amount\",\n                \"currency\",\n            ]\n        )\n\n    recalculate_order(order)\n\n\ndef update_order_status(order):\n    \"\"\"Update order status depending on fulfillments.\"\"\"\n    quantity_fulfilled = order.quantity_fulfilled\n    total_quantity = order.get_total_quantity()\n\n    if quantity_fulfilled <= 0:\n        status = OrderStatus.UNFULFILLED\n    elif quantity_fulfilled < total_quantity:\n        status = OrderStatus.PARTIALLY_FULFILLED\n    else:\n        status = OrderStatus.FULFILLED\n\n    if status != order.status:\n        order.status = status\n        order.save(update_fields=[\"status\"])\n\n\n@transaction.atomic\ndef add_variant_to_order(\n    order,\n    variant,\n    quantity,\n    discounts=None,\n    allow_overselling=False,\n    track_inventory=True,\n):\n    \"\"\"Add total_quantity of variant to order.\n\n    Returns an order line the variant was added to.\n\n    By default, raises InsufficientStock exception if  quantity could not be\n    fulfilled. This can be disabled by setting `allow_overselling` to True.\n    \"\"\"\n    country = get_order_country(order)\n    if not allow_overselling:\n        check_stock_quantity(variant, country, quantity)\n\n    try:\n        line = order.lines.get(variant=variant)\n        line.quantity += quantity\n        line.save(update_fields=[\"quantity\"])\n    except OrderLine.DoesNotExist:\n        unit_price = variant.get_price(discounts)\n        unit_price = TaxedMoney(net=unit_price, gross=unit_price)\n        product = variant.product\n        product_name = str(product)\n        variant_name = str(variant)\n        translated_product_name = str(product.translated)\n        translated_variant_name = str(variant.translated)\n        if translated_product_name == product_name:\n            translated_product_name = \"\"\n        if translated_variant_name == variant_name:\n            translated_variant_name = \"\"\n        line = order.lines.create(\n            product_name=product_name,\n            variant_name=variant_name,\n            translated_product_name=translated_product_name,\n            translated_variant_name=translated_variant_name,\n            product_sku=variant.sku,\n            is_shipping_required=variant.is_shipping_required(),\n            quantity=quantity,\n            unit_price=unit_price,\n            variant=variant,\n        )\n        manager = get_extensions_manager()\n        unit_price = manager.calculate_order_line_unit(line)\n        line.unit_price = unit_price\n        line.tax_rate = unit_price.tax / unit_price.net\n        line.save(\n            update_fields=[\n                \"currency\",\n                \"unit_price_net_amount\",\n                \"unit_price_gross_amount\",\n                \"tax_rate\",\n            ]\n        )\n\n    if variant.track_inventory and track_inventory:\n        allocate_stock(variant, country, quantity)\n    return line\n\n\ndef add_gift_card_to_order(order, gift_card, total_price_left):\n    \"\"\"Add gift card to order.\n\n    Return a total price left after applying the gift cards.\n    \"\"\"\n    if total_price_left > zero_money(total_price_left.currency):\n        order.gift_cards.add(gift_card)\n        if total_price_left < gift_card.current_balance:\n            gift_card.current_balance = gift_card.current_balance - total_price_left\n            total_price_left = zero_money(total_price_left.currency)\n        else:\n            total_price_left = total_price_left - gift_card.current_balance\n            gift_card.current_balance_amount = 0\n        gift_card.last_used_on = timezone.now()\n        gift_card.save(update_fields=[\"current_balance_amount\", \"last_used_on\"])\n    return total_price_left\n\n\ndef change_order_line_quantity(user, line, old_quantity, new_quantity):\n    \"\"\"Change the quantity of ordered items in a order line.\"\"\"\n    if new_quantity:\n        line.quantity = new_quantity\n        line.save(update_fields=[\"quantity\"])\n    else:\n        delete_order_line(line)\n\n    quantity_diff = old_quantity - new_quantity\n\n    # Create the removal event\n    if quantity_diff > 0:\n        events.draft_order_removed_products_event(\n            order=line.order, user=user, order_lines=[(quantity_diff, line)]\n        )\n    elif quantity_diff < 0:\n        events.draft_order_added_products_event(\n            order=line.order, user=user, order_lines=[(quantity_diff * -1, line)]\n        )\n\n\ndef delete_order_line(line):\n    \"\"\"Delete an order line from an order.\"\"\"\n    line.delete()\n\n\ndef restock_order_lines(order):\n    \"\"\"Return ordered products to corresponding stocks.\"\"\"\n    country = get_order_country(order)\n\n    for line in order:\n        if line.variant and line.variant.track_inventory:\n            if line.quantity_unfulfilled > 0:\n                deallocate_stock(line.variant, country, line.quantity_unfulfilled)\n            if line.quantity_fulfilled > 0:\n                increase_stock(line.variant, country, line.quantity_fulfilled)\n\n        if line.quantity_fulfilled > 0:\n            line.quantity_fulfilled = 0\n            line.save(update_fields=[\"quantity_fulfilled\"])\n\n\ndef restock_fulfillment_lines(fulfillment):\n    \"\"\"Return fulfilled products to corresponding stocks.\"\"\"\n    country = get_order_country(fulfillment.order)\n    for line in fulfillment:\n        if line.order_line.variant and line.order_line.variant.track_inventory:\n            increase_stock(\n                line.order_line.variant, country, line.quantity, allocate=True\n            )\n\n\ndef sum_order_totals(qs):\n    zero = Money(0, currency=settings.DEFAULT_CURRENCY)\n    taxed_zero = TaxedMoney(zero, zero)\n    return sum([order.total for order in qs], taxed_zero)\n\n\ndef get_valid_shipping_methods_for_order(order: Order):\n    return ShippingMethod.objects.applicable_shipping_methods_for_instance(\n        order, price=order.get_subtotal().gross\n    )\n\n\ndef get_products_voucher_discount_for_order(voucher: Voucher) -> Money:\n    \"\"\"Calculate products discount value for a voucher, depending on its type.\"\"\"\n    prices = None\n    if not prices:\n        msg = \"This offer is only valid for selected items.\"\n        raise NotApplicable(msg)\n    return get_products_voucher_discount(voucher, prices)\n\n\ndef get_voucher_discount_for_order(order: Order) -> Money:\n    \"\"\"Calculate discount value depending on voucher and discount types.\n\n    Raise NotApplicable if voucher of given type cannot be applied.\n    \"\"\"\n    if not order.voucher:\n        return zero_money(order.currency)\n    validate_voucher_in_order(order)\n    subtotal = order.get_subtotal()\n    if order.voucher.type == VoucherType.ENTIRE_ORDER:\n        return order.voucher.get_discount_amount_for(subtotal.gross)\n    if order.voucher.type == VoucherType.SHIPPING:\n        return order.voucher.get_discount_amount_for(order.shipping_price)\n    if order.voucher.type == VoucherType.SPECIFIC_PRODUCT:\n        return get_products_voucher_discount_for_order(order.voucher)\n    raise NotImplementedError(\"Unknown discount type\")\n\n\ndef match_orders_with_new_user(user: User) -> None:\n    Order.objects.confirmed().filter(user_email=user.email, user=None).update(user=user)\n\n\ndef generate_invoice_pdf_for_order(invoice):\n    logo_path = static_finders.find(\"images/logo-light.svg\")\n    rendered_template = get_template(\"invoice.html\").render(\n        {\"invoice\": invoice, \"order\": invoice.order, \"logo_path\": f\"file://{logo_path}\"}\n    )\n    content_file = ContentFile(HTML(string=rendered_template).write_pdf())\n    return default_storage.save(f\"{uuid4()}.pdf\", content_file)\n", "path": "saleor/order/utils.py"}]}