261 lines
7.7 KiB
Python
261 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, Form, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.cart import (
|
|
available_stock,
|
|
build_cart_items,
|
|
cart_count,
|
|
get_cart_map,
|
|
save_cart,
|
|
)
|
|
from app.config import get_settings
|
|
from app.database import get_db
|
|
from app.models import Category, Order, Product
|
|
from app.orders import OrderError, create_order_from_cart, pay_and_deliver
|
|
|
|
router = APIRouter()
|
|
settings = get_settings()
|
|
|
|
|
|
def setup(templates: Jinja2Templates) -> None:
|
|
router.templates = templates # type: ignore[attr-defined]
|
|
|
|
|
|
def ctx(request: Request, **kwargs):
|
|
data = {
|
|
"request": request,
|
|
"app_name": settings.app_name,
|
|
"cart_count": cart_count(request),
|
|
}
|
|
data.update(kwargs)
|
|
return data
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
def home(request: Request, db: Session = Depends(get_db)):
|
|
categories = db.scalars(
|
|
select(Category).order_by(Category.sort_order, Category.name)
|
|
).all()
|
|
featured = db.scalars(
|
|
select(Product)
|
|
.where(Product.is_active.is_(True), Product.is_featured.is_(True))
|
|
.order_by(Product.id)
|
|
.limit(4)
|
|
).all()
|
|
products = db.scalars(
|
|
select(Product)
|
|
.where(Product.is_active.is_(True))
|
|
.order_by(Product.is_featured.desc(), Product.id)
|
|
.limit(12)
|
|
).all()
|
|
stock_map = {
|
|
p.id: available_stock(db, p.id) for p in [*featured, *products]
|
|
}
|
|
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"index.html",
|
|
ctx(
|
|
request,
|
|
categories=categories,
|
|
featured=featured,
|
|
products=products,
|
|
stock_map=stock_map,
|
|
),
|
|
)
|
|
|
|
|
|
@router.get("/product/{slug}", response_class=HTMLResponse)
|
|
def product_page(slug: str, request: Request, db: Session = Depends(get_db)):
|
|
product = db.scalar(
|
|
select(Product).where(Product.slug == slug, Product.is_active.is_(True))
|
|
)
|
|
if not product:
|
|
return RedirectResponse("/", status_code=303)
|
|
stock = available_stock(db, product.id)
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"product.html",
|
|
ctx(request, product=product, stock=stock),
|
|
)
|
|
|
|
|
|
@router.post("/cart/add/{product_id}")
|
|
def cart_add(
|
|
product_id: int,
|
|
request: Request,
|
|
quantity: int = Form(1),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
product = db.scalar(
|
|
select(Product).where(Product.id == product_id, Product.is_active.is_(True))
|
|
)
|
|
if not product:
|
|
return RedirectResponse("/", status_code=303)
|
|
|
|
qty = max(1, min(int(quantity or 1), 10))
|
|
stock = available_stock(db, product.id)
|
|
cart = get_cart_map(request)
|
|
current = cart.get(str(product_id), 0)
|
|
cart[str(product_id)] = min(current + qty, max(stock, 0))
|
|
if cart[str(product_id)] <= 0:
|
|
cart.pop(str(product_id), None)
|
|
save_cart(request, cart)
|
|
return RedirectResponse("/cart", status_code=303)
|
|
|
|
|
|
@router.get("/cart", response_class=HTMLResponse)
|
|
def cart_page(request: Request, db: Session = Depends(get_db)):
|
|
items, total = build_cart_items(db, request)
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"cart.html",
|
|
ctx(request, items=items, total=total),
|
|
)
|
|
|
|
|
|
@router.post("/cart/update")
|
|
def cart_update(
|
|
request: Request,
|
|
product_id: int = Form(...),
|
|
quantity: int = Form(...),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
cart = get_cart_map(request)
|
|
pid = str(product_id)
|
|
qty = int(quantity)
|
|
if qty <= 0:
|
|
cart.pop(pid, None)
|
|
else:
|
|
stock = available_stock(db, product_id)
|
|
cart[pid] = min(qty, max(stock, 0))
|
|
if cart[pid] <= 0:
|
|
cart.pop(pid, None)
|
|
save_cart(request, cart)
|
|
return RedirectResponse("/cart", status_code=303)
|
|
|
|
|
|
@router.post("/cart/remove/{product_id}")
|
|
def cart_remove(product_id: int, request: Request):
|
|
cart = get_cart_map(request)
|
|
cart.pop(str(product_id), None)
|
|
save_cart(request, cart)
|
|
return RedirectResponse("/cart", status_code=303)
|
|
|
|
|
|
@router.get("/checkout", response_class=HTMLResponse)
|
|
def checkout_page(request: Request, db: Session = Depends(get_db)):
|
|
items, total = build_cart_items(db, request)
|
|
if not items:
|
|
return RedirectResponse("/cart", status_code=303)
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"checkout.html",
|
|
ctx(request, items=items, total=total, error=None),
|
|
)
|
|
|
|
|
|
@router.post("/checkout", response_class=HTMLResponse)
|
|
def checkout_submit(
|
|
request: Request,
|
|
email: str = Form(...),
|
|
note: str = Form(""),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
items, total = build_cart_items(db, request)
|
|
if not items:
|
|
return RedirectResponse("/cart", status_code=303)
|
|
|
|
cart = get_cart_map(request)
|
|
try:
|
|
order = create_order_from_cart(
|
|
db, email=email, cart=cart, customer_note=note
|
|
)
|
|
order = pay_and_deliver(db, order)
|
|
save_cart(request, {})
|
|
return RedirectResponse(f"/order/{order.public_id}?email={order.email}", status_code=303)
|
|
except OrderError as exc:
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"checkout.html",
|
|
ctx(request, items=items, total=total, error=str(exc)),
|
|
status_code=400,
|
|
)
|
|
|
|
|
|
@router.get("/order/{public_id}", response_class=HTMLResponse)
|
|
def order_page(
|
|
public_id: str,
|
|
request: Request,
|
|
email: str = "",
|
|
db: Session = Depends(get_db),
|
|
):
|
|
order = db.scalar(select(Order).where(Order.public_id == public_id.upper()))
|
|
if not order:
|
|
return RedirectResponse("/", status_code=303)
|
|
if email and email.strip().lower() != order.email:
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"order_locked.html",
|
|
ctx(request, public_id=public_id, error="Email не совпадает с заказом"),
|
|
)
|
|
if not email:
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"order_locked.html",
|
|
ctx(request, public_id=public_id, error=None),
|
|
)
|
|
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"order.html",
|
|
ctx(request, order=order),
|
|
)
|
|
|
|
|
|
@router.post("/order/{public_id}", response_class=HTMLResponse)
|
|
def order_unlock(
|
|
public_id: str,
|
|
request: Request,
|
|
email: str = Form(...),
|
|
):
|
|
return RedirectResponse(
|
|
f"/order/{public_id}?email={email.strip().lower()}",
|
|
status_code=303,
|
|
)
|
|
|
|
|
|
@router.get("/lookup", response_class=HTMLResponse)
|
|
def lookup_page(request: Request):
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"lookup.html",
|
|
ctx(request, error=None),
|
|
)
|
|
|
|
|
|
@router.post("/lookup")
|
|
def lookup_submit(
|
|
request: Request,
|
|
public_id: str = Form(...),
|
|
email: str = Form(...),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
order = db.scalar(select(Order).where(Order.public_id == public_id.strip().upper()))
|
|
if not order or order.email != email.strip().lower():
|
|
return router.templates.TemplateResponse( # type: ignore[attr-defined]
|
|
request,
|
|
"lookup.html",
|
|
ctx(request, error="Заказ не найден. Проверьте номер и email."),
|
|
status_code=404,
|
|
)
|
|
return RedirectResponse(
|
|
f"/order/{order.public_id}?email={order.email}",
|
|
status_code=303,
|
|
)
|