now products have different options, like different parameters
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled

This commit is contained in:
Priec
2026-06-22 15:44:02 +02:00
parent 29854a972b
commit 3f798432a0
52 changed files with 1281 additions and 628 deletions

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
use sea_orm::{Set, TransactionTrait};
use uuid::Uuid;
use crate::models::_entities::{order_items, products, shipping_methods};
use crate::models::_entities::{order_items, product_variants, products, shipping_methods};
use crate::models::users;
use crate::shared::pricing;
pub use crate::models::_entities::orders::{ActiveModel, Column, Entity, Model};
@@ -40,10 +40,10 @@ fn generate_order_number() -> String {
format!("ORD-{suffix}")
}
/// Atomically place an order for the given `(product_id, quantity)` lines:
/// snapshot each product's price/name, decrement stock (re-checking inside the
/// transaction so an item can't oversell between cart and pay), then write the
/// order and its line items. Returns the persisted order.
/// Atomically place an order for the given `(variant_id, quantity)` lines:
/// snapshot each variant's price/name/label, decrement its stock (re-checking
/// inside the transaction so an item can't oversell between cart and pay), then
/// write the order and its line items. Returns the persisted order.
pub async fn place(
ctx: &AppContext,
items: &[(i32, i32)],
@@ -55,13 +55,17 @@ pub async fn place(
let mut subtotal: i64 = 0;
let mut currency = "EUR".to_string();
let mut snapshots = Vec::new();
for (product_id, qty) in items {
let product = products::Entity::find_by_id(*product_id)
for (variant_id, qty) in items {
let variant = product_variants::Entity::find_by_id(*variant_id)
.one(&txn)
.await?
.ok_or_else(|| Error::BadRequest("an item is no longer available".to_string()))?;
let product = products::Entity::find_by_id(variant.product_id)
.filter(products::Column::Published.eq(true))
.one(&txn)
.await?
.ok_or_else(|| Error::BadRequest("a product is no longer available".to_string()))?;
if product.stock < *qty {
if variant.stock < *qty {
return Err(Error::BadRequest(format!(
"not enough stock for {}",
product.name
@@ -71,14 +75,14 @@ pub async fn place(
// Snapshot the price the buyer actually pays — public sale or, for a
// business account, their negotiated/lowest price (same resolver the
// cart and storefront use).
let unit_price_cents = pricing::price_for(ctx, &product, user).await?.price_cents;
let unit_price_cents = pricing::price_variant(ctx, &variant, user).await?.price_cents;
subtotal += unit_price_cents * i64::from(*qty);
let mut active = product.clone().into_active_model();
active.stock = Set(product.stock - *qty);
let mut active = variant.clone().into_active_model();
active.stock = Set(variant.stock - *qty);
active.update(&txn).await?;
snapshots.push((product.id, product.name, unit_price_cents, *qty));
snapshots.push((product.id, variant.id, product.name, variant.label, unit_price_cents, *qty));
}
let order = ActiveModel {
@@ -111,11 +115,13 @@ pub async fn place(
.insert(&txn)
.await?;
for (product_id, name, unit_price_cents, qty) in snapshots {
for (product_id, variant_id, name, variant_label, unit_price_cents, qty) in snapshots {
order_items::ActiveModel {
order_id: Set(order.id),
product_id: Set(Some(product_id)),
variant_id: Set(Some(variant_id)),
product_name: Set(name),
variant_label: Set(variant_label),
unit_price_cents: Set(unit_price_cents),
quantity: Set(qty),
..Default::default()