Skip to main content

Overview

The Product Management system provides a complete solution for managing your inventory catalog. Each product is tracked with detailed attributes including automatic SKU generation, categorization, pricing history, and perishable goods management.
Products are the foundation of the inventory system. All batches, movements, and reports are linked to products.

Product Data Model

Products in the system contain the following attributes:
class Product(Base, AuditableEntity):
    __tablename__ = "products"

    id = Column(String(50), primary_key=True, index=True)
    sku = Column(String(100), unique=True, index=True, nullable=False)
    name = Column(String(200), index=True, nullable=False)
    description = Column(String(500), nullable=True)
    category = Column(String(100), index=True, nullable=False)
    unit_measure = Column(String(50), nullable=False)
    unit_value = Column(Float, default=1.0, nullable=False)
    is_perishable = Column(Boolean, default=False, nullable=False)
    expiration_date = Column(String(50), nullable=True)
    suggested_price = Column(Float, nullable=False)

Key Attributes

FieldTypeDescription
idStringUnique identifier (UUID)
skuStringStock Keeping Unit (auto-generated)
nameStringProduct name
categoryStringProduct category for grouping
unit_measureStringUnit of measurement (e.g., “kg”, “units”, “liters”)
unit_valueFloatValue per unit (default: 1.0)
is_perishableBooleanWhether product has expiration dates
suggested_priceFloatRecommended selling price

SKU Generation

The system automatically generates unique SKU codes for each product using a combination of category prefix, name prefix, and a unique identifier.
def generate_sku(category: str, name: str) -> str:
    cat_prefix = category[:3].upper() if category else "GEN"
    name_prefix = name[:3].upper() if name else "PRD"
    unique_suffix = str(uuid.uuid4())[:6].upper()
    return f"{cat_prefix}-{name_prefix}-{unique_suffix}"

SKU Format

1

Category Prefix

First 3 characters of the category in uppercase (e.g., “BEB” for Beverages)
2

Name Prefix

First 3 characters of the product name in uppercase (e.g., “COC” for Coca-Cola)
3

Unique Suffix

6-character UUID segment for uniqueness (e.g., “A3F2D1”)
Example SKU: BEB-COC-A3F2D1

Creating Products

Products are created through the API with role-based access control. Only administrators and managers can create products.
@router.route('/', methods=['POST'])
@require_role('admin', 'gestor')
def create_product():
    data = request.get_json()
    if not data or not data.get('name') or not data.get('category') \
       or not data.get('unit_measure') or not data.get('sku'):
        return jsonify({"error": "Missing required fields"}), 400

    new_product = Product(
        id=str(uuid.uuid4()),
        sku=data.get('sku'),
        name=data.get('name'),
        description=data.get('description'),
        category=data.get('category'),
        unit_measure=data.get('unit_measure'),
        unit_value=float(data.get('unit_value', 1.0)),
        is_perishable=data.get('is_perishable', False),
        expiration_date=data.get('expiration_date'),
        suggested_price=float(data.get('suggested_price', 0))
    )
    product = repo.create(new_product)
    return jsonify({...}), 201

API Endpoint

Create Product

POST /api/v1/products/Creates a new product in the catalog.
{
  "sku": "BEB-COC-A3F2D1",
  "name": "Coca-Cola 500ml",
  "description": "Refreshing cola beverage",
  "category": "Beverages",
  "unit_measure": "units",
  "unit_value": 1.0,
  "is_perishable": true,
  "expiration_date": "2026-12-31",
  "suggested_price": 15.50
}

Product Categories

Categories help organize products and enable filtering in reports and dashboards. Common categories include:

Beverages

Drinks, juices, sodas

Food

Perishable food items

Electronics

Electronic devices

Clothing

Apparel and textiles

Hardware

Tools and equipment

Supplies

Office and general supplies

Price Management

The system tracks price history to maintain transparency and enable price trend analysis.

Price History Tracking

class PriceHistory(Base, AuditableEntity):
    __tablename__ = "price_history"

    id = Column(String(50), primary_key=True, index=True)
    product_id = Column(String(50), ForeignKey("products.id"), nullable=False)
    old_price = Column(Float, nullable=False)
    new_price = Column(Float, nullable=False)
    reason = Column(String(200), nullable=True)

Bulk Price Updates

Update multiple product prices simultaneously with automatic history tracking:
@router.route('/update-prices', methods=['POST'])
@require_role('admin', 'gestor')
def update_prices():
    updates = request.get_json()
    
    for update in updates:
        product = prod_repo.get_by_id(update.get('product_id'))
        if product and product.suggested_price != update.get('new_price'):
            history = PriceHistory(
                id=str(uuid.uuid4()),
                product_id=product.id,
                old_price=product.suggested_price,
                new_price=update.get('new_price'),
                reason=update.get('reason', "Massive update")
            )
            product.suggested_price = update.get('new_price')
            prod_repo.update(product)
            hist_repo.create(history)
            updated_count += 1
[
  {
    "product_id": "uuid-1",
    "new_price": 18.50,
    "reason": "Market adjustment"
  },
  {
    "product_id": "uuid-2",
    "new_price": 25.00,
    "reason": "Supplier price increase"
  }
]

Perishable Products

Products marked as perishable have special handling for expiration dates:
Perishable products should always have expiration dates set on their batches to enable FEFO (First Expired, First Out) inventory management.

Expiration Tracking Features

Each batch of a perishable product can have its own expiration date, allowing precise tracking of different purchase lots.
When selling perishable products, the system automatically uses batches closest to expiration first (see Batch Tracking).
Dashboard alerts help identify products approaching expiration dates.

Querying Products

Retrieve products with pagination support:
@router.route('/', methods=['GET'])
@require_role('admin', 'gestor', 'consultor')
def get_products():
    skip = int(request.args.get('skip', 0))
    limit = int(request.args.get('limit', 100))
    
    products = repo.get_all(skip=skip, limit=limit)
    return jsonify(result), 200

Query Parameters

ParameterTypeDefaultDescription
skipInteger0Number of records to skip
limitInteger100Maximum records to return

Best Practices

Use Descriptive Names

Product names should be clear and include key attributes (e.g., “Coca-Cola 500ml” vs “Coke”)

Consistent Categories

Maintain a standardized list of categories for better organization and reporting

Update Prices Regularly

Keep suggested prices current to ensure accurate profit margin calculations

Mark Perishables Correctly

Always set is_perishable flag for products with expiration dates

Batch Tracking

Learn about batch-level inventory management

Stock Movements

Understand inventory transactions

API Reference

Complete API documentation

Build docs developers (and LLMs) love