brand-master/PRICE_INHERITANCE.md
dvirlabs 437fe72e48
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Manage contact us messages
2026-05-08 18:40:12 +03:00

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:

  1. Set a base price on a model (e.g., "New Balance 9060" at ₪599)
  2. Create product variants (different colors) without specifying price
  3. All variants automatically use the model's base price
  4. Optionally override the price for specific product variants

How It Works

Backend Logic

  1. Product Price Field: Now nullable - can be NULL in database

  2. Price Resolution:

    • If product has its own price set → use product price
    • If product price is NULL AND product is linked to a model → inherit model's base_price
    • If both are NULL → defaults to 0.0 (fallback)
  3. Validation: When creating/updating a product:

    • If price is not provided → model_id must be provided
    • The assigned model must have a base_price set
    • API returns 400 error if validation fails

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:

  1. No Model Selected:

    • Label: "Price *" (required)
    • Field is required
    • Placeholder: "Enter price"
  2. 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"
  3. 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

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:

  1. Go to Models tab
  2. Edit "New Balance 9060" model
  3. Change base_price from ₪599 to ₪549
  4. Save

Result: All 10 products now show ₪549 (unless individually overridden)

Code Changes Summary

Backend Files Modified

  1. backend/app/models/product.py

    • Made price field nullable
    • Added get_effective_price() helper method
  2. backend/app/schemas/product.py

    • Changed ProductCreate.price from required to Optional[float]
    • Changed ProductResponse.price to Optional[float]
  3. 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()
  4. backend/migrations/006_make_product_price_nullable.sql (NEW)

    • Database migration to make price column nullable

Frontend Files Modified

  1. 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

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:

  1. Provide a price value, OR
  2. 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:

  1. Edit the model and set a base_price, OR
  2. 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:

  1. Edit the model and set base_price, OR
  2. 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:

  1. Discount Inheritance: Inherit discount_price from model
  2. Price History: Track when prices are inherited vs. overridden
  3. Bulk Operations: Update multiple products to use/remove model price
  4. Price Comparison: Show difference between model price and overridden price
  5. Reporting: Analytics on price inheritance usage