0 is out of stock and nothing is available from now on
This commit is contained in:
@@ -14,7 +14,7 @@ pub struct Model {
|
||||
pub label: String,
|
||||
pub position: i32,
|
||||
pub sku: Option<String>,
|
||||
pub stock: i32,
|
||||
pub stock: Option<i32>,
|
||||
pub price_cents: i64,
|
||||
pub sale_price_cents: Option<i64>,
|
||||
pub business_sale_price_cents: Option<i64>,
|
||||
|
||||
@@ -65,11 +65,15 @@ pub async fn place(
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| Error::BadRequest("a product is no longer available".to_string()))?;
|
||||
if variant.stock < *qty {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"not enough stock for {}",
|
||||
product.name
|
||||
)));
|
||||
// Tracked variants can't oversell; untracked ones (stock = None) are
|
||||
// always available and never decremented.
|
||||
if let Some(on_hand) = variant.stock {
|
||||
if on_hand < *qty {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"not enough stock for {}",
|
||||
product.name
|
||||
)));
|
||||
}
|
||||
}
|
||||
currency = product.currency.clone();
|
||||
// Snapshot the price the buyer actually pays — public sale or, for a
|
||||
@@ -78,9 +82,11 @@ pub async fn place(
|
||||
let unit_price_cents = pricing::price_variant(ctx, &variant, user).await?.price_cents;
|
||||
subtotal += unit_price_cents * i64::from(*qty);
|
||||
|
||||
let mut active = variant.clone().into_active_model();
|
||||
active.stock = Set(variant.stock - *qty);
|
||||
active.update(&txn).await?;
|
||||
if let Some(on_hand) = variant.stock {
|
||||
let mut active = variant.clone().into_active_model();
|
||||
active.stock = Set(Some(on_hand - *qty));
|
||||
active.update(&txn).await?;
|
||||
}
|
||||
|
||||
snapshots.push((product.id, variant.id, product.name, variant.label, unit_price_cents, *qty));
|
||||
}
|
||||
|
||||
@@ -45,6 +45,30 @@ impl Model {
|
||||
pub fn business_on_sale(&self) -> bool {
|
||||
matches!(self.business_sale_price_cents, Some(sale) if sale < self.price_cents)
|
||||
}
|
||||
|
||||
/// Whether the variant's inventory is tracked. A `None` stock means
|
||||
/// "available, not tracked" (always purchasable, unlimited).
|
||||
#[must_use]
|
||||
pub fn tracked(&self) -> bool {
|
||||
self.stock.is_some()
|
||||
}
|
||||
|
||||
/// Whether the variant can currently be bought: untracked variants are always
|
||||
/// available; tracked ones need a positive quantity on hand.
|
||||
#[must_use]
|
||||
pub fn in_stock(&self) -> bool {
|
||||
self.stock.map_or(true, |s| s > 0)
|
||||
}
|
||||
|
||||
/// Clamp a desired quantity to what's available: capped at the tracked stock,
|
||||
/// or left as-is (only floored at 0) when untracked.
|
||||
#[must_use]
|
||||
pub fn cap(&self, qty: i32) -> i32 {
|
||||
match self.stock {
|
||||
Some(s) => qty.clamp(0, s),
|
||||
None => qty.max(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implement your write-oriented logic here
|
||||
|
||||
Reference in New Issue
Block a user