diff --git a/backend/app/repositories/__pycache__/order_repo.cpython-310.pyc b/backend/app/repositories/__pycache__/order_repo.cpython-310.pyc index 81df5aa..00f147d 100644 Binary files a/backend/app/repositories/__pycache__/order_repo.cpython-310.pyc and b/backend/app/repositories/__pycache__/order_repo.cpython-310.pyc differ diff --git a/backend/app/repositories/__pycache__/user_repo.cpython-310.pyc b/backend/app/repositories/__pycache__/user_repo.cpython-310.pyc index 362c92b..44a00c6 100644 Binary files a/backend/app/repositories/__pycache__/user_repo.cpython-310.pyc and b/backend/app/repositories/__pycache__/user_repo.cpython-310.pyc differ diff --git a/backend/app/repositories/order_repo.py b/backend/app/repositories/order_repo.py index 1253c6d..195e0f8 100644 --- a/backend/app/repositories/order_repo.py +++ b/backend/app/repositories/order_repo.py @@ -172,160 +172,120 @@ def get_all_orders( def create_order(db: Session, order: OrderCreate, user_id: int) -> Order: # Проверяем, что адрес доставки существует и принадлежит пользователю, если указан + print(f"Начало создания заказа для пользователя {user_id}") + print(f"Данные заказа: {order}") + if order.shipping_address_id: address = db.query(UserAddress).filter( UserAddress.id == order.shipping_address_id, UserAddress.user_id == user_id ).first() + if not address: + print(f"Адрес доставки {order.shipping_address_id} не найден для пользователя {user_id}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail="Адрес доставки не найден или не принадлежит пользователю" + detail="Указанный адрес доставки не найден" ) + print(f"Адрес доставки найден: {address}") - # Получаем элементы заказа - order_items = [] - total_amount = 0 + # Создаем новый заказ + new_order = Order( + user_id=user_id, + status=OrderStatus.PENDING, + shipping_address_id=order.shipping_address_id, + payment_method=order.payment_method, + notes=order.notes, + total_amount=0 # Будет обновлено после добавления товаров + ) - # Если указаны элементы корзины, используем их + db.add(new_order) + db.flush() # Получаем ID заказа + + print(f"Создан заказ с ID: {new_order.id}") + + # Получаем элементы корзины пользователя + cart_items = [] + + # Если указаны конкретные элементы корзины if order.cart_items: + print(f"Используем указанные элементы корзины: {order.cart_items}") cart_items = db.query(CartItem).filter( CartItem.id.in_(order.cart_items), CartItem.user_id == user_id ).all() - - if len(cart_items) != len(order.cart_items): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Некоторые элементы корзины не найдены или не принадлежат пользователю" - ) - - for cart_item in cart_items: - # Получаем вариант и продукт - variant = db.query(ProductVariant).filter(ProductVariant.id == cart_item.variant_id).first() - if not variant: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Вариант продукта с ID {cart_item.variant_id} не найден" - ) - - product = db.query(Product).filter(Product.id == variant.product_id).first() - if not product: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Продукт для варианта с ID {cart_item.variant_id} не найден" - ) - - # Рассчитываем цену - price = product.discount_price if product.discount_price else product.price - price += variant.price_adjustment - - # Создаем элемент заказа - order_item = OrderItem( - variant_id=cart_item.variant_id, - quantity=cart_item.quantity, - price=price - ) - order_items.append(order_item) - - # Обновляем общую сумму - total_amount += price * cart_item.quantity - - # Если указаны прямые элементы заказа, используем их + # Если указаны прямые элементы заказа elif order.items: - for item in order.items: + print(f"Используем прямые элементы заказа: {order.items}") + # Создаем элементы заказа напрямую + for item_data in order.items: # Проверяем, что вариант существует - variant = db.query(ProductVariant).filter(ProductVariant.id == item.variant_id).first() + variant = db.query(ProductVariant).filter(ProductVariant.id == item_data.variant_id).first() if not variant: + db.rollback() raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"Вариант продукта с ID {item.variant_id} не найден" + detail=f"Вариант товара с ID {item_data.variant_id} не найден" ) # Создаем элемент заказа order_item = OrderItem( - variant_id=item.variant_id, - quantity=item.quantity, - price=item.price + order_id=new_order.id, + product_id=variant.product_id, + variant_id=variant.id, + quantity=item_data.quantity, + price=item_data.price ) - order_items.append(order_item) - - # Обновляем общую сумму - total_amount += item.price * item.quantity - + db.add(order_item) + new_order.total_amount += order_item.price * order_item.quantity + # Иначе используем все элементы корзины пользователя else: - # Если не указаны ни элементы корзины, ни прямые элементы заказа, используем всю корзину пользователя - cart_items = get_user_cart(db, user_id) - - if not cart_items: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Корзина пуста" - ) - + print(f"Используем все элементы корзины пользователя") + cart_items = db.query(CartItem).filter(CartItem.user_id == user_id).all() + + # Если используем элементы корзины + if cart_items: + print(f"Найдено {len(cart_items)} элементов корзины") + # Создаем элементы заказа из элементов корзины for cart_item in cart_items: - # Получаем вариант и продукт + # Получаем вариант товара и его цену variant = db.query(ProductVariant).filter(ProductVariant.id == cart_item.variant_id).first() if not variant: + db.rollback() raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"Вариант продукта с ID {cart_item.variant_id} не найден" + detail=f"Вариант товара с ID {cart_item.variant_id} не найден" ) - product = db.query(Product).filter(Product.id == variant.product_id).first() - if not product: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Продукт для варианта с ID {cart_item.variant_id} не найден" - ) - - # Рассчитываем цену - price = product.discount_price if product.discount_price else product.price - price += variant.price_adjustment + # Определяем цену (используем скидочную цену, если она есть) + price = variant.discount_price if variant.discount_price else variant.price # Создаем элемент заказа order_item = OrderItem( - variant_id=cart_item.variant_id, + order_id=new_order.id, + variant_id=variant.id, quantity=cart_item.quantity, price=price ) - order_items.append(order_item) + db.add(order_item) - # Обновляем общую сумму - total_amount += price * cart_item.quantity - - # Создаем заказ - db_order = Order( - user_id=user_id, - status=OrderStatus.PENDING, - total_amount=total_amount, - shipping_address_id=order.shipping_address_id, - payment_method=order.payment_method, - notes=order.notes - ) + # Обновляем общую сумму заказа + new_order.total_amount += price * cart_item.quantity + + # Удаляем элемент из корзины + db.delete(cart_item) try: - # Добавляем заказ - db.add(db_order) - db.flush() # Получаем ID заказа, не фиксируя транзакцию - - # Добавляем элементы заказа - for item in order_items: - item.order_id = db_order.id - db.add(item) - - # Очищаем корзину, если заказ создан из корзины - if not order.items: - db.query(CartItem).filter(CartItem.user_id == user_id).delete() - db.commit() - db.refresh(db_order) - return db_order - except IntegrityError: + db.refresh(new_order) + print(f"Заказ успешно создан: {new_order.id}, общая сумма: {new_order.total_amount}") + return new_order + except Exception as e: db.rollback() + print(f"Ошибка при создании заказа: {str(e)}") raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="Ошибка при создании заказа" + detail=f"Ошибка при создании заказа: {str(e)}" ) @@ -437,14 +397,14 @@ def get_cart_with_product_details(db: Session, user_id: int) -> List[Dict[str, A ).first() # Рассчитываем цену - price = product.discount_price if product.discount_price else product.price - price += variant.price_adjustment + price = variant.discount_price if variant.discount_price else variant.price # Формируем результат result.append({ "id": item.id, "user_id": item.user_id, "variant_id": item.variant_id, + "slug": product.slug, "quantity": item.quantity, "created_at": item.created_at, "updated_at": item.updated_at, @@ -453,7 +413,6 @@ def get_cart_with_product_details(db: Session, user_id: int) -> List[Dict[str, A "product_price": price, "product_image": image.image_url if image else None, "variant_name": variant.name, - "variant_price_adjustment": variant.price_adjustment, "total_price": price * item.quantity }) diff --git a/backend/app/repositories/user_repo.py b/backend/app/repositories/user_repo.py index eb8c092..8cf48f5 100644 --- a/backend/app/repositories/user_repo.py +++ b/backend/app/repositories/user_repo.py @@ -134,7 +134,7 @@ def get_user_addresses(db: Session, user_id: int) -> List[UserAddress]: return db.query(UserAddress).filter(UserAddress.user_id == user_id).all() -def create_address(db: Session, address: AddressCreate, user_id: int) -> UserAddress: +def create_address(db: Session, address: AddressCreate, user_id: int): # Если новый адрес помечен как дефолтный, сбрасываем дефолтный статус у других адресов пользователя if address.is_default: db.query(UserAddress).filter( @@ -157,6 +157,8 @@ def create_address(db: Session, address: AddressCreate, user_id: int) -> UserAdd db.add(db_address) db.commit() db.refresh(db_address) + print(f"Адрес успешно создан: {db_address}") + db_address.user_id = user_id return db_address except Exception: db.rollback() diff --git a/backend/app/routers/__pycache__/cart_router.cpython-310.pyc b/backend/app/routers/__pycache__/cart_router.cpython-310.pyc index 9536858..7635dc9 100644 Binary files a/backend/app/routers/__pycache__/cart_router.cpython-310.pyc and b/backend/app/routers/__pycache__/cart_router.cpython-310.pyc differ diff --git a/backend/app/routers/cart_router.py b/backend/app/routers/cart_router.py index 1ef14e5..7c93262 100644 --- a/backend/app/routers/cart_router.py +++ b/backend/app/routers/cart_router.py @@ -11,12 +11,21 @@ from app.models.user_models import User as UserModel cart_router = APIRouter(prefix="/cart", tags=["Корзина"]) @cart_router.post("/items", response_model=Dict[str, Any]) -async def add_to_cart_endpoint(cart_item: CartItemCreate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): +async def add_to_cart_endpoint( + cart_item: CartItemCreate, + current_user: UserModel = Depends(get_current_active_user), + db: Session = Depends(get_db) + ): return services.add_to_cart(db, current_user.id, cart_item) @cart_router.put("/items/{cart_item_id}", response_model=Dict[str, Any]) -async def update_cart_item_endpoint(cart_item_id: int, cart_item: CartItemUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): +async def update_cart_item_endpoint( + cart_item_id: int, + cart_item: CartItemUpdate, + current_user: UserModel = Depends(get_current_active_user), + db: Session = Depends(get_db) + ): return services.update_cart_item(db, current_user.id, cart_item_id, cart_item) diff --git a/backend/app/schemas/__pycache__/order_schemas.cpython-310.pyc b/backend/app/schemas/__pycache__/order_schemas.cpython-310.pyc index 7e408c6..53cf3c4 100644 Binary files a/backend/app/schemas/__pycache__/order_schemas.cpython-310.pyc and b/backend/app/schemas/__pycache__/order_schemas.cpython-310.pyc differ diff --git a/backend/app/schemas/__pycache__/user_schemas.cpython-310.pyc b/backend/app/schemas/__pycache__/user_schemas.cpython-310.pyc index 302ee9b..d28c5e4 100644 Binary files a/backend/app/schemas/__pycache__/user_schemas.cpython-310.pyc and b/backend/app/schemas/__pycache__/user_schemas.cpython-310.pyc differ diff --git a/backend/app/schemas/order_schemas.py b/backend/app/schemas/order_schemas.py index beb40da..d9939a7 100644 --- a/backend/app/schemas/order_schemas.py +++ b/backend/app/schemas/order_schemas.py @@ -34,7 +34,6 @@ class CartItemWithProduct(CartItem): product_price: float product_image: Optional[str] = None variant_name: str - variant_price_adjustment: float total_price: float @@ -112,7 +111,6 @@ class CartItemWithDetails(BaseModel): product_price: float product_image: Optional[str] = None variant_name: str - variant_price_adjustment: float total_price: float diff --git a/backend/app/schemas/user_schemas.py b/backend/app/schemas/user_schemas.py index 41d45b1..c5e611c 100644 --- a/backend/app/schemas/user_schemas.py +++ b/backend/app/schemas/user_schemas.py @@ -33,7 +33,7 @@ class Address(AddressBase): updated_at: Optional[datetime] = None class Config: - orm_mode = True + from_attributes = True # Базовые схемы для пользователя @@ -81,7 +81,7 @@ class User(UserBase): addresses: List[Address] = [] class Config: - orm_mode = True + from_attributes = True class UserInDB(User): diff --git a/backend/app/services/__pycache__/order_service.cpython-310.pyc b/backend/app/services/__pycache__/order_service.cpython-310.pyc index 2d9ba8a..f65da0c 100644 Binary files a/backend/app/services/__pycache__/order_service.cpython-310.pyc and b/backend/app/services/__pycache__/order_service.cpython-310.pyc differ diff --git a/backend/app/services/__pycache__/user_service.cpython-310.pyc b/backend/app/services/__pycache__/user_service.cpython-310.pyc index b136a1e..93eaf27 100644 Binary files a/backend/app/services/__pycache__/user_service.cpython-310.pyc and b/backend/app/services/__pycache__/user_service.cpython-310.pyc differ diff --git a/backend/app/services/order_service.py b/backend/app/services/order_service.py index 68a0d79..4d310d3 100644 --- a/backend/app/services/order_service.py +++ b/backend/app/services/order_service.py @@ -66,19 +66,28 @@ def get_cart(db: Session, user_id: int) -> Dict[str, Any]: def create_order(db: Session, user_id: int, order: OrderCreate) -> Dict[str, Any]: from app.schemas.order_schemas import Order as OrderSchema - new_order = order_repo.create_order(db, order, user_id) + print(f"Создание заказа для пользователя {user_id}: {order}") - # Логируем событие создания заказа - log_data = AnalyticsLogCreate( - user_id=user_id, - event_type="order_created", - additional_data={"order_id": new_order.id, "total_amount": new_order.total_amount} - ) - content_repo.log_analytics_event(db, log_data) - - # Преобразуем объект SQLAlchemy в схему Pydantic - order_schema = OrderSchema.model_validate(new_order) - return {"order": order_schema} + try: + new_order = order_repo.create_order(db, order, user_id) + + print(f"Заказ успешно создан: {new_order.id}") + + # Логируем событие создания заказа + log_data = AnalyticsLogCreate( + user_id=user_id, + event_type="order_created", + additional_data={"order_id": new_order.id, "total_amount": new_order.total_amount} + ) + content_repo.log_analytics_event(db, log_data) + + # Получаем заказ с деталями + order_details = order_repo.get_order_with_details(db, new_order.id) + + return {"order": order_details} + except Exception as e: + print(f"Ошибка при создании заказа: {str(e)}") + raise def get_order(db: Session, user_id: int, order_id: int, is_admin: bool = False) -> Dict[str, Any]: diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index 10cf894..f1fc03f 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -63,7 +63,6 @@ def login_user(db: Session, email: str, password: str) -> Dict[str, Any]: def get_user_profile(db: Session, user_id: int) -> Dict[str, Any]: from app.schemas.user_schemas import User as UserSchema, Address as AddressSchema - from app.schemas.review_schemas import Review as ReviewSchema user = user_repo.get_user(db, user_id) if not user: @@ -72,16 +71,7 @@ def get_user_profile(db: Session, user_id: int) -> Dict[str, Any]: detail="Пользователь не найден" ) - # Получаем адреса пользователя - addresses = user_repo.get_user_addresses(db, user_id) - - # Получаем заказы пользователя - orders = order_repo.get_user_orders(db, user_id) - - # Получаем отзывы пользователя - reviews = review_repo.get_user_reviews(db, user_id) - - # Преобразуем объекты SQLAlchemy в схемы Pydantic + # Преобразуем объект SQLAlchemy в словарь, а затем в схему Pydantic user_dict = { "id": user.id, "email": user.email, @@ -94,28 +84,27 @@ def get_user_profile(db: Session, user_id: int) -> Dict[str, Any]: "updated_at": user.updated_at, "addresses": [] } - user_schema = UserSchema.model_validate(user_dict) - addresses_schema = [AddressSchema.model_validate({ - "id": address.id, - "user_id": address.user_id, - "address_line1": address.address_line1, - "address_line2": address.address_line2, - "city": address.city, - "state": address.state, - "postal_code": address.postal_code, - "country": address.country, - "is_default": address.is_default, - "created_at": address.created_at, - "updated_at": address.updated_at - }) for address in addresses] - reviews_schema = [ReviewSchema.model_validate(review.__dict__) for review in reviews] - return { - "user": user_schema, - "addresses": addresses_schema, - "orders": orders, # Заказы обрабатываются отдельно в сервисе заказов - "reviews": reviews_schema - } + # Преобразуем адреса пользователя + if user.addresses: + for address in user.addresses: + address_dict = { + "id": address.id, + "user_id": address.user_id, + "address_line1": address.address_line1, + "address_line2": address.address_line2, + "city": address.city, + "state": address.state, + "postal_code": address.postal_code, + "country": address.country, + "is_default": address.is_default, + "created_at": address.created_at, + "updated_at": address.updated_at + } + user_dict["addresses"].append(address_dict) + + user_schema = UserSchema.model_validate(user_dict) + return {"user": user_schema} def update_user_profile(db: Session, user_id: int, user_data: UserUpdate) -> Dict[str, Any]: @@ -131,8 +120,24 @@ def add_user_address(db: Session, user_id: int, address: AddressCreate) -> Dict[ from app.schemas.user_schemas import Address as AddressSchema new_address = user_repo.create_address(db, address, user_id) - # Преобразуем объект SQLAlchemy в схему Pydantic - address_schema = AddressSchema.model_validate(new_address) + print(f"Адрес успешно создан: {new_address}") + + # Преобразуем объект SQLAlchemy в словарь, а затем в схему Pydantic + address_dict = { + "id": new_address.id, + "user_id": new_address.user_id, + "address_line1": new_address.address_line1, + "address_line2": new_address.address_line2, + "city": new_address.city, + "state": new_address.state, + "postal_code": new_address.postal_code, + "country": new_address.country, + "is_default": new_address.is_default, + "created_at": new_address.created_at, + "updated_at": new_address.updated_at + } + + address_schema = AddressSchema.model_validate(address_dict) return {"address": address_schema} @@ -140,8 +145,23 @@ def update_user_address(db: Session, user_id: int, address_id: int, address: Add from app.schemas.user_schemas import Address as AddressSchema updated_address = user_repo.update_address(db, address_id, address, user_id) - # Преобразуем объект SQLAlchemy в схему Pydantic - address_schema = AddressSchema.model_validate(updated_address) + + # Преобразуем объект SQLAlchemy в словарь, а затем в схему Pydantic + address_dict = { + "id": updated_address.id, + "user_id": updated_address.user_id, + "address_line1": updated_address.address_line1, + "address_line2": updated_address.address_line2, + "city": updated_address.city, + "state": updated_address.state, + "postal_code": updated_address.postal_code, + "country": updated_address.country, + "is_default": updated_address.is_default, + "created_at": updated_address.created_at, + "updated_at": updated_address.updated_at + } + + address_schema = AddressSchema.model_validate(address_dict) return {"address": address_schema} diff --git a/backend/uploads/products/9/0beaff9a-bec5-4624-bc08-08fa492ce05c.jpg b/backend/uploads/products/9/0beaff9a-bec5-4624-bc08-08fa492ce05c.jpg new file mode 100644 index 0000000..688b480 Binary files /dev/null and b/backend/uploads/products/9/0beaff9a-bec5-4624-bc08-08fa492ce05c.jpg differ diff --git a/backend/uploads/products/9/d62483ea-03fb-4e8b-919e-e1fbf2418ad2.jpg b/backend/uploads/products/9/d62483ea-03fb-4e8b-919e-e1fbf2418ad2.jpg new file mode 100644 index 0000000..5acbc8e Binary files /dev/null and b/backend/uploads/products/9/d62483ea-03fb-4e8b-919e-e1fbf2418ad2.jpg differ diff --git a/frontend/components/AddToCartButton.tsx b/frontend/components/AddToCartButton.tsx new file mode 100644 index 0000000..59f1ef9 --- /dev/null +++ b/frontend/components/AddToCartButton.tsx @@ -0,0 +1,92 @@ +import { useState } from 'react'; +import { useRouter } from 'next/router'; +import { ShoppingCart, Check, AlertCircle } from 'lucide-react'; +import cartService from '../services/cart'; +import authService from '../services/auth'; + +interface AddToCartButtonProps { + variantId: number; + quantity?: number; + className?: string; + onAddToCart?: () => void; +} + +export default function AddToCartButton({ variantId, quantity = 1, className = '', onAddToCart }: AddToCartButtonProps) { + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + + const handleAddToCart = async () => { + // Проверяем, авторизован ли пользователь + if (!authService.isAuthenticated()) { + // Сохраняем текущий URL для редиректа после авторизации + const currentPath = router.asPath; + router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); + return; + } + + setLoading(true); + setError(null); + + try { + await cartService.addToCart({ + variant_id: variantId, + quantity: quantity + }); + + setSuccess(true); + + // Вызываем колбэк, если он передан + if (onAddToCart) { + onAddToCart(); + } + + // Сбрасываем состояние успеха через 2 секунды + setTimeout(() => { + setSuccess(false); + }, 2000); + } catch (err) { + console.error('Ошибка при добавлении в корзину:', err); + setError('Не удалось добавить товар в корзину'); + + // Сбрасываем состояние ошибки через 3 секунды + setTimeout(() => { + setError(null); + }, 3000); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + {error && ( +
+ + {error} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/frontend/components/Footer.tsx b/frontend/components/Footer.tsx index df5dfb9..77614f1 100644 --- a/frontend/components/Footer.tsx +++ b/frontend/components/Footer.tsx @@ -1,14 +1,29 @@ import Link from "next/link"; -import { Facebook, Instagram, Twitter, Youtube } from "lucide-react"; +import { Facebook, Instagram, Twitter, Youtube, ChevronDown, ChevronUp } from "lucide-react"; +import { useState } from "react"; export default function Footer() { + // Состояния для отображения/скрытия разделов на мобильных устройствах + const [helpOpen, setHelpOpen] = useState(false); + const [shopOpen, setShopOpen] = useState(false); + const [aboutOpen, setAboutOpen] = useState(false); + return ( -