- Updated all OpenTelemetry packages to latest versions: - opentelemetry-api: 1.27.0 → 1.39.1 - opentelemetry-sdk: 1.27.0 → 1.39.1 - opentelemetry-exporter-otlp-proto-grpc: 1.27.0 → 1.39.1 - opentelemetry-exporter-otlp-proto-http: 1.27.0 → 1.39.1 - opentelemetry-instrumentation-fastapi: 0.48b0 → 0.60b1 - opentelemetry-instrumentation-httpx: 0.48b0 → 0.60b1 - opentelemetry-instrumentation-redis: 0.48b0 → 0.60b1 - opentelemetry-instrumentation-sqlalchemy: 0.48b0 → 0.60b1 - Removed prometheus-client==0.23.1 from all services - Unified all services to use the same monitoring package versions Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Orders Service
Overview
The Orders Service manages the complete customer order lifecycle from creation to fulfillment, tracking custom orders, wholesale orders, and direct sales. It maintains a comprehensive customer database with purchase history, enables order scheduling for pickup/delivery, and provides analytics on customer behavior and order patterns. This service is essential for B2B relationships with restaurants and hotels, as well as managing special orders for events and celebrations.
Key Features
Order Management
- Multi-Channel Orders - In-store, phone, online, wholesale
- Order Lifecycle Tracking - From pending to completed/cancelled
- Custom Orders - Special requests for events and celebrations
- Recurring Orders - Automated weekly/monthly orders for B2B
- Order Scheduling - Pickup/delivery date and time management
- Order Priority - Rush orders vs. standard processing
- Order Status Updates - Real-time status with customer notifications
Customer Database
- Customer Profiles - Complete contact and preference information
- Purchase History - Track all orders per customer
- Customer Segmentation - B2B vs. B2C, loyalty tiers
- Customer Preferences - Favorite products, allergen notes
- Credit Terms - Payment terms for wholesale customers
- Customer Analytics - RFM analysis (Recency, Frequency, Monetary)
- Customer Lifetime Value - Total value per customer
B2B Wholesale Management
- Wholesale Pricing - Custom pricing per B2B customer
- Volume Discounts - Automatic tier-based discounts
- Delivery Routes - Optimize delivery scheduling
- Invoice Generation - Automated invoicing with payment terms
- Standing Orders - Repeat orders without manual entry
- Account Management - Credit limits and payment tracking
Order Fulfillment
- Production Integration - Orders trigger production planning
- Inventory Reservation - Reserve stock for confirmed orders
- Fulfillment Status - Track preparation and delivery
- Delivery Management - Route planning and tracking
- Order Picking Lists - Generate lists for warehouse staff
- Quality Control - Pre-delivery quality checks
Payment Tracking
- Payment Methods - Cash, card, transfer, credit terms
- Payment Status - Paid, pending, overdue
- Partial Payments - Split payments over time
- Invoice History - Complete payment records
- Overdue Alerts - Automatic reminders for B2B accounts
- Revenue Recognition - Track revenue per order
Analytics & Reporting
- Order Dashboard - Real-time order metrics
- Customer Analytics - Top customers, retention rates
- Product Analytics - Most ordered products
- Revenue Analytics - Daily/weekly/monthly revenue
- Order Source Analysis - Channel performance
- Delivery Performance - On-time delivery rates
Business Value
For Bakery Owners
- Revenue Growth - Better customer relationships drive repeat business
- B2B Efficiency - Automate wholesale order management
- Cash Flow - Track outstanding payments and credit terms
- Customer Retention - Purchase history enables personalized service
- Order Accuracy - Digital orders reduce errors vs. phone/paper
- Analytics - Understand customer behavior for marketing
Quantifiable Impact
- Revenue Growth: 10-20% through improved B2B relationships
- Time Savings: 5-8 hours/week on order management
- Order Accuracy: 99%+ vs. 85-90% manual (phone/paper)
- Payment Collection: 30% faster with automated reminders
- Customer Retention: 15-25% improvement with history tracking
- B2B Efficiency: 50-70% time reduction on wholesale orders
For Sales Staff
- Quick Order Entry - Fast order creation with customer lookup
- Customer History - See previous orders for upselling
- Pricing Accuracy - Automatic wholesale pricing application
- Order Tracking - Know exactly when orders will be ready
- Customer Notes - Allergen info and preferences visible
For Customers
- Order Confirmation - Immediate confirmation with details
- Order Tracking - Real-time status updates
- Order History - View and repeat previous orders
- Flexible Scheduling - Choose pickup/delivery times
- Payment Options - Multiple payment methods
Technology Stack
- Framework: FastAPI (Python 3.11+) - Async web framework
- Database: PostgreSQL 17 - Order and customer data
- Caching: Redis 7.4 - Customer and order cache
- Messaging: RabbitMQ 4.1 - Order event publishing
- ORM: SQLAlchemy 2.0 (async) - Database abstraction
- Validation: Pydantic 2.0 - Schema validation
- Logging: Structlog - Structured JSON logging
- Metrics: Prometheus Client - Order metrics
API Endpoints (Key Routes)
Order Management
GET /api/v1/orders- List orders with filtersPOST /api/v1/orders- Create new orderGET /api/v1/orders/{order_id}- Get order detailsPUT /api/v1/orders/{order_id}- Update orderDELETE /api/v1/orders/{order_id}- Cancel orderPUT /api/v1/orders/{order_id}/status- Update order statusPOST /api/v1/orders/{order_id}/complete- Mark order complete
Order Items
GET /api/v1/orders/{order_id}/items- List order itemsPOST /api/v1/orders/{order_id}/items- Add item to orderPUT /api/v1/orders/{order_id}/items/{item_id}- Update order itemDELETE /api/v1/orders/{order_id}/items/{item_id}- Remove item
Customer Management
GET /api/v1/customers- List customers with filtersPOST /api/v1/customers- Create new customerGET /api/v1/customers/{customer_id}- Get customer detailsPUT /api/v1/customers/{customer_id}- Update customerGET /api/v1/customers/{customer_id}/orders- Get customer order historyGET /api/v1/customers/{customer_id}/analytics- Customer analytics
Wholesale Management
GET /api/v1/orders/wholesale- List wholesale ordersPOST /api/v1/orders/wholesale/recurring- Create recurring orderGET /api/v1/orders/wholesale/invoices- List invoicesPOST /api/v1/orders/wholesale/invoices/{invoice_id}/send- Send invoiceGET /api/v1/orders/wholesale/overdue- List overdue payments
Fulfillment
GET /api/v1/orders/fulfillment/pending- Orders pending fulfillmentPOST /api/v1/orders/{order_id}/prepare- Start order preparationPOST /api/v1/orders/{order_id}/ready- Mark order readyPOST /api/v1/orders/{order_id}/deliver- Mark order deliveredGET /api/v1/orders/fulfillment/picking-list- Generate picking list
Analytics
GET /api/v1/orders/analytics/dashboard- Order dashboard KPIsGET /api/v1/orders/analytics/revenue- Revenue analyticsGET /api/v1/orders/analytics/customers/top- Top customersGET /api/v1/orders/analytics/products/popular- Most ordered productsGET /api/v1/orders/analytics/channels- Order channel breakdown
Database Schema
Main Tables
customers
CREATE TABLE customers (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
customer_type VARCHAR(50) NOT NULL, -- retail, wholesale, restaurant, hotel
business_name VARCHAR(255), -- For B2B customers
contact_name VARCHAR(255) NOT NULL,
email VARCHAR(255),
phone VARCHAR(50) NOT NULL,
secondary_phone VARCHAR(50),
address_line1 VARCHAR(255),
address_line2 VARCHAR(255),
city VARCHAR(100),
postal_code VARCHAR(20),
country VARCHAR(100) DEFAULT 'España',
tax_id VARCHAR(50), -- CIF/NIF for businesses
credit_limit DECIMAL(10, 2), -- For B2B customers
credit_term_days INTEGER DEFAULT 0, -- Payment terms (e.g., Net 30)
payment_status VARCHAR(50) DEFAULT 'good_standing', -- good_standing, overdue, suspended
customer_notes TEXT,
allergen_notes TEXT,
preferred_contact_method VARCHAR(50), -- email, phone, whatsapp
loyalty_tier VARCHAR(50) DEFAULT 'standard', -- standard, silver, gold, platinum
total_lifetime_value DECIMAL(12, 2) DEFAULT 0.00,
total_orders INTEGER DEFAULT 0,
last_order_date DATE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(tenant_id, email),
UNIQUE(tenant_id, phone)
);
orders
CREATE TABLE orders (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
order_number VARCHAR(100) NOT NULL, -- Human-readable order number
customer_id UUID REFERENCES customers(id),
order_type VARCHAR(50) NOT NULL, -- retail, wholesale, custom, standing
order_source VARCHAR(50), -- in_store, phone, online, email
status VARCHAR(50) DEFAULT 'pending', -- pending, confirmed, preparing, ready, completed, cancelled
priority VARCHAR(50) DEFAULT 'standard', -- rush, standard, scheduled
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
requested_date DATE, -- Pickup/delivery date
requested_time TIME, -- Pickup/delivery time
fulfilled_date DATE,
subtotal DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
discount_amount DECIMAL(10, 2) DEFAULT 0.00,
tax_amount DECIMAL(10, 2) DEFAULT 0.00,
total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
payment_method VARCHAR(50), -- cash, card, transfer, credit
payment_status VARCHAR(50) DEFAULT 'unpaid', -- unpaid, paid, partial, overdue
payment_due_date DATE,
delivery_method VARCHAR(50), -- pickup, delivery, shipping
delivery_address TEXT,
delivery_notes TEXT,
internal_notes TEXT,
created_by UUID NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(tenant_id, order_number)
);
order_items
CREATE TABLE order_items (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
product_id UUID NOT NULL,
product_name VARCHAR(255) NOT NULL, -- Cached for performance
quantity DECIMAL(10, 2) NOT NULL,
unit VARCHAR(50) NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
discount_percentage DECIMAL(5, 2) DEFAULT 0.00,
line_total DECIMAL(10, 2) NOT NULL,
custom_instructions TEXT,
recipe_id UUID, -- Link to recipe if applicable
production_batch_id UUID, -- Link to production batch
fulfilled_quantity DECIMAL(10, 2) DEFAULT 0.00,
fulfillment_status VARCHAR(50) DEFAULT 'pending', -- pending, reserved, prepared, fulfilled
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
customer_pricing
CREATE TABLE customer_pricing (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
customer_id UUID REFERENCES customers(id) ON DELETE CASCADE,
product_id UUID NOT NULL,
custom_price DECIMAL(10, 2) NOT NULL,
discount_percentage DECIMAL(5, 2),
min_quantity DECIMAL(10, 2), -- Minimum order quantity for price
valid_from DATE DEFAULT CURRENT_DATE,
valid_until DATE,
is_active BOOLEAN DEFAULT TRUE,
notes TEXT,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(tenant_id, customer_id, product_id)
);
recurring_orders
CREATE TABLE recurring_orders (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
customer_id UUID REFERENCES customers(id) ON DELETE CASCADE,
recurring_name VARCHAR(255) NOT NULL,
frequency VARCHAR(50) NOT NULL, -- daily, weekly, biweekly, monthly
delivery_day VARCHAR(50), -- Monday, Tuesday, etc.
delivery_time TIME,
order_items JSONB NOT NULL, -- Array of {product_id, quantity, unit}
is_active BOOLEAN DEFAULT TRUE,
next_order_date DATE,
last_generated_order_id UUID,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
order_status_history
CREATE TABLE order_status_history (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
from_status VARCHAR(50),
to_status VARCHAR(50) NOT NULL,
changed_by UUID NOT NULL,
notes TEXT,
changed_at TIMESTAMP DEFAULT NOW()
);
invoices
CREATE TABLE invoices (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
invoice_number VARCHAR(100) NOT NULL,
order_id UUID REFERENCES orders(id),
customer_id UUID REFERENCES customers(id),
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE NOT NULL,
subtotal DECIMAL(10, 2) NOT NULL,
tax_amount DECIMAL(10, 2) NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
amount_paid DECIMAL(10, 2) DEFAULT 0.00,
amount_due DECIMAL(10, 2) NOT NULL,
status VARCHAR(50) DEFAULT 'sent', -- draft, sent, paid, overdue, cancelled
payment_terms VARCHAR(255),
notes TEXT,
sent_at TIMESTAMP,
paid_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(tenant_id, invoice_number)
);
Indexes for Performance
CREATE INDEX idx_orders_tenant_status ON orders(tenant_id, status);
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_date ON orders(tenant_id, order_date DESC);
CREATE INDEX idx_orders_requested_date ON orders(tenant_id, requested_date);
CREATE INDEX idx_customers_tenant_type ON customers(tenant_id, customer_type);
CREATE INDEX idx_order_items_order ON order_items(order_id);
CREATE INDEX idx_order_items_product ON order_items(tenant_id, product_id);
CREATE INDEX idx_invoices_status ON invoices(tenant_id, status);
CREATE INDEX idx_invoices_due_date ON invoices(tenant_id, due_date) WHERE status != 'paid';
Business Logic Examples
Order Creation with Pricing
async def create_order(order_data: OrderCreate, current_user: User) -> Order:
"""
Create new order with automatic pricing and customer detection.
"""
# Get or create customer
customer = await get_or_create_customer(
order_data.customer_phone,
order_data.customer_name,
order_data.customer_email
)
# Generate order number
order_number = await generate_order_number(current_user.tenant_id)
# Create order
order = Order(
tenant_id=current_user.tenant_id,
order_number=order_number,
customer_id=customer.id,
order_type=order_data.order_type,
order_source=order_data.order_source,
status='pending',
order_date=date.today(),
requested_date=order_data.requested_date,
created_by=current_user.id
)
db.add(order)
await db.flush() # Get order.id
# Add order items with pricing
subtotal = Decimal('0.00')
for item_data in order_data.items:
# Get product price
base_price = await get_product_price(item_data.product_id)
# Check for customer-specific pricing
custom_price = await get_customer_price(
customer.id,
item_data.product_id,
item_data.quantity
)
unit_price = custom_price if custom_price else base_price
# Apply wholesale discount if applicable
if customer.customer_type == 'wholesale':
discount_pct = await calculate_volume_discount(
item_data.product_id,
item_data.quantity
)
else:
discount_pct = Decimal('0.00')
# Calculate line total
line_total = (unit_price * item_data.quantity) * (1 - discount_pct / 100)
# Create order item
order_item = OrderItem(
tenant_id=current_user.tenant_id,
order_id=order.id,
product_id=item_data.product_id,
product_name=item_data.product_name,
quantity=item_data.quantity,
unit=item_data.unit,
unit_price=unit_price,
discount_percentage=discount_pct,
line_total=line_total
)
db.add(order_item)
subtotal += line_total
# Calculate tax (e.g., Spanish IVA 10% for food)
tax_rate = Decimal('0.10')
tax_amount = subtotal * tax_rate
total_amount = subtotal + tax_amount
# Update order totals
order.subtotal = subtotal
order.tax_amount = tax_amount
order.total_amount = total_amount
# Set payment terms for B2B
if customer.customer_type == 'wholesale':
order.payment_due_date = date.today() + timedelta(days=customer.credit_term_days)
order.payment_status = 'unpaid'
else:
order.payment_status = 'paid' # Retail assumes immediate payment
await db.commit()
await db.refresh(order)
# Publish order created event
await publish_event('orders', 'order.created', {
'order_id': str(order.id),
'customer_id': str(customer.id),
'total_amount': float(order.total_amount),
'requested_date': order.requested_date.isoformat() if order.requested_date else None
})
return order
Recurring Order Generation
async def generate_recurring_orders(tenant_id: UUID):
"""
Generate orders from recurring order templates.
Run daily via orchestrator.
"""
# Get active recurring orders due today
today = date.today()
recurring_orders = await db.query(RecurringOrder).filter(
RecurringOrder.tenant_id == tenant_id,
RecurringOrder.is_active == True,
RecurringOrder.next_order_date <= today
).all()
generated_count = 0
for recurring in recurring_orders:
try:
# Create order from template
order = Order(
tenant_id=tenant_id,
order_number=await generate_order_number(tenant_id),
customer_id=recurring.customer_id,
order_type='standing',
order_source='auto_recurring',
status='confirmed',
order_date=today,
requested_date=recurring.next_order_date,
requested_time=recurring.delivery_time
)
db.add(order)
await db.flush()
# Add items from template
subtotal = Decimal('0.00')
for item_template in recurring.order_items:
product_price = await get_product_price(item_template['product_id'])
line_total = product_price * Decimal(str(item_template['quantity']))
order_item = OrderItem(
tenant_id=tenant_id,
order_id=order.id,
product_id=UUID(item_template['product_id']),
product_name=item_template['product_name'],
quantity=Decimal(str(item_template['quantity'])),
unit=item_template['unit'],
unit_price=product_price,
line_total=line_total
)
db.add(order_item)
subtotal += line_total
# Calculate totals
tax_amount = subtotal * Decimal('0.10')
order.subtotal = subtotal
order.tax_amount = tax_amount
order.total_amount = subtotal + tax_amount
# Update recurring order
recurring.last_generated_order_id = order.id
recurring.next_order_date = calculate_next_order_date(
recurring.next_order_date,
recurring.frequency
)
await db.commit()
generated_count += 1
# Publish event
await publish_event('orders', 'recurring_order.generated', {
'order_id': str(order.id),
'recurring_order_id': str(recurring.id),
'customer_id': str(recurring.customer_id)
})
except Exception as e:
logger.error("Failed to generate recurring order",
recurring_id=str(recurring.id),
error=str(e))
continue
logger.info("Generated recurring orders",
tenant_id=str(tenant_id),
count=generated_count)
return generated_count
Customer RFM Analysis
async def calculate_customer_rfm(customer_id: UUID) -> dict:
"""
Calculate RFM (Recency, Frequency, Monetary) metrics for customer.
"""
# Get customer orders
orders = await db.query(Order).filter(
Order.customer_id == customer_id,
Order.status.in_(['completed'])
).order_by(Order.order_date.desc()).all()
if not orders:
return {"rfm_score": 0, "segment": "inactive"}
# Recency: Days since last order
last_order_date = orders[0].order_date
recency_days = (date.today() - last_order_date).days
# Frequency: Number of orders in last 365 days
one_year_ago = date.today() - timedelta(days=365)
recent_orders = [o for o in orders if o.order_date >= one_year_ago]
frequency = len(recent_orders)
# Monetary: Total spend in last 365 days
monetary = sum(o.total_amount for o in recent_orders)
# Score each dimension (1-5 scale)
recency_score = 5 if recency_days <= 30 else \
4 if recency_days <= 60 else \
3 if recency_days <= 90 else \
2 if recency_days <= 180 else 1
frequency_score = 5 if frequency >= 12 else \
4 if frequency >= 6 else \
3 if frequency >= 3 else \
2 if frequency >= 1 else 1
monetary_score = 5 if monetary >= 5000 else \
4 if monetary >= 2000 else \
3 if monetary >= 500 else \
2 if monetary >= 100 else 1
# Overall RFM score
rfm_score = (recency_score + frequency_score + monetary_score) / 3
# Customer segment
if rfm_score >= 4.5:
segment = "champion"
elif rfm_score >= 3.5:
segment = "loyal"
elif rfm_score >= 2.5:
segment = "potential"
elif rfm_score >= 1.5:
segment = "at_risk"
else:
segment = "inactive"
return {
"rfm_score": round(rfm_score, 2),
"recency_days": recency_days,
"recency_score": recency_score,
"frequency": frequency,
"frequency_score": frequency_score,
"monetary": float(monetary),
"monetary_score": monetary_score,
"segment": segment
}
Events & Messaging
Published Events (RabbitMQ)
Exchange: orders
Routing Keys: orders.created, orders.completed, orders.cancelled, orders.overdue
Order Created Event
{
"event_type": "order_created",
"tenant_id": "uuid",
"order_id": "uuid",
"order_number": "ORD-2025-1106-001",
"customer_id": "uuid",
"customer_name": "Restaurante El Prado",
"order_type": "wholesale",
"total_amount": 450.00,
"requested_date": "2025-11-07",
"requested_time": "06:00:00",
"item_count": 12,
"timestamp": "2025-11-06T10:30:00Z"
}
Order Completed Event
{
"event_type": "order_completed",
"tenant_id": "uuid",
"order_id": "uuid",
"order_number": "ORD-2025-1106-001",
"customer_id": "uuid",
"total_amount": 450.00,
"payment_status": "paid",
"completed_at": "2025-11-07T06:15:00Z",
"timestamp": "2025-11-07T06:15:00Z"
}
Payment Overdue Alert
{
"event_type": "payment_overdue",
"tenant_id": "uuid",
"invoice_id": "uuid",
"invoice_number": "INV-2025-1106-001",
"customer_id": "uuid",
"customer_name": "Hotel Gran Vía",
"amount_due": 850.00,
"days_overdue": 15,
"due_date": "2025-10-22",
"timestamp": "2025-11-06T09:00:00Z"
}
Alert Events
The Orders service also publishes procurement-related alerts through the alert processor.
Exchange: events.exchange
Domain: procurement
1. POs Pending Approval Alert
Event Type: procurement.pos_pending_approval
Severity: urgent (>€10,000), high (>€5,000 or critical POs), medium (otherwise)
Trigger: New purchase orders created and awaiting approval
{
"event_type": "procurement.pos_pending_approval",
"severity": "high",
"metadata": {
"tenant_id": "uuid",
"pos_count": 3,
"total_amount": 6500.00,
"critical_count": 1,
"pos": [
{
"po_id": "uuid",
"po_number": "PO-001",
"supplier_id": "uuid",
"total_amount": 3000.00,
"auto_approved": false
}
],
"action_required": true,
"action_url": "/app/comprar"
}
}
2. Approval Reminder Alert
Event Type: procurement.approval_reminder
Severity: high (>36 hours pending), medium (otherwise)
Trigger: PO not approved within threshold time
{
"event_type": "procurement.approval_reminder",
"severity": "high",
"metadata": {
"tenant_id": "uuid",
"po_id": "uuid",
"po_number": "PO-001",
"supplier_name": "Supplier ABC",
"total_amount": 3000.00,
"hours_pending": 40,
"created_at": "2025-12-18T10:00:00Z",
"action_required": true,
"action_url": "/app/comprar?po=uuid"
}
}
3. Critical PO Escalation Alert
Event Type: procurement.critical_po_escalation
Severity: urgent
Trigger: Critical/urgent PO not approved in time
{
"event_type": "procurement.critical_po_escalation",
"severity": "urgent",
"metadata": {
"tenant_id": "uuid",
"po_id": "uuid",
"po_number": "PO-001",
"supplier_name": "Supplier ABC",
"total_amount": 5000.00,
"priority": "urgent",
"required_delivery_date": "2025-12-22",
"hours_pending": 48,
"escalated": true,
"action_required": true,
"action_url": "/app/comprar?po=uuid"
}
}
4. Auto-Approval Summary (Notification)
Event Type: procurement.auto_approval_summary
Type: Notification (not alert)
Trigger: Daily summary of auto-approved POs
{
"event_type": "procurement.auto_approval_summary",
"metadata": {
"tenant_id": "uuid",
"auto_approved_count": 5,
"total_auto_approved_amount": 8500.00,
"manual_approval_count": 2,
"summary_date": "2025-12-19",
"auto_approved_pos": [...],
"pending_approval_pos": [...],
"action_url": "/app/comprar"
}
}
5. PO Approved Confirmation (Notification)
Event Type: procurement.po_approved_confirmation
Type: Notification (not alert)
Trigger: Purchase order approved
{
"event_type": "procurement.po_approved_confirmation",
"metadata": {
"tenant_id": "uuid",
"po_id": "uuid",
"po_number": "PO-001",
"supplier_name": "Supplier ABC",
"total_amount": 3000.00,
"approved_by": "user@example.com",
"auto_approved": false,
"approved_at": "2025-12-19T14:30:00Z",
"action_url": "/app/comprar?po=uuid"
}
}
Consumed Events
- From Production: Batch completion updates order fulfillment status
- From Inventory: Stock availability affects order confirmation
- From Forecasting: Demand forecasts inform production for pending orders
Custom Metrics (Prometheus)
# Order metrics
orders_total = Counter(
'orders_total',
'Total orders created',
['tenant_id', 'order_type', 'order_source', 'status']
)
order_value_euros = Histogram(
'order_value_euros',
'Order value distribution',
['tenant_id', 'order_type'],
buckets=[10, 25, 50, 100, 200, 500, 1000, 2000, 5000]
)
# Customer metrics
customers_total = Gauge(
'customers_total',
'Total customers',
['tenant_id', 'customer_type']
)
customer_lifetime_value_euros = Histogram(
'customer_lifetime_value_euros',
'Customer lifetime value distribution',
['tenant_id', 'customer_type'],
buckets=[100, 500, 1000, 2000, 5000, 10000, 20000, 50000]
)
# Fulfillment metrics
order_fulfillment_time_hours = Histogram(
'order_fulfillment_time_hours',
'Time from order to fulfillment',
['tenant_id', 'order_type'],
buckets=[1, 6, 12, 24, 48, 72]
)
# Payment metrics
invoice_payment_time_days = Histogram(
'invoice_payment_time_days',
'Days from invoice to payment',
['tenant_id'],
buckets=[0, 7, 14, 21, 30, 45, 60, 90]
)
overdue_invoices_total = Gauge(
'overdue_invoices_total',
'Total overdue invoices',
['tenant_id']
)
Configuration
Environment Variables
Service Configuration:
PORT- Service port (default: 8010)DATABASE_URL- PostgreSQL connection stringREDIS_URL- Redis connection stringRABBITMQ_URL- RabbitMQ connection string
Order Configuration:
AUTO_CONFIRM_RETAIL_ORDERS- Auto-confirm retail orders (default: true)ORDER_NUMBER_PREFIX- Order number prefix (default: "ORD")DEFAULT_TAX_RATE- Default tax rate (default: 0.10 for Spain's 10% IVA)ENABLE_RECURRING_ORDERS- Enable recurring order generation (default: true)
Payment Configuration:
DEFAULT_CREDIT_TERMS_DAYS- Default payment terms (default: 30)OVERDUE_ALERT_THRESHOLD_DAYS- Days before overdue alert (default: 7)MAX_CREDIT_LIMIT- Maximum credit limit per customer (default: 10000.00)
Notification:
SEND_ORDER_CONFIRMATION- Send order confirmation to customer (default: true)SEND_READY_NOTIFICATION- Notify when order ready (default: true)SEND_OVERDUE_REMINDERS- Send overdue payment reminders (default: true)
Development Setup
Prerequisites
- Python 3.11+
- PostgreSQL 17
- Redis 7.4
- RabbitMQ 4.1
Local Development
cd services/orders
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export DATABASE_URL=postgresql://user:pass@localhost:5432/orders
export REDIS_URL=redis://localhost:6379/0
export RABBITMQ_URL=amqp://guest:guest@localhost:5672/
alembic upgrade head
python main.py
Integration Points
Dependencies
- Customers Service - Customer data (if separate)
- Products Service - Product catalog and pricing
- Inventory Service - Stock availability checks
- Production Service - Production planning for orders
- Auth Service - User authentication
- PostgreSQL - Order and customer data
- Redis - Caching
- RabbitMQ - Event publishing
Dependents
- Production Service - Orders trigger production planning
- Inventory Service - Orders reserve stock
- Invoicing/Accounting - Financial reporting
- Notification Service - Order confirmations and alerts
- AI Insights Service - Customer behavior analysis
- Frontend Dashboard - Order management UI
Business Value for VUE Madrid
Problem Statement
Spanish bakeries struggle with:
- Manual order tracking on paper or spreadsheets
- Lost orders and miscommunication (especially phone orders)
- No customer purchase history for relationship management
- Complex wholesale order management with multiple B2B clients
- Overdue payment tracking for credit accounts
- No analytics on customer behavior or product popularity
Solution
Bakery-IA Orders Service provides:
- Digital Order Management: Capture all orders across channels
- Customer Database: Complete purchase history and preferences
- B2B Automation: Recurring orders and automated invoicing
- Payment Tracking: Monitor outstanding payments with alerts
- Analytics: Customer segmentation and product performance
Quantifiable Impact
Revenue Growth:
- 10-20% revenue increase through improved B2B relationships
- 5-10% from reduced lost orders (99% order accuracy)
- 15-25% customer retention improvement with history tracking
- Total: €300-600/month additional revenue per bakery
Time Savings:
- 5-8 hours/week on order management and tracking
- 2-3 hours/week on invoicing and payment follow-up
- 1-2 hours/week on customer lookup and history
- Total: 8-13 hours/week saved
Financial Performance:
- 30% faster payment collection (overdue alerts)
- 50-70% time reduction on wholesale order processing
- 99%+ order accuracy vs. 85-90% manual
Target Market Fit (Spanish Bakeries)
- B2B Focus: Many Spanish bakeries supply restaurants, hotels, cafés
- Payment Terms: Spanish B2B typically uses Net 30-60 payment terms
- Relationship-Driven: Customer history critical for Spanish business culture
- Regulatory: Spanish tax law requires proper invoicing and records
ROI Calculation
Investment: €0 additional (included in platform subscription) Monthly Value: €300-600 additional revenue + cost savings Annual ROI: €3,600-7,200 value per bakery Payback: Immediate (included in subscription)
Copyright © 2025 Bakery-IA. All rights reserved.