Auto set the price for product when model is set
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
dvirlabs 2026-05-08 18:23:16 +03:00
parent 9176e32e6f
commit 7fbc2f7d41
8 changed files with 85 additions and 9 deletions

View File

@ -11,7 +11,7 @@ class Product(Base):
name = Column(String, index=True) name = Column(String, index=True)
slug = Column(String, unique=True, index=True, nullable=True) slug = Column(String, unique=True, index=True, nullable=True)
description = Column(Text) description = Column(Text)
price = Column(Float) price = Column(Float, nullable=True) # Nullable - can inherit from model
discount_price = Column(Float, nullable=True) discount_price = Column(Float, nullable=True)
category_id = Column(Integer, ForeignKey("category.id")) category_id = Column(Integer, ForeignKey("category.id"))
model_id = Column(Integer, ForeignKey("model.id", ondelete="SET NULL"), nullable=True) model_id = Column(Integer, ForeignKey("model.id", ondelete="SET NULL"), nullable=True)
@ -31,3 +31,12 @@ class Product(Base):
model = relationship("Model", back_populates="products") model = relationship("Model", back_populates="products")
cart_items = relationship("CartItem", back_populates="product") cart_items = relationship("CartItem", back_populates="product")
order_items = relationship("OrderItem", back_populates="product") order_items = relationship("OrderItem", back_populates="product")
def get_effective_price(self) -> float:
"""Get the effective price - either product's own price or model's base_price"""
if self.price is not None:
return float(self.price)
elif self.model and self.model.base_price is not None:
return float(self.model.base_price)
else:
return 0.0 # Default fallback

View File

@ -60,10 +60,13 @@ def list_products(
products = query.offset(skip).limit(limit).all() products = query.offset(skip).limit(limit).all()
# Inherit sizes from model if product doesn't have sizes # Inherit sizes and price from model if product doesn't have them
for product in products: for product in products:
if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model: if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model:
product.sizes = product.model.sizes or [] product.sizes = product.model.sizes or []
# Inherit price from model if not set
if product.price is None and product.model_id and product.model and product.model.base_price:
product.price = float(product.model.base_price)
return products return products
@ -72,10 +75,13 @@ def list_products(
def search(q: str, skip: int = 0, limit: int = 20, db: Session = Depends(get_db)): def search(q: str, skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
products = search_products(db, q, skip=skip, limit=limit) products = search_products(db, q, skip=skip, limit=limit)
# Inherit sizes from model if product doesn't have sizes # Inherit sizes and price from model if product doesn't have them
for product in products: for product in products:
if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model: if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model:
product.sizes = product.model.sizes or [] product.sizes = product.model.sizes or []
# Inherit price from model if not set
if product.price is None and product.model_id and product.model and product.model.base_price:
product.price = float(product.model.base_price)
return products return products
@ -89,6 +95,9 @@ def get_product(product_id: int, db: Session = Depends(get_db)):
# If product doesn't have sizes but has a model, inherit sizes from model # If product doesn't have sizes but has a model, inherit sizes from model
if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model: if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model:
product.sizes = product.model.sizes or [] product.sizes = product.model.sizes or []
# Inherit price from model if not set
if product.price is None and product.model_id and product.model and product.model.base_price:
product.price = float(product.model.base_price)
return product return product
@ -99,6 +108,24 @@ def create_new_product(
db: Session = Depends(get_db), db: Session = Depends(get_db),
admin: User = Depends(get_current_admin_user), admin: User = Depends(get_current_admin_user),
): ):
# Validate price: either product has price OR model has base_price
if product.price is None:
if not product.model_id:
raise HTTPException(
status_code=400,
detail="Price is required when no model is assigned"
)
# Check if model exists and has base_price
from app.models import Model
model = db.query(Model).filter(Model.id == product.model_id).first()
if not model:
raise HTTPException(status_code=404, detail="Model not found")
if model.base_price is None:
raise HTTPException(
status_code=400,
detail="Model must have a base_price set if product price is not provided"
)
# Auto-generate slug if not provided # Auto-generate slug if not provided
if not product.slug: if not product.slug:
base_slug = generate_slug(f"{product.brand} {product.name}") base_slug = generate_slug(f"{product.brand} {product.name}")
@ -110,7 +137,18 @@ def create_new_product(
counter += 1 counter += 1
product.slug = slug product.slug = slug
return create_product(db, product) created_product = create_product(db, product)
# Refresh to get the model relationship
db.refresh(created_product)
# Inherit price and sizes from model if needed
if created_product.price is None and created_product.model_id and created_product.model:
created_product.price = float(created_product.model.base_price)
if (not created_product.sizes or len(created_product.sizes) == 0) and created_product.model_id and created_product.model:
created_product.sizes = created_product.model.sizes or []
return created_product
@router.put("/{product_id}", response_model=ProductResponse) @router.put("/{product_id}", response_model=ProductResponse)
@ -123,6 +161,16 @@ def update_existing_product(
product = update_product(db, product_id, product_update) product = update_product(db, product_id, product_update)
if not product: if not product:
raise HTTPException(status_code=404, detail="Product not found") raise HTTPException(status_code=404, detail="Product not found")
# Refresh to ensure model relationship is loaded
db.refresh(product)
# Inherit price and sizes from model if needed
if product.price is None and product.model_id and product.model and product.model.base_price:
product.price = float(product.model.base_price)
if (not product.sizes or len(product.sizes) == 0) and product.model_id and product.model:
product.sizes = product.model.sizes or []
return product return product

View File

@ -8,7 +8,7 @@ class ProductCreate(BaseModel):
name: str name: str
slug: Optional[str] = None slug: Optional[str] = None
description: str description: str
price: float price: Optional[float] = None # Optional - inherits from model if not set
discount_price: Optional[float] = None discount_price: Optional[float] = None
category_id: int category_id: int
model_id: Optional[int] = None model_id: Optional[int] = None
@ -49,7 +49,7 @@ class ProductResponse(BaseModel):
name: str name: str
slug: Optional[str] slug: Optional[str]
description: str description: str
price: float price: Optional[float] # May be None if inherited from model
discount_price: Optional[float] discount_price: Optional[float]
category_id: int category_id: int
model_id: Optional[int] model_id: Optional[int]

View File

@ -209,7 +209,7 @@ export default function Admin() {
const productData = { const productData = {
...formData, ...formData,
price: parseFloat(formData.price), price: formData.price ? parseFloat(formData.price) : null, // Allow null - inherits from model
discount_price: formData.discount_price ? parseFloat(formData.discount_price) : null, discount_price: formData.discount_price ? parseFloat(formData.discount_price) : null,
category_id: parseInt(formData.category_id), category_id: parseInt(formData.category_id),
model_id: formData.model_id ? parseInt(formData.model_id) : null, model_id: formData.model_id ? parseInt(formData.model_id) : null,
@ -821,8 +821,27 @@ export default function Admin() {
</div> </div>
<div className="form-group"> <div className="form-group">
<label>Price *</label> <label>
<input type="number" step="0.01" name="price" value={formData.price} onChange={handleChange} required /> Price {formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price ? '(Optional - inherits from model)' : '*'}
</label>
<input
type="number"
step="0.01"
name="price"
value={formData.price}
onChange={handleChange}
required={!formData.model_id || !models.find(m => m.id === parseInt(formData.model_id))?.base_price}
placeholder={
formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price
? `Model price: ₪${parseFloat(models.find(m => m.id === parseInt(formData.model_id)).base_price).toFixed(2)}`
: 'Enter price'
}
/>
{formData.model_id && models.find(m => m.id === parseInt(formData.model_id))?.base_price && (
<small style={{ color: '#666', fontSize: '0.85em' }}>
Leave empty to use model's base price of {parseFloat(models.find(m => m.id === parseInt(formData.model_id)).base_price).toFixed(2)}
</small>
)}
</div> </div>
<div className="form-group"> <div className="form-group">