from decimal import Decimal from typing import Any from django.conf import settings from django.core.exceptions import ValidationError from django.core.mail import EmailMultiAlternatives from django.db import transaction from django.template.loader import render_to_string from django.utils import formats from items.models import Item from orders.models import Cart, CartItem, Order, OrderItem DELIVERY_PRICE = Decimal("500.00") FLOOR_LIFT_PRICE = Decimal("45.00") ZERO_MONEY = Decimal("0.00") def format_currency(value: Decimal) -> str: return f"{formats.number_format(value, 2)} руб." def calculate_checkout_totals(cart_items: list[CartItem], delivery_method: str) -> dict[str, Decimal]: products_total = sum( (cart_item.item.price * cart_item.quantity for cart_item in cart_items), start=ZERO_MONEY, ) discount_total = ZERO_MONEY delivery_total = DELIVERY_PRICE if delivery_method == Order.DeliveryMethod.COURIER else ZERO_MONEY floor_lift_total = FLOOR_LIFT_PRICE if delivery_method == Order.DeliveryMethod.COURIER else ZERO_MONEY total = products_total - discount_total + delivery_total + floor_lift_total return { "products_total": products_total, "discount_total": discount_total, "delivery_total": delivery_total, "floor_lift_total": floor_lift_total, "total": total, } def get_or_create_cart(user: Any) -> Cart: cart, _ = Cart.objects.get_or_create(user=user) return cart def get_user_cart_item_ids(user: Any) -> set[int]: if not getattr(user, "is_authenticated", False): return set() return set( CartItem.objects.filter(cart__user=user).values_list("item_id", flat=True) ) def get_user_cart(user: Any) -> Cart | None: if not getattr(user, "is_authenticated", False): return None return ( Cart.objects.filter(user=user) .prefetch_related("items__item__images") .select_related("user") .first() ) def get_user_cart_items(user: Any) -> list[CartItem]: cart = get_user_cart(user) if not cart: return [] return list( cart.items .select_related("item__category", "item__subcategory", "selected_color") .prefetch_related("item__images") ) def add_item_to_cart(user: Any, item: Item, quantity: int = 1, color=None) -> tuple[CartItem, bool]: if quantity < 1: raise ValidationError("Количество товара должно быть не меньше 1.") if not item.is_available: raise ValidationError("Недоступный товар нельзя добавить в корзину.") cart = get_or_create_cart(user) cart_item, created = CartItem.objects.get_or_create( cart=cart, item=item, defaults={"quantity": quantity, "selected_color": color}, ) if not created and cart_item.selected_color_id != (color.pk if color else None): cart_item.selected_color = color cart_item.save(update_fields=["selected_color", "updated_at"]) return cart_item, created def remove_item_from_cart(user: Any, item_id: int) -> bool: deleted, _ = CartItem.objects.filter(cart__user=user, item_id=item_id).delete() return bool(deleted) def update_cart_item_quantity(user: Any, item_id: int, quantity: int) -> CartItem: if quantity < 1: raise ValidationError("Количество товара должно быть не меньше 1.") cart_item = CartItem.objects.select_related("item").get(cart__user=user, item_id=item_id) cart_item.quantity = quantity cart_item.save(update_fields=["quantity", "updated_at"]) return cart_item @transaction.atomic def create_order_from_cart(user: Any, cleaned_data: dict[str, Any]) -> Order: cart = get_user_cart(user) if not cart: raise ValidationError("Корзина пуста.") cart_items = list(cart.items.select_related("item")) if not cart_items: raise ValidationError("Корзина пуста.") totals = calculate_checkout_totals(cart_items, cleaned_data["delivery_method"]) order = Order.objects.create( user=user, customer_name=cleaned_data["customer_name"], phone=cleaned_data["phone"], email=cleaned_data["email"], delivery_method=cleaned_data["delivery_method"], region=cleaned_data.get("region", ""), address=cleaned_data.get("address", ""), payment_method=cleaned_data["payment_method"], promo_code=cleaned_data.get("promo_code", ""), products_total=totals["products_total"], discount_total=totals["discount_total"], delivery_total=totals["delivery_total"], floor_lift_total=totals["floor_lift_total"], total=totals["total"], status=Order.Status.PAID, ) OrderItem.objects.bulk_create( [ OrderItem( order=order, item=cart_item.item, item_name=cart_item.item.name, item_slug=cart_item.item.slug, item_price=cart_item.item.price, quantity=cart_item.quantity, line_total=cart_item.item.price * cart_item.quantity, ) for cart_item in cart_items ] ) cart.items.all().delete() return order def send_order_confirmation_email(order: Order) -> None: order = ( Order.objects.select_related("user") .prefetch_related("items") .get(pk=order.pk) ) context = { "order": order, "site_name": getattr(settings, "SITE_NAME", "SantehRay"), } subject = render_to_string("orders/emails/order_confirmation_subject.txt", context).strip() text_body = render_to_string("orders/emails/order_confirmation_email.txt", context) html_body = render_to_string("orders/emails/order_confirmation_email.html", context) message = EmailMultiAlternatives( subject=subject, body=text_body, from_email=settings.DEFAULT_FROM_EMAIL, to=[order.email], bcc=[settings.ADMINISTRATOR_EMAIL], ) message.attach_alternative(html_body, "text/html") message.send()