Files
santeh-ray/orders/services.py
Azimkin ad962d946b
All checks were successful
Build and Push Docker Image / build (push) Successful in 43s
feat: update order payment methods and refine profile edit functionality
- Replaced `Order.PaymentMethod` options with "cash" and "card" while adjusting related code and templates
- Set "cash" as the default payment method for orders and checkout forms
- Added profile editing functionality with `ProfileForm`, allowing users to update names
- Improved feedback messages across profile and order management templates
- Updated catalog to handle empty filter results with a "reset filters" option
2026-05-30 12:25:19 +02:00

186 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()