diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..1827fd8e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(lsof -ti:3000,8001)", + "Bash(xargs kill:*)", + "Bash(netstat -ano)", + "Bash(taskkill /F /PID 40304)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md index d2086efa..a3cc558e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,6 +23,9 @@ Use the Task tool with these specialized subagents for appropriate tasks: - **ALWAYS use Playwright MCP tools** (`mcp__playwright__*`) for browser testing - Test against: `http://localhost:3000` (frontend), `http://localhost:8001` (API) +### Code Quality +- **Always document non-obvious logic changes with comments** + ## Stack - **Frontend**: Vue 3 + Composition API + Vite (port 3000) - **Backend**: Python FastAPI (port 8001) diff --git a/architecture.html b/architecture.html new file mode 100644 index 00000000..7f7bf1b1 --- /dev/null +++ b/architecture.html @@ -0,0 +1,716 @@ + + + + + + Factory Inventory Management System - Architecture + + + +
+
+

Factory Inventory Management System

+

Complete System Architecture & Technical Documentation

+
+ + +
+

System Overview

+

+ A modern, full-stack inventory management system featuring real-time data filtering, + comprehensive reporting, and demand forecasting. Built with Vue 3 on the frontend and + Python FastAPI on the backend, with in-memory JSON data storage optimized for rapid prototyping and demo scenarios. +

+ +
+
+
2
+
Main Services
+
+
+
4
+
Global Filters
+
+
+
15+
+
API Endpoints
+
+
+
7
+
Core Views
+
+
+
+ + +
+

Technology Stack

+
+
+

🎨 Frontend

+

Vue 3 with Composition API - Modern reactive component framework

+

Vite - Lightning-fast build tool and dev server

+

Axios - HTTP client for API communication

+

Vue Router - Client-side routing

+

CSS Grid & Flexbox - Responsive layouts

+
+ Vue 3 + Composition API + Vite + Axios + i18n +
+
+ +
+

⚙️ Backend

+

FastAPI - High-performance async Python web framework

+

Pydantic - Data validation and serialization

+

Uvicorn - ASGI application server

+

JSON Files - In-memory data storage

+

pytest - Testing framework

+
+ FastAPI + Python + Pydantic + JSON + pytest +
+
+
+
+ + +
+

System Architecture

+
+
+
Frontend (Vue 3)
+
+
API Layer (Axios)
+
+
Backend (FastAPI)
+
+
Data (JSON Files)
+
+
+ +
+

Architecture Layers:

+
    +
  • Frontend Layer (Port 3000): Vue 3 components with reactive composables managing global filters and state
  • +
  • API Client Layer: Centralized Axios instance building query parameters and handling HTTP requests
  • +
  • Backend Layer (Port 8001): FastAPI routes with Pydantic models for data validation and filtering logic
  • +
  • Data Layer: JSON files loaded into memory, filtered in-memory on each request
  • +
+
+
+ + +
+

Data Flow: Request Cycle

+
+
+
1
+
User Interaction - Selects warehouse, category, status, or time period in FilterBar
+
+
+ +
+
2
+
State Update - useFilters composable updates reactive refs with new filter values
+
+
+ +
+
3
+
Watch Triggered - Component watches filters and calls loadData() API method
+
+
+ +
+
4
+
API Client - Builds URLSearchParams with all active filters (ignores 'all' values)
+
+
+ +
+
5
+
HTTP Request - Axios sends GET request with query parameters
+
+
+ +
+
6
+
Backend Processing - FastAPI route loads JSON into memory and applies filters
+
+
+ +
+
7
+
Validation & Serialization - Pydantic models validate and serialize data to JSON
+
+
+ +
+
8
+
Response Received - Frontend receives filtered data array
+
+
+ +
+
9
+
State Storage - Component stores raw data in ref, derived data in computed properties
+
+
+ +
+
10
+
Rendering - Vue reactivity triggers template update with new data
+
+
+
+ + +
+

Frontend Structure

+
+
+

Views (Pages)

+
    +
  • Dashboard
  • +
  • Inventory
  • +
  • Orders
  • +
  • Demand
  • +
  • Backlog
  • +
  • Spending
  • +
  • Reports
  • +
+
+ +
+

Components

+
    +
  • FilterBar
  • +
  • ProfileMenu
  • +
  • LanguageSwitcher
  • +
  • Various Modals
  • +
  • Navigation
  • +
+
+ +
+

Composables

+
    +
  • useFilters
  • +
  • useAuth
  • +
  • useI18n
  • +
  • Custom hooks
  • +
+
+
+
+ + +
+

API Endpoints

+

All endpoints are prefixed with /api/

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EndpointMethodFilters SupportedReturns
/inventoryGETwarehouse, categoryList of inventory items
/inventory/{id}GETSingle inventory item
/ordersGETwarehouse, category, status, monthList of orders
/orders/{id}GETSingle order
/dashboard/summaryGETwarehouse, category, status, monthDashboard KPIs
/demandGETDemand forecasts
/backlogGETBacklog items
/spending/*GETFinancial data
/reports/*GETQuarterly & monthly reports
+
+ + +
+

Global Filter System

+

+ Four filters are managed centrally via useFilters composable and applied to + compatible endpoints via query parameters. +

+ +
+
+

Time Period

+

Filters orders by month/quarter. Format: 2025-01 or Q1-2025

+

Supported endpoints: /orders, /dashboard/summary

+
+ +
+

Warehouse

+

Filters by storage location: San Francisco, Boston, Chicago

+

Supported endpoints: /inventory, /orders, /dashboard/summary

+
+ +
+

Category

+

Filters by product category: Circuit Boards, Sensors, Power Supplies

+

Supported endpoints: /inventory, /orders, /dashboard/summary

+
+ +
+

Order Status

+

Filters orders by status: Delivered, Processing, Backordered, Shipped

+

Supported endpoints: /orders, /dashboard/summary

+
+
+
+ + +
+

Key Features & Views

+
+
+

📊 Dashboard

+

Real-time KPI cards showing inventory value, low stock alerts, pending orders, and backlog status. Responsive to all four filters.

+
+ +
+

📦 Inventory

+

Browse all SKUs by warehouse with detailed information. Warehouse and category filters. Detail modals with reorder recommendations.

+
+ +
+

📋 Orders

+

Track customer orders with status lifecycle (Delivered, Processing, Backordered, Shipped). Monthly breakdown and fulfillment metrics.

+
+ +
+

📈 Demand Forecasting

+

Analyze current vs. forecasted demand with trend indicators (Up, Down, Stable). Global view across all items.

+
+ +
+

⚠️ Backlog Management

+

Monitor delayed items with priority levels (Critical, High, Medium). Create and track purchase orders for backlog resolution.

+
+ +
+

💰 Financial Analytics

+

Monthly spending trends, category breakdown, transaction history, and budget vs. actual performance tracking.

+
+
+
+ + +
+

Core Data Models

+
+

Pydantic Models (Backend Validation)

+
+InventoryItem: id, sku, name, category, warehouse, quantity, cost, location +Order: id, order_number, customer, items[], status, dates, total_value, warehouse, category +DemandForecast: id, item_sku, current_demand, forecasted_demand, trend, period +BacklogItem: id, order_id, item_sku, quantity_needed, quantity_available, days_delayed, priority +PurchaseOrder: id, backlog_item_id, supplier, quantity, unit_cost, delivery_date, status +
+
+
+ + +
+

Development Patterns

+
+
+

Frontend Reactivity

+
    +
  • refs: Raw data, loading states, modal visibility
  • +
  • computed: Filtered lists, KPI calculations
  • +
  • watch: Monitor filter changes, trigger API calls
  • +
  • composables: Shared logic across components
  • +
+
+ +
+

Backend Processing

+
    +
  • Startup: Load JSON files into memory
  • +
  • Per-Request: Apply filters to in-memory data
  • +
  • Validation: Pydantic models serialize responses
  • +
  • CORS: Enabled for dev environment
  • +
+
+
+
+ + +
+

Running the Application

+
+
+

Frontend Server

+

Port: 3000 (or next available)

+

Command: cd client && npm run dev

+

URL: http://localhost:3000

+
+ +
+

Backend Server

+

Port: 8001

+

Command: cd server && python main.py

+

Docs: http://localhost:8001/docs

+
+
+
+ + +
+ + \ No newline at end of file diff --git a/client/src/App.vue b/client/src/App.vue index c2da05a5..424a595a 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -16,6 +16,7 @@ {{ t('nav.orders') }} + Restocking {{ t('nav.finance') }} @@ -162,6 +163,32 @@ export default { diff --git a/client/src/views/Orders.vue b/client/src/views/Orders.vue index 7413f6e6..ddc3c637 100644 --- a/client/src/views/Orders.vue +++ b/client/src/views/Orders.vue @@ -5,6 +5,51 @@

{{ t('orders.description') }}

+ +
+
+

Submitted Restocking Orders ({{ restockingOrders.length }})

+
+
+ + + + + + + + + + + + + + + + + + + + + +
Order NumberItemsStatusOrder DateExpected DeliveryTotal Value
{{ rOrder.order_number }} +
+ + {{ rOrder.items.length }} item{{ rOrder.items.length !== 1 ? 's' : '' }} + +
+
+ {{ item.name }} + SKU: {{ item.sku }} — Qty: {{ item.quantity }} @ {{ currencySymbol }}{{ item.unit_price }} +
+
+
+
+ Processing + {{ formatDate(rOrder.order_date) }}{{ formatDate(rOrder.expected_delivery) }}{{ currencySymbol }}{{ Number(rOrder.total_value).toLocaleString() }}
+
+
+
{{ t('common.loading') }}
{{ error }}
@@ -95,6 +140,9 @@ export default { const loading = ref(true) const error = ref(null) const orders = ref([]) + const restockingOrders = ref([]) + + const hasRestockingOrders = computed(() => restockingOrders.value.length > 0) // Use shared filters const { @@ -124,6 +172,15 @@ export default { } } + const loadRestockingOrders = async () => { + try { + restockingOrders.value = await api.getRestockingOrders() + } catch (err) { + // Non-critical: log but don't surface to user + console.error('Failed to load restocking orders:', err) + } + } + // Watch for filter changes and reload data watch([selectedPeriod, selectedLocation, selectedCategory, selectedStatus], () => { loadOrders() @@ -153,13 +210,18 @@ export default { }) } - onMounted(loadOrders) + onMounted(() => { + loadOrders() + loadRestockingOrders() + }) return { t, loading, error, orders, + restockingOrders, + hasRestockingOrders, getOrdersByStatus, getOrderStatusClass, formatDate, diff --git a/client/src/views/Reports.vue b/client/src/views/Reports.vue index 35187eaf..341512b0 100644 --- a/client/src/views/Reports.vue +++ b/client/src/views/Reports.vue @@ -1,35 +1,35 @@ + + diff --git a/dashboard.png b/dashboard.png new file mode 100644 index 00000000..a92a53bd Binary files /dev/null and b/dashboard.png differ diff --git a/demand.png b/demand.png new file mode 100644 index 00000000..3f245b2d Binary files /dev/null and b/demand.png differ diff --git a/finance.png b/finance.png new file mode 100644 index 00000000..bc391adb Binary files /dev/null and b/finance.png differ diff --git a/orders.png b/orders.png new file mode 100644 index 00000000..718fef52 Binary files /dev/null and b/orders.png differ diff --git a/reports.png b/reports.png new file mode 100644 index 00000000..c49c8527 Binary files /dev/null and b/reports.png differ diff --git a/server/main.py b/server/main.py index a0c2d8c5..6ca1328f 100644 --- a/server/main.py +++ b/server/main.py @@ -2,6 +2,7 @@ from fastapi.middleware.cors import CORSMiddleware from typing import List, Optional from pydantic import BaseModel +from datetime import datetime, timedelta from mock_data import inventory_items, orders, demand_forecasts, backlog_items, spending_summary, monthly_spending, category_spending, recent_transactions, purchase_orders app = FastAPI(title="Factory Inventory Management System") @@ -55,6 +56,10 @@ def apply_filters(items: list, warehouse: Optional[str] = None, category: Option allow_headers=["*"], ) +# In-memory storage for restocking orders +restocking_orders: List[dict] = [] +restocking_order_counter = {"value": 1} + # Data models class InventoryItem(BaseModel): id: str @@ -120,6 +125,43 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class RestockingRecommendation(BaseModel): + sku: str + name: str + category: str + warehouse: str + quantity_on_hand: int + reorder_point: int + unit_cost: float + recommended_quantity: int + estimated_cost: float + demand_source: str + stockout_severity: float + +class RestockingOrderItem(BaseModel): + sku: str + name: str + quantity: int + unit_price: float + +class PlaceRestockingOrderRequest(BaseModel): + budget: float + items: List[RestockingOrderItem] + warehouse: Optional[str] = None + category: Optional[str] = None + +class RestockingOrder(BaseModel): + id: str + order_number: str + order_date: str + expected_delivery: str + status: str + items: List[dict] + total_value: float + budget_used: float + warehouse: Optional[str] = None + category: Optional[str] = None + # API endpoints @app.get("/") def root(): @@ -304,6 +346,110 @@ def get_monthly_trends(): result.sort(key=lambda x: x['month']) return result +@app.get("/api/restocking/recommendations", response_model=List[RestockingRecommendation]) +def get_restocking_recommendations( + budget: float = 0, + warehouse: Optional[str] = None, + category: Optional[str] = None +): + """Get restocking recommendations for low-stock items""" + # Apply filters to inventory + filtered_inventory = apply_filters(inventory_items, warehouse, category) + + # Keep only items where quantity_on_hand <= reorder_point + low_stock = [item for item in filtered_inventory if item["quantity_on_hand"] <= item["reorder_point"]] + + # Build forecast lookup + forecast_by_sku = {f["item_sku"]: f for f in demand_forecasts} + + # Build recommendations + recommendations = [] + for item in low_stock: + sku = item["sku"] + + # Determine recommended quantity and demand source + forecast = forecast_by_sku.get(sku) + if forecast and forecast["forecasted_demand"] > forecast["current_demand"]: + recommended_qty = forecast["forecasted_demand"] - forecast["current_demand"] + demand_source = "forecast" + else: + recommended_qty = max(1, item["reorder_point"] - item["quantity_on_hand"]) + demand_source = "reorder_point" + + if recommended_qty == 0: + continue + + estimated_cost = recommended_qty * item["unit_cost"] + stockout_severity = max(0.0, (item["reorder_point"] - item["quantity_on_hand"]) / item["reorder_point"]) if item["reorder_point"] > 0 else 0.0 + + recommendations.append({ + "sku": sku, + "name": item["name"], + "category": item["category"], + "warehouse": item["warehouse"], + "quantity_on_hand": item["quantity_on_hand"], + "reorder_point": item["reorder_point"], + "unit_cost": item["unit_cost"], + "recommended_quantity": recommended_qty, + "estimated_cost": estimated_cost, + "demand_source": demand_source, + "stockout_severity": stockout_severity + }) + + # Sort by stockout_severity (descending), then by estimated_cost (ascending) + recommendations.sort(key=lambda x: (-x["stockout_severity"], x["estimated_cost"])) + + # If budget > 0, apply greedy selection + if budget > 0: + selected = [] + remaining_budget = budget + for rec in recommendations: + if rec["estimated_cost"] <= remaining_budget: + selected.append(rec) + remaining_budget -= rec["estimated_cost"] + return selected + + return recommendations + +@app.post("/api/restocking/orders", response_model=RestockingOrder) +def place_restocking_order(request: PlaceRestockingOrderRequest): + """Place a restocking order""" + if not request.items: + raise HTTPException(status_code=400, detail="Items list cannot be empty") + + # Calculate total value + total_value = sum(item.quantity * item.unit_price for item in request.items) + + # Generate order number + order_number = f"RST-{datetime.now().strftime('%Y%m%d')}-{str(restocking_order_counter['value']).zfill(4)}" + restocking_order_counter['value'] += 1 + + # Set dates + order_date = datetime.now().isoformat() + expected_delivery = (datetime.now() + timedelta(days=14)).isoformat() + + # Create order + order = { + "id": f"restocking_{restocking_order_counter['value']}", + "order_number": order_number, + "order_date": order_date, + "expected_delivery": expected_delivery, + "status": "Processing", + "items": [item.dict() for item in request.items], + "total_value": total_value, + "budget_used": total_value, + "warehouse": request.warehouse, + "category": request.category + } + + restocking_orders.append(order) + return order + +@app.get("/api/restocking/orders", response_model=List[RestockingOrder]) +def get_restocking_orders(): + """Get all restocking orders""" + return restocking_orders + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)