now products have different options, like different parameters
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::{controllers::i18n::current_lang, shared::{guard, money::format_price, pricing}, models::products};
|
||||
use crate::{controllers::i18n::current_lang, shared::{guard, money::format_price, pricing}, models::{product_variants, products}};
|
||||
use axum::{
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::Redirect,
|
||||
@@ -15,22 +15,22 @@ const CART_MAX_AGE_DAYS: i64 = 30;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddForm {
|
||||
product_id: i32,
|
||||
variant_id: i32,
|
||||
quantity: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateForm {
|
||||
product_id: i32,
|
||||
variant_id: i32,
|
||||
quantity: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RemoveForm {
|
||||
product_id: i32,
|
||||
variant_id: i32,
|
||||
}
|
||||
|
||||
/// Parse the `cart` cookie ("id:qty,id:qty") into `(product_id, quantity)`
|
||||
/// Parse the `cart` cookie ("id:qty,id:qty") into `(variant_id, quantity)`
|
||||
/// pairs, silently dropping malformed or non-positive entries.
|
||||
pub(crate) fn parse_cart(jar: &CookieJar) -> Vec<(i32, i32)> {
|
||||
let Some(cookie) = jar.get(CART_COOKIE) else {
|
||||
@@ -64,12 +64,23 @@ fn cart_cookie(value: String) -> Cookie<'static> {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Look up a published product, returning its current stock cap.
|
||||
async fn published_product(ctx: &AppContext, id: i32) -> Result<Option<products::Model>> {
|
||||
Ok(products::Entity::find_by_id(id)
|
||||
/// Look up a variant whose product is published, returning the variant together
|
||||
/// with its parent product (for name/slug/currency).
|
||||
async fn published_variant(
|
||||
ctx: &AppContext,
|
||||
variant_id: i32,
|
||||
) -> Result<Option<(product_variants::Model, products::Model)>> {
|
||||
let Some(variant) = product_variants::Entity::find_by_id(variant_id)
|
||||
.one(&ctx.db)
|
||||
.await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let product = products::Entity::find_by_id(variant.product_id)
|
||||
.filter(products::Column::Published.eq(true))
|
||||
.one(&ctx.db)
|
||||
.await?)
|
||||
.await?;
|
||||
Ok(product.map(|p| (variant, p)))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
@@ -79,16 +90,16 @@ async fn add(
|
||||
headers: HeaderMap,
|
||||
Form(form): Form<AddForm>,
|
||||
) -> Result<Response> {
|
||||
let Some(product) = published_product(&ctx, form.product_id).await? else {
|
||||
let Some((variant, _product)) = published_variant(&ctx, form.variant_id).await? else {
|
||||
return Err(Error::NotFound);
|
||||
};
|
||||
|
||||
let mut items = parse_cart(&jar);
|
||||
let add_qty = form.quantity.unwrap_or(1).max(1);
|
||||
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == product.id) {
|
||||
entry.1 = (entry.1 + add_qty).min(product.stock);
|
||||
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == variant.id) {
|
||||
entry.1 = (entry.1 + add_qty).min(variant.stock);
|
||||
} else {
|
||||
items.push((product.id, add_qty.min(product.stock)));
|
||||
items.push((variant.id, add_qty.min(variant.stock)));
|
||||
}
|
||||
items.retain(|(_, qty)| *qty > 0);
|
||||
|
||||
@@ -117,14 +128,14 @@ async fn update(
|
||||
headers: HeaderMap,
|
||||
Form(form): Form<UpdateForm>,
|
||||
) -> Result<Response> {
|
||||
let stock = published_product(&ctx, form.product_id)
|
||||
let stock = published_variant(&ctx, form.variant_id)
|
||||
.await?
|
||||
.map(|p| p.stock)
|
||||
.map(|(v, _)| v.stock)
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut items = parse_cart(&jar);
|
||||
let clamped = form.quantity.clamp(0, stock);
|
||||
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == form.product_id) {
|
||||
if let Some(entry) = items.iter_mut().find(|(id, _)| *id == form.variant_id) {
|
||||
entry.1 = clamped;
|
||||
}
|
||||
items.retain(|(_, qty)| *qty > 0);
|
||||
@@ -142,7 +153,7 @@ async fn remove(
|
||||
Form(form): Form<RemoveForm>,
|
||||
) -> Result<Response> {
|
||||
let mut items = parse_cart(&jar);
|
||||
items.retain(|(id, _)| *id != form.product_id);
|
||||
items.retain(|(id, _)| *id != form.variant_id);
|
||||
|
||||
let jar = jar.add(cart_cookie(serialize_cart(&items)));
|
||||
cart_response(&ctx, &v, jar, &headers).await
|
||||
@@ -192,38 +203,40 @@ pub(crate) async fn resolve_cart(
|
||||
// Resolve the cart entries to in-stock products first, then price them all
|
||||
// for the current viewer in one batch (the price depends on who's logged in).
|
||||
let user = guard::current_user(ctx, jar).await;
|
||||
let mut items: Vec<(products::Model, i32)> = Vec::new();
|
||||
let mut items: Vec<(product_variants::Model, products::Model, i32)> = Vec::new();
|
||||
for (id, qty) in parse_cart(jar) {
|
||||
let Some(product) = published_product(ctx, id).await? else {
|
||||
let Some((variant, product)) = published_variant(ctx, id).await? else {
|
||||
continue;
|
||||
};
|
||||
let qty = qty.clamp(0, product.stock);
|
||||
let qty = qty.clamp(0, variant.stock);
|
||||
if qty == 0 {
|
||||
continue;
|
||||
}
|
||||
items.push((product, qty));
|
||||
items.push((variant, product, qty));
|
||||
}
|
||||
let products_only: Vec<products::Model> = items.iter().map(|(p, _)| p.clone()).collect();
|
||||
let priced = pricing::price_many(ctx, &products_only, user.as_ref()).await?;
|
||||
let variants_only: Vec<product_variants::Model> =
|
||||
items.iter().map(|(v, _, _)| v.clone()).collect();
|
||||
let priced = pricing::price_variants(ctx, &variants_only, user.as_ref()).await?;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
let mut valid = Vec::new();
|
||||
let mut total: i64 = 0;
|
||||
for ((product, qty), priced) in items.iter().zip(priced.iter()) {
|
||||
for ((variant, product, qty), priced) in items.iter().zip(priced.iter()) {
|
||||
let unit_price = priced.price_cents;
|
||||
let line_total = unit_price * i64::from(*qty);
|
||||
total += line_total;
|
||||
valid.push((product.id, *qty));
|
||||
valid.push((variant.id, *qty));
|
||||
lines.push(json!({
|
||||
"id": product.id,
|
||||
"id": variant.id,
|
||||
"name": product.name,
|
||||
"variant_label": variant.label,
|
||||
"slug": product.slug,
|
||||
"price": format_price(unit_price),
|
||||
"regular_price": format_price(priced.regular_cents),
|
||||
"on_sale": priced.is_reduced(),
|
||||
"currency": product.currency,
|
||||
"quantity": qty,
|
||||
"stock": product.stock,
|
||||
"stock": variant.stock,
|
||||
"line_total": format_price(line_total),
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user