# POS Service ## Overview The **POS (Point of Sale) Service** integrates with popular POS systems like Square, Toast, and Lightspeed to automatically sync sales transactions into Bakery-IA. It eliminates manual sales data entry, ensures real-time sales tracking, and provides the foundation for accurate demand forecasting. This service bridges the gap between retail operations and business intelligence, making the platform immediately valuable for bakeries already using modern POS systems. ## Key Features ### Multi-POS Integration - **Square Integration** - Full API integration with Square POS - **Toast Integration** - Restaurant POS system integration - **Lightspeed Integration** - Retail POS system integration - **Webhook Support** - Real-time transaction sync via webhooks - **OAuth Authentication** - Secure POS account linking - **Multi-Location Support** - Handle multiple store locations - **Automatic Reconnection** - Handle API token expiration gracefully ### Sales Data Synchronization - **Real-Time Sync** - Transactions sync within seconds - **Historical Import** - Import past sales data on initial setup - **Product Mapping** - Map POS products to Bakery-IA products - **Transaction Deduplication** - Prevent duplicate entries - **Data Validation** - Ensure data quality and accuracy - **Sync Status Tracking** - Monitor sync health and errors - **Manual Sync Trigger** - Force sync on demand ### Transaction Processing - **Line Item Details** - Product, quantity, price per transaction - **Payment Methods** - Cash, card, contactless tracking - **Customer Data** - Customer name, email if available - **Discounts & Taxes** - Full transaction details preserved - **Refunds & Voids** - Handle transaction cancellations - **Tips & Gratuities** - Track additional revenue - **Transaction Metadata** - Store name, cashier, timestamp ### Product Catalog Sync - **Product Import** - Sync product catalog from POS - **Category Mapping** - Map POS categories to Bakery-IA - **Price Sync** - Keep prices updated - **Product Updates** - Detect new products automatically - **SKU Matching** - Match by SKU, name, or manual mapping - **Inventory Integration** - Link POS products to inventory items ### Analytics & Monitoring - **Sync Dashboard** - Monitor sync status across POS systems - **Error Tracking** - Log and alert on sync failures - **Data Quality Metrics** - Track unmapped products, errors - **Sync Performance** - Monitor sync speed and latency - **Transaction Volume** - Daily/hourly transaction counts - **API Health Monitoring** - Track POS API availability ### Configuration Management - **POS Account Linking** - Connect POS accounts via OAuth - **Mapping Configuration** - Product and category mappings - **Sync Schedule** - Configure sync frequency - **Webhook Management** - Register/update webhook endpoints - **API Credentials** - Secure storage of API keys - **Multi-Tenant Isolation** - Separate POS accounts per tenant ## Business Value ### For Bakery Owners - **Zero Manual Entry** - Sales automatically sync to Bakery-IA - **Real-Time Visibility** - Know sales performance instantly - **Accurate Forecasting** - ML models use actual sales data - **Time Savings** - Eliminate daily sales data entry - **Data Accuracy** - 99.9%+ vs. manual entry errors - **Immediate ROI** - Value from day one of POS connection ### Quantifiable Impact - **Time Savings**: 5-8 hours/week eliminating manual entry - **Data Accuracy**: 99.9%+ vs. 85-95% manual entry - **Forecast Improvement**: 10-20% better accuracy with real data - **Revenue Tracking**: Real-time vs. end-of-day manual reconciliation - **Setup Time**: 15 minutes to connect vs. hours of manual entry - **Error Elimination**: Zero transcription errors ### For Sales Staff - **No Extra Work** - POS integration is invisible to staff - **Focus on Customers** - No post-sale data entry - **Instant Reporting** - Managers see sales in real-time ### For Managers - **Real-Time Dashboards** - Sales performance updates live - **Product Performance** - Know what's selling instantly - **Multi-Store Visibility** - All locations in one view - **Trend Detection** - Spot patterns as they emerge ## Technology Stack - **Framework**: FastAPI (Python 3.11+) - Async web framework - **Database**: PostgreSQL 17 - Transaction and mapping data - **Caching**: Redis 7.4 - Transaction deduplication cache - **Messaging**: RabbitMQ 4.1 - Transaction event publishing - **HTTP Client**: HTTPx - Async API calls to POS systems - **OAuth**: Authlib - OAuth 2.0 flows for POS authentication - **Webhooks**: FastAPI webhook receivers - **Logging**: Structlog - Structured JSON logging - **Metrics**: Prometheus Client - Sync metrics ## API Endpoints (Key Routes) ### POS Account Management - `GET /api/v1/pos/accounts` - List connected POS accounts - `POST /api/v1/pos/accounts` - Connect new POS account - `GET /api/v1/pos/accounts/{account_id}` - Get account details - `PUT /api/v1/pos/accounts/{account_id}` - Update account - `DELETE /api/v1/pos/accounts/{account_id}` - Disconnect account - `POST /api/v1/pos/accounts/{account_id}/reconnect` - Refresh OAuth tokens ### OAuth & Authentication - `GET /api/v1/pos/oauth/square/authorize` - Start Square OAuth flow - `GET /api/v1/pos/oauth/square/callback` - Square OAuth callback - `GET /api/v1/pos/oauth/toast/authorize` - Start Toast OAuth flow - `GET /api/v1/pos/oauth/toast/callback` - Toast OAuth callback - `GET /api/v1/pos/oauth/lightspeed/authorize` - Start Lightspeed OAuth - `GET /api/v1/pos/oauth/lightspeed/callback` - Lightspeed callback ### Synchronization - `POST /api/v1/pos/sync/{account_id}` - Trigger manual sync - `POST /api/v1/pos/sync/{account_id}/historical` - Import historical data - `GET /api/v1/pos/sync/{account_id}/status` - Get sync status - `GET /api/v1/pos/sync/{account_id}/history` - Sync history log ### Product Mapping - `GET /api/v1/pos/mappings` - List product mappings - `POST /api/v1/pos/mappings` - Create product mapping - `PUT /api/v1/pos/mappings/{mapping_id}` - Update mapping - `DELETE /api/v1/pos/mappings/{mapping_id}` - Delete mapping - `GET /api/v1/pos/mappings/unmapped` - List unmapped POS products - `POST /api/v1/pos/mappings/auto-map` - Auto-map by name/SKU ### Webhooks - `POST /api/v1/pos/webhooks/square` - Square webhook receiver - `POST /api/v1/pos/webhooks/toast` - Toast webhook receiver - `POST /api/v1/pos/webhooks/lightspeed` - Lightspeed webhook receiver - `POST /api/v1/pos/accounts/{account_id}/webhooks/register` - Register webhooks ### Analytics - `GET /api/v1/pos/analytics/dashboard` - POS sync dashboard - `GET /api/v1/pos/analytics/sync-health` - Sync health metrics - `GET /api/v1/pos/analytics/unmapped-revenue` - Revenue from unmapped products ## Database Schema ### Main Tables **pos_accounts** ```sql CREATE TABLE pos_accounts ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_provider VARCHAR(50) NOT NULL, -- square, toast, lightspeed account_name VARCHAR(255), location_id VARCHAR(255), -- POS location identifier location_name VARCHAR(255), -- OAuth credentials (encrypted) access_token TEXT, refresh_token TEXT, token_expires_at TIMESTAMP, merchant_id VARCHAR(255), -- Sync configuration sync_enabled BOOLEAN DEFAULT TRUE, sync_frequency_minutes INTEGER DEFAULT 15, last_sync_at TIMESTAMP, last_successful_sync_at TIMESTAMP, next_sync_at TIMESTAMP, -- Webhook configuration webhook_id VARCHAR(255), webhook_url VARCHAR(500), webhook_signature_key TEXT, -- Status status VARCHAR(50) DEFAULT 'active', -- active, disconnected, error error_message TEXT, error_count INTEGER DEFAULT 0, last_error_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, pos_provider, location_id) ); ``` **pos_transactions** ```sql CREATE TABLE pos_transactions ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_account_id UUID REFERENCES pos_accounts(id) ON DELETE CASCADE, pos_transaction_id VARCHAR(255) NOT NULL, -- Original POS transaction ID pos_provider VARCHAR(50) NOT NULL, -- Transaction details transaction_date TIMESTAMP NOT NULL, transaction_type VARCHAR(50) DEFAULT 'sale', -- sale, refund, void status VARCHAR(50), -- completed, pending, failed -- Financial subtotal DECIMAL(10, 2) NOT NULL, tax_amount DECIMAL(10, 2) DEFAULT 0.00, discount_amount DECIMAL(10, 2) DEFAULT 0.00, tip_amount DECIMAL(10, 2) DEFAULT 0.00, total_amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(10) DEFAULT 'EUR', -- Payment payment_method VARCHAR(50), -- cash, card, contactless, mobile card_last_four VARCHAR(4), card_brand VARCHAR(50), -- Customer (if available) customer_name VARCHAR(255), customer_email VARCHAR(255), customer_phone VARCHAR(50), -- Metadata cashier_name VARCHAR(255), device_name VARCHAR(255), receipt_number VARCHAR(100), -- Processing synced_to_sales BOOLEAN DEFAULT FALSE, sales_record_id UUID, sync_error TEXT, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, pos_provider, pos_transaction_id) ); ``` **pos_transaction_items** ```sql CREATE TABLE pos_transaction_items ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_transaction_id UUID REFERENCES pos_transactions(id) ON DELETE CASCADE, pos_item_id VARCHAR(255), -- POS product ID -- Product details product_name VARCHAR(255) NOT NULL, product_sku VARCHAR(100), category VARCHAR(100), quantity DECIMAL(10, 2) NOT NULL, unit_price DECIMAL(10, 2) NOT NULL, discount_amount DECIMAL(10, 2) DEFAULT 0.00, line_total DECIMAL(10, 2) NOT NULL, -- Mapping mapped_product_id UUID, -- Bakery-IA product ID is_mapped BOOLEAN DEFAULT FALSE, -- Modifiers (e.g., "Extra frosting") modifiers JSONB, created_at TIMESTAMP DEFAULT NOW() ); ``` **pos_product_mappings** ```sql CREATE TABLE pos_product_mappings ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_account_id UUID REFERENCES pos_accounts(id) ON DELETE CASCADE, pos_product_id VARCHAR(255) NOT NULL, pos_product_name VARCHAR(255) NOT NULL, pos_product_sku VARCHAR(100), pos_category VARCHAR(100), -- Mapping bakery_product_id UUID NOT NULL, -- Link to products catalog bakery_product_name VARCHAR(255) NOT NULL, -- Configuration mapping_type VARCHAR(50) DEFAULT 'manual', -- manual, auto, sku confidence_score DECIMAL(3, 2), -- For auto-mapping created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(tenant_id, pos_account_id, pos_product_id) ); ``` **pos_sync_logs** ```sql CREATE TABLE pos_sync_logs ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_account_id UUID REFERENCES pos_accounts(id) ON DELETE CASCADE, sync_started_at TIMESTAMP NOT NULL, sync_completed_at TIMESTAMP, sync_duration_seconds INTEGER, -- Status status VARCHAR(50) NOT NULL, -- success, partial, failed error_message TEXT, -- Metrics transactions_fetched INTEGER DEFAULT 0, transactions_processed INTEGER DEFAULT 0, transactions_failed INTEGER DEFAULT 0, new_products_discovered INTEGER DEFAULT 0, unmapped_products_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW() ); ``` **pos_webhooks** ```sql CREATE TABLE pos_webhooks ( id UUID PRIMARY KEY, tenant_id UUID NOT NULL, pos_account_id UUID REFERENCES pos_accounts(id) ON DELETE CASCADE, webhook_event_id VARCHAR(255), -- POS webhook event ID event_type VARCHAR(100) NOT NULL, -- payment.created, order.updated, etc. event_data JSONB NOT NULL, received_at TIMESTAMP DEFAULT NOW(), processed_at TIMESTAMP, processing_status VARCHAR(50) DEFAULT 'pending', -- pending, processed, failed error_message TEXT, retry_count INTEGER DEFAULT 0 ); ``` ### Indexes for Performance ```sql CREATE INDEX idx_pos_accounts_tenant ON pos_accounts(tenant_id, status); CREATE INDEX idx_pos_transactions_tenant_date ON pos_transactions(tenant_id, transaction_date DESC); CREATE INDEX idx_pos_transactions_account ON pos_transactions(pos_account_id); CREATE INDEX idx_pos_transactions_synced ON pos_transactions(tenant_id, synced_to_sales) WHERE synced_to_sales = FALSE; CREATE INDEX idx_pos_transaction_items_transaction ON pos_transaction_items(pos_transaction_id); CREATE INDEX idx_pos_transaction_items_unmapped ON pos_transaction_items(tenant_id, is_mapped) WHERE is_mapped = FALSE; CREATE INDEX idx_pos_mappings_account ON pos_product_mappings(pos_account_id); CREATE INDEX idx_pos_sync_logs_account_date ON pos_sync_logs(pos_account_id, sync_started_at DESC); ``` ## Business Logic Examples ### Square Transaction Sync ```python async def sync_square_transactions(pos_account_id: UUID, start_date: datetime = None) -> dict: """ Sync transactions from Square POS. """ # Get POS account pos_account = await get_pos_account(pos_account_id) if pos_account.pos_provider != 'square': raise ValueError("Not a Square account") # Check token expiration if pos_account.token_expires_at and pos_account.token_expires_at < datetime.utcnow(): await refresh_square_oauth_token(pos_account) # Create sync log sync_log = POSSyncLog( tenant_id=pos_account.tenant_id, pos_account_id=pos_account.id, sync_started_at=datetime.utcnow(), status='in_progress' ) db.add(sync_log) await db.flush() try: # Default to last sync time or 24 hours ago if not start_date: start_date = pos_account.last_successful_sync_at or (datetime.utcnow() - timedelta(days=1)) # Call Square API async with httpx.AsyncClient() as client: response = await client.post( f"https://connect.squareup.com/v2/payments/list", headers={ "Authorization": f"Bearer {pos_account.access_token}", "Content-Type": "application/json" }, json={ "location_id": pos_account.location_id, "begin_time": start_date.isoformat(), "end_time": datetime.utcnow().isoformat(), "limit": 100 } ) if response.status_code != 200: raise Exception(f"Square API error: {response.text}") data = response.json() payments = data.get('payments', []) transactions_processed = 0 transactions_failed = 0 for payment in payments: try: # Check for duplicate existing = await db.query(POSTransaction).filter( POSTransaction.tenant_id == pos_account.tenant_id, POSTransaction.pos_transaction_id == payment['id'] ).first() if existing: continue # Skip duplicates # Create transaction transaction = POSTransaction( tenant_id=pos_account.tenant_id, pos_account_id=pos_account.id, pos_transaction_id=payment['id'], pos_provider='square', transaction_date=datetime.fromisoformat(payment['created_at'].replace('Z', '+00:00')), transaction_type='sale' if payment['status'] == 'COMPLETED' else 'pending', status=payment['status'].lower(), total_amount=Decimal(payment['amount_money']['amount']) / 100, currency=payment['amount_money']['currency'], payment_method=payment.get('card_details', {}).get('card', {}).get('card_brand', 'unknown').lower(), card_last_four=payment.get('card_details', {}).get('card', {}).get('last_4'), receipt_number=payment.get('receipt_number') ) db.add(transaction) await db.flush() # Get line items from order if 'order_id' in payment: order_response = await client.get( f"https://connect.squareup.com/v2/orders/{payment['order_id']}", headers={"Authorization": f"Bearer {pos_account.access_token}"} ) if order_response.status_code == 200: order = order_response.json().get('order', {}) line_items = order.get('line_items', []) for item in line_items: # Create transaction item pos_item = POSTransactionItem( tenant_id=pos_account.tenant_id, pos_transaction_id=transaction.id, pos_item_id=item.get('catalog_object_id'), product_name=item['name'], quantity=Decimal(item['quantity']), unit_price=Decimal(item['base_price_money']['amount']) / 100, line_total=Decimal(item['total_money']['amount']) / 100 ) # Check for mapping mapping = await get_product_mapping( pos_account.id, item.get('catalog_object_id') ) if mapping: pos_item.mapped_product_id = mapping.bakery_product_id pos_item.is_mapped = True db.add(pos_item) # Sync to sales service await sync_transaction_to_sales(transaction) transactions_processed += 1 except Exception as e: logger.error("Failed to process Square payment", payment_id=payment.get('id'), error=str(e)) transactions_failed += 1 continue # Update sync log sync_log.sync_completed_at = datetime.utcnow() sync_log.sync_duration_seconds = int((sync_log.sync_completed_at - sync_log.sync_started_at).total_seconds()) sync_log.status = 'success' if transactions_failed == 0 else 'partial' sync_log.transactions_fetched = len(payments) sync_log.transactions_processed = transactions_processed sync_log.transactions_failed = transactions_failed # Update pos account pos_account.last_sync_at = datetime.utcnow() pos_account.last_successful_sync_at = datetime.utcnow() pos_account.error_count = 0 await db.commit() # Publish sync completed event await publish_event('pos', 'pos.sync_completed', { 'tenant_id': str(pos_account.tenant_id), 'pos_account_id': str(pos_account.id), 'transactions_processed': transactions_processed, 'transactions_failed': transactions_failed }) return { 'status': 'success', 'transactions_processed': transactions_processed, 'transactions_failed': transactions_failed } except Exception as e: sync_log.status = 'failed' sync_log.error_message = str(e) sync_log.sync_completed_at = datetime.utcnow() pos_account.error_count += 1 pos_account.last_error_at = datetime.utcnow() pos_account.error_message = str(e) await db.commit() logger.error("Square sync failed", pos_account_id=str(pos_account_id), error=str(e)) raise ``` ### Auto Product Mapping ```python async def auto_map_products(pos_account_id: UUID) -> dict: """ Automatically map POS products to Bakery-IA products using name/SKU matching. """ # Get unmapped transaction items unmapped_items = await db.query(POSTransactionItem).filter( POSTransactionItem.pos_account_id == pos_account_id, POSTransactionItem.is_mapped == False ).all() # Get unique products unique_products = {} for item in unmapped_items: key = (item.pos_item_id, item.product_name, item.product_sku) if key not in unique_products: unique_products[key] = item # Get all Bakery-IA products bakery_products = await get_all_products(pos_account.tenant_id) mapped_count = 0 high_confidence_count = 0 for (pos_id, pos_name, pos_sku), item in unique_products.items(): best_match = None confidence = 0.0 # Try SKU match first (highest confidence) if pos_sku: for product in bakery_products: if product.sku and product.sku.upper() == pos_sku.upper(): best_match = product confidence = 1.0 break # Try name match (fuzzy matching) if not best_match: from difflib import SequenceMatcher for product in bakery_products: # Calculate similarity ratio ratio = SequenceMatcher(None, pos_name.lower(), product.name.lower()).ratio() if ratio > confidence and ratio > 0.80: # 80% similarity threshold best_match = product confidence = ratio # Create mapping if confidence is high enough if best_match and confidence >= 0.80: mapping = POSProductMapping( tenant_id=pos_account.tenant_id, pos_account_id=pos_account_id, pos_product_id=pos_id, pos_product_name=pos_name, pos_product_sku=pos_sku, bakery_product_id=best_match.id, bakery_product_name=best_match.name, mapping_type='auto', confidence_score=Decimal(str(round(confidence, 2))) ) db.add(mapping) # Update all unmapped items with this product await db.query(POSTransactionItem).filter( POSTransactionItem.pos_account_id == pos_account_id, POSTransactionItem.pos_item_id == pos_id, POSTransactionItem.is_mapped == False ).update({ 'mapped_product_id': best_match.id, 'is_mapped': True }) mapped_count += 1 if confidence >= 0.95: high_confidence_count += 1 await db.commit() return { 'total_unmapped_products': len(unique_products), 'products_mapped': mapped_count, 'high_confidence_mappings': high_confidence_count, 'remaining_unmapped': len(unique_products) - mapped_count } ``` ### Webhook Handler ```python async def handle_square_webhook(request: Request) -> dict: """ Handle incoming webhook from Square. """ # Verify webhook signature signature = request.headers.get('X-Square-Signature') body = await request.body() # Signature verification (simplified) # In production, use proper HMAC verification with webhook signature key # Parse webhook payload payload = await request.json() event_type = payload.get('type') merchant_id = payload.get('merchant_id') # Find POS account pos_account = await db.query(POSAccount).filter( POSAccount.pos_provider == 'square', POSAccount.merchant_id == merchant_id, POSAccount.status == 'active' ).first() if not pos_account: logger.warning("Webhook received for unknown merchant", merchant_id=merchant_id) return {'status': 'ignored', 'reason': 'unknown_merchant'} # Store webhook for processing webhook = POSWebhook( tenant_id=pos_account.tenant_id, pos_account_id=pos_account.id, webhook_event_id=payload.get('event_id'), event_type=event_type, event_data=payload, processing_status='pending' ) db.add(webhook) await db.commit() # Process webhook asynchronously # (In production, use background task queue) try: if event_type == 'payment.created': # Sync this specific payment payment_id = payload.get('data', {}).get('id') await sync_specific_square_payment(pos_account, payment_id) webhook.processing_status = 'processed' webhook.processed_at = datetime.utcnow() except Exception as e: webhook.processing_status = 'failed' webhook.error_message = str(e) logger.error("Webhook processing failed", webhook_id=str(webhook.id), error=str(e)) await db.commit() return {'status': 'received'} ``` ## Events & Messaging ### Published Events (RabbitMQ) **Exchange**: `pos` **Routing Keys**: `pos.sync_completed`, `pos.mapping_needed`, `pos.error` **POS Sync Completed Event** ```json { "event_type": "pos_sync_completed", "tenant_id": "uuid", "pos_account_id": "uuid", "pos_provider": "square", "location_name": "VUE Madrid - Centro", "transactions_processed": 45, "transactions_failed": 0, "new_products_discovered": 3, "sync_duration_seconds": 12, "timestamp": "2025-11-06T10:30:00Z" } ``` **POS Mapping Needed Alert** ```json { "event_type": "pos_mapping_needed", "tenant_id": "uuid", "pos_account_id": "uuid", "unmapped_products_count": 5, "unmapped_revenue_euros": 125.50, "sample_unmapped_products": [ {"pos_product_name": "Croissant Especial", "transaction_count": 12}, {"pos_product_name": "Pan Integral Grande", "transaction_count": 8} ], "timestamp": "2025-11-06T14:00:00Z" } ``` **POS Error Alert** ```json { "event_type": "pos_error", "tenant_id": "uuid", "pos_account_id": "uuid", "pos_provider": "square", "error_type": "authentication_failed", "error_message": "OAuth token expired", "consecutive_failures": 3, "action_required": "Reconnect POS account", "timestamp": "2025-11-06T11:30:00Z" } ``` ### Consumed Events - **From Sales**: Sales data validation triggers re-sync if discrepancies found - **From Orchestrator**: Daily sync triggers for all active POS accounts ## Custom Metrics (Prometheus) ```python # POS metrics pos_accounts_total = Gauge( 'pos_accounts_total', 'Total connected POS accounts', ['tenant_id', 'pos_provider', 'status'] ) pos_transactions_synced_total = Counter( 'pos_transactions_synced_total', 'Total transactions synced from POS', ['tenant_id', 'pos_provider'] ) pos_sync_duration_seconds = Histogram( 'pos_sync_duration_seconds', 'POS sync duration', ['tenant_id', 'pos_provider'], buckets=[5, 10, 30, 60, 120, 300] ) pos_sync_errors_total = Counter( 'pos_sync_errors_total', 'Total POS sync errors', ['tenant_id', 'pos_provider', 'error_type'] ) pos_unmapped_products_total = Gauge( 'pos_unmapped_products_total', 'Products without mapping', ['tenant_id', 'pos_account_id'] ) ``` ## Configuration ### Environment Variables **Service Configuration:** - `PORT` - Service port (default: 8013) - `DATABASE_URL` - PostgreSQL connection string - `REDIS_URL` - Redis connection string - `RABBITMQ_URL` - RabbitMQ connection string **POS Provider Configuration:** - `SQUARE_APP_ID` - Square application ID - `SQUARE_APP_SECRET` - Square application secret - `TOAST_CLIENT_ID` - Toast client ID - `TOAST_CLIENT_SECRET` - Toast client secret - `LIGHTSPEED_CLIENT_ID` - Lightspeed client ID - `LIGHTSPEED_CLIENT_SECRET` - Lightspeed client secret **Sync Configuration:** - `DEFAULT_SYNC_FREQUENCY_MINUTES` - Default sync interval (default: 15) - `ENABLE_WEBHOOKS` - Use webhooks for real-time sync (default: true) - `MAX_SYNC_RETRIES` - Max retry attempts (default: 3) - `HISTORICAL_IMPORT_DAYS` - Days to import on initial setup (default: 90) **Mapping Configuration:** - `AUTO_MAPPING_ENABLED` - Enable automatic product mapping (default: true) - `AUTO_MAPPING_CONFIDENCE_THRESHOLD` - Minimum confidence (default: 0.80) - `ALERT_ON_UNMAPPED_PRODUCTS` - Alert for unmapped products (default: true) ## Development Setup ### Prerequisites - Python 3.11+ - PostgreSQL 17 - Redis 7.4 - RabbitMQ 4.1 - POS system developer accounts (Square, Toast, Lightspeed) ### Local Development ```bash cd services/pos python -m venv venv source venv/bin/activate pip install -r requirements.txt export DATABASE_URL=postgresql://user:pass@localhost:5432/pos export REDIS_URL=redis://localhost:6379/0 export RABBITMQ_URL=amqp://guest:guest@localhost:5672/ export SQUARE_APP_ID=your_square_app_id export SQUARE_APP_SECRET=your_square_app_secret alembic upgrade head python main.py ``` ## Integration Points ### Dependencies - **POS Providers** - Square, Toast, Lightspeed APIs - **Auth Service** - User authentication - **PostgreSQL** - Transaction and mapping data - **Redis** - Deduplication cache - **RabbitMQ** - Event publishing ### Dependents - **Sales Service** - Receives synced transaction data - **Forecasting Service** - Uses sales data for ML models - **Inventory Service** - Stock deduction from sales - **Notification Service** - Sync error alerts - **Frontend Dashboard** - POS connection and mapping UI ## Business Value for VUE Madrid ### Problem Statement Spanish bakeries struggle with: - Hours of daily manual sales data entry - Transcription errors reducing forecast accuracy - Delayed visibility into sales performance - No integration between POS and business intelligence - Double data entry (POS + spreadsheets/accounting) ### Solution Bakery-IA POS Service provides: - **Zero Manual Entry**: Automatic transaction sync from POS - **Real-Time Data**: Sales data available within seconds - **Higher Accuracy**: 99.9%+ vs. 85-95% manual entry - **Immediate Value**: Works from day one, no setup needed - **Universal Compatibility**: Works with popular POS systems ### Quantifiable Impact **Time Savings:** - 5-8 hours/week eliminating manual data entry - 1-2 hours/week on sales reconciliation - **Total: 6-10 hours/week saved** **Data Quality:** - 99.9%+ accuracy vs. 85-95% manual entry - Zero transcription errors - Real-time vs. end-of-day data availability - 10-20% forecast accuracy improvement **Operational Efficiency:** - 15-minute setup vs. hours of daily manual entry - Automatic sync every 15 minutes - Multi-location support in single dashboard - Instant error detection and alerts ### Target Market Fit (Spanish Bakeries) - **POS Adoption**: Growing use of Square, Toast, Lightspeed in Spain - **Labor Costs**: Spanish minimum wage makes manual entry expensive - **Modernization**: New generation of bakery owners embrace technology - **Market Trend**: Digital transformation in retail/food service ### ROI Calculation **Investment**: €0 additional (included in platform subscription) **Time Savings Value**: 6-10 hours/week × €15/hour = €360-600/month **Forecast Improvement Value**: 10-20% better accuracy = €100-400/month **Total Monthly Value**: €460-1,000 **Annual ROI**: €5,520-12,000 value per bakery **Payback**: Immediate (included in subscription) ### Competitive Advantage - **First-Mover**: Few Spanish bakery platforms offer POS integration - **Multi-POS Support**: Flexibility for customers to choose POS - **Plug-and-Play**: 15-minute setup vs. competitors requiring IT setup - **Real-Time**: Webhook support for instant sync vs. batch processing --- **Copyright © 2025 Bakery-IA. All rights reserved.**