Files
santeh-ray/orders/services.py
Azimkin 56af404dc9
All checks were successful
Build and Push Docker Image / build (push) Successful in 38s
feat: enable color selection for cart items and enhance UI integration
- Added `selected_color` field to `CartItem` model with migration
- Updated color swatch behavior on product detail page with selection logic and disabled button handling
- Improved cart templates to display selected color alongside item details
- Modified `add_item_to_cart` service and `add` view to handle color selection
- Enhanced styles for active swatches and cart item color display
2026-05-26 03:12:00 +02:00

185 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()