11 KiB
Price Inheritance from Models to Products
Feature Overview
Products can now inherit their price from the assigned Model's base_price field. This allows you to:
- Set a base price on a model (e.g., "New Balance 9060" at ₪599)
- Create product variants (different colors) without specifying price
- All variants automatically use the model's base price
- Optionally override the price for specific product variants
How It Works
Backend Logic
-
Product Price Field: Now nullable - can be
NULLin database -
Price Resolution:
- If product has its own
priceset → use product price - If product
priceisNULLAND product is linked to a model → inherit model'sbase_price - If both are
NULL→ defaults to 0.0 (fallback)
- If product has its own
-
Validation: When creating/updating a product:
- If
priceis not provided →model_idmust be provided - The assigned model must have a
base_priceset - API returns 400 error if validation fails
- If
API Changes
Product Create (POST /api/products)
Request Body - Price is now optional:
{
"name": "New Balance 9060 - Black/Red",
"description": "Stylish colorway",
"price": null, // ← Can be null now
"category_id": 1,
"model_id": 5, // ← Required when price is null
"brand": "New Balance",
"gender": "men",
"images": ["..."],
"sizes": [] // Can also inherit from model
}
Validation:
- ✅ Price provided, model optional → Valid
- ✅ Price null, model with base_price provided → Valid (inherits)
- ❌ Price null, no model → Error: "Price is required when no model is assigned"
- ❌ Price null, model without base_price → Error: "Model must have a base_price set if product price is not provided"
Response:
{
"id": 123,
"name": "New Balance 9060 - Black/Red",
"price": 599.0, // ← Inherited from model's base_price
"model_id": 5,
...
}
Product Get (GET /api/products, GET /api/products/{id})
Products returned with computed price:
- If product.price is set → returns product.price
- If product.price is NULL → returns model.base_price
- Response always includes effective price
Database Changes
Migration: 006_make_product_price_nullable.sql
-- Make price column nullable
ALTER TABLE product ALTER COLUMN price DROP NOT NULL;
-- Add documentation
COMMENT ON COLUMN product.price IS 'Product-specific price. If NULL, inherits from model.base_price';
To apply migration:
# Connect to database
kubectl exec -it -n my-apps brand-master-db-0 -- psql -U brand_master_user -d brand_master_db
# Run migration
\i /path/to/migrations/006_make_product_price_nullable.sql
# Or use apply-migration script
./apply-migration.sh 006_make_product_price_nullable.sql
Frontend Changes
Admin Panel - Product Form
Price Field Behavior:
-
No Model Selected:
- Label: "Price *" (required)
- Field is required
- Placeholder: "Enter price"
-
Model Selected (with base_price):
- Label: "Price (Optional - inherits from model)"
- Field is optional
- Placeholder: "Model price: ₪599.00"
- Help text: "Leave empty to use model's base price of ₪599.00"
-
Model Selected (without base_price):
- Label: "Price *" (required)
- Field is required
- Must enter price manually
Form Validation:
- Price field becomes optional dynamically when a model with base_price is selected
- JavaScript validation checks:
required={!formData.model_id || !models.find(m => m.id === parseInt(formData.model_id))?.base_price}
Usage Examples
Example 1: Model with Variants (Recommended Use Case)
Step 1: Create a Model
Name: 9060
Brand: New Balance
Category: Sneakers
Base Price: ₪599
Sizes: 40, 41, 42, 43, 44, 45
Step 2: Create Product Variants (without specifying price)
Product 1:
Name: New Balance 9060 - Black/Red
Model: New Balance 9060
Price: (leave empty) → Inherits ₪599
Images: [black-red-1.jpg, black-red-2.jpg]
Product 2:
Name: New Balance 9060 - White/Blue
Model: New Balance 9060
Price: (leave empty) → Inherits ₪599
Images: [white-blue-1.jpg, white-blue-2.jpg]
Product 3:
Name: New Balance 9060 - Limited Edition Gold
Model: New Balance 9060
Price: ₪899 → Override model price
Images: [gold-1.jpg, gold-2.jpg]
Result:
- Products 1 & 2: Display at ₪599 (inherited)
- Product 3: Displays at ₪899 (overridden)
- If you update model's base_price to ₪649, Products 1 & 2 automatically reflect new price
- Product 3 remains at ₪899
Example 2: Standalone Product (No Model)
Product:
Name: Nike Air Max Classic
Price: ₪399 (required - no model)
Model: (none)
Category: Sneakers
Brand: Nike
Result: Works as before, price must be specified
Example 3: Bulk Price Update
Scenario: You have 10 colorways of "New Balance 9060", all using the model
To update all prices:
- Go to Models tab
- Edit "New Balance 9060" model
- Change base_price from ₪599 to ₪549
- Save
Result: All 10 products now show ₪549 (unless individually overridden)
Code Changes Summary
Backend Files Modified
-
backend/app/models/product.py- Made
pricefield nullable - Added
get_effective_price()helper method
- Made
-
backend/app/schemas/product.py- Changed
ProductCreate.pricefrom required toOptional[float] - Changed
ProductResponse.pricetoOptional[float]
- Changed
-
backend/app/routers/products.py- Added validation in
create_new_product()endpoint - Added price inheritance logic in all GET endpoints
- Added price inheritance in
update_existing_product()
- Added validation in
-
backend/migrations/006_make_product_price_nullable.sql(NEW)- Database migration to make price column nullable
Frontend Files Modified
frontend/src/pages/Admin.jsx- Updated
handleSubmit()to send null price when empty - Enhanced price input field with dynamic placeholder and help text
- Made price field conditionally required based on model selection
- Updated
Testing Checklist
Backend Testing
- Create product with price and no model → Works
- Create product with price and model → Works (uses product price)
- Create product without price but with model (has base_price) → Works (inherits)
- Create product without price and no model → Error 400
- Create product without price with model (no base_price) → Error 400
- Update product, set model_id, remove price → Inherits model price
- Get product list → Returns computed prices correctly
- Get single product → Returns computed price correctly
- Search products → Returns computed prices correctly
Frontend Testing
- Create product without model → Price field required
- Select model without base_price → Price field still required
- Select model with base_price → Price field becomes optional
- See model's base_price in placeholder
- Leave price empty → Product created successfully
- Product list shows inherited price correctly
- Edit product with inherited price → Shows empty price field with placeholder
- Update model base_price → All linked products show new price
Database Testing
-- Check nullable constraint
SELECT column_name, is_nullable, data_type
FROM information_schema.columns
WHERE table_name = 'product' AND column_name = 'price';
-- Expected: is_nullable = 'YES'
-- Find products with inherited price
SELECT p.id, p.name, p.price as product_price, m.base_price as model_price
FROM product p
LEFT JOIN model m ON p.model_id = m.id
WHERE p.price IS NULL;
-- Find models with products
SELECT m.id, m.name, m.base_price, COUNT(p.id) as product_count
FROM model m
LEFT JOIN product p ON p.model_id = m.id
GROUP BY m.id, m.name, m.base_price
ORDER BY product_count DESC;
Migration Instructions
1. Apply Database Migration
# Option A: Using migration script
cd ~/OneDrive/Desktop/gitea/brand-master/backend
./apply-migration.sh migrations/006_make_product_price_nullable.sql
# Option B: Manual execution
kubectl exec -it -n my-apps brand-master-db-0 -- \
psql -U brand_master_user -d brand_master_db \
-c "ALTER TABLE product ALTER COLUMN price DROP NOT NULL;"
2. Deploy Backend Changes
cd ~/OneDrive/Desktop/gitea/brand-master
# Build and push backend
docker build -t harbor.dvirlabs.com/my-apps/brand-master-backend:latest -f backend/Dockerfile backend/
docker push harbor.dvirlabs.com/my-apps/brand-master-backend:latest
3. Deploy Frontend Changes
# Build and push frontend
docker build -t harbor.dvirlabs.com/my-apps/brand-master-frontend:latest -f frontend/Dockerfile frontend/
docker push harbor.dvirlabs.com/my-apps/brand-master-frontend:latest
4. Upgrade Helm Release
cd ~/OneDrive/Desktop/gitea/my-apps
helm upgrade brand-master charts/brand-master-chart \
-f manifests/brand-master/values.yaml \
-n my-apps
5. Verify Deployment
# Check pods are running
kubectl get pods -n my-apps -l app.kubernetes.io/name=brand-master
# Check backend logs
kubectl logs -n my-apps -l app.kubernetes.io/component=backend --tail=50
# Test API
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api-brand-master.dvirlabs.com/api/products | jq '.[0] | {id, name, price, model_id}'
Troubleshooting
Error: "Price is required when no model is assigned"
Cause: Trying to create product without price and without model
Solution: Either:
- Provide a price value, OR
- Select a model that has a base_price set
Error: "Model must have a base_price set if product price is not provided"
Cause: Selected model doesn't have base_price configured
Solution:
- Edit the model and set a base_price, OR
- Provide a specific price for this product
Products showing ₪0.00 price
Cause: Product has no price AND linked model has no base_price
Solution:
- Edit the model and set base_price, OR
- Edit each product and set individual price
Cannot update existing products
Cause: Old products might have validation issues
Solution: Ensure all existing products either have:
- A price value set, OR
- A model_id linked to a model with base_price
Benefits
✅ Easier Product Management: Create multiple colorways without re-entering price ✅ Bulk Price Updates: Change model price → all variants update ✅ Flexibility: Can still override price for special editions ✅ Cleaner Data: One source of truth for model prices ✅ Inheritance: Sizes and price both inherit from models ✅ Backwards Compatible: Existing products with prices continue to work
Future Enhancements
Potential improvements for future versions:
- Discount Inheritance: Inherit discount_price from model
- Price History: Track when prices are inherited vs. overridden
- Bulk Operations: Update multiple products to use/remove model price
- Price Comparison: Show difference between model price and overridden price
- Reporting: Analytics on price inheritance usage