diff --git a/CLAUDE.md b/CLAUDE.md index d2086efa..8858b615 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,9 @@ npm install && npm run dev - Data: `server/data/*.json` - Styles: `client/src/App.vue` +## Code Style +- Always document non-obvious logic changes with comments + ## Design System - Colors: Slate/gray (#0f172a, #64748b, #e2e8f0) - Status: green/blue/yellow/red diff --git a/client/src/App.vue b/client/src/App.vue index c2da05a5..72f6b070 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -25,6 +25,9 @@ Reports + + {{ t('nav.restocking') }} + + + @@ -129,6 +172,12 @@ export default { loadOrders() }) + const submittedOrders = computed(() => orders.value.filter(o => o.status === 'Submitted')) + + const formatCurrency = (value) => { + return currencySymbol.value + value.toLocaleString() + } + const getOrdersByStatus = (status) => { return orders.value.filter(order => order.status === status) } @@ -138,7 +187,8 @@ export default { 'Delivered': 'success', 'Shipped': 'info', 'Processing': 'warning', - 'Backordered': 'danger' + 'Backordered': 'danger', + 'Submitted': 'stable' } return statusMap[status] || 'info' } @@ -160,9 +210,11 @@ export default { loading, error, orders, + submittedOrders, getOrdersByStatus, getOrderStatusClass, formatDate, + formatCurrency, currencySymbol, translateProductName, translateCustomerName @@ -276,4 +328,29 @@ export default { font-size: 0.813rem; color: #64748b; } + +.submitted-orders-card { + margin-top: 1.5rem; +} + +.submitted-count { + font-size: 0.875rem; + color: #64748b; +} + +.lead-time-badge { + background: #e0e7ff; + color: #3730a3; + padding: 0.25rem 0.625rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 600; +} + +.order-number { + font-family: monospace; + font-size: 0.875rem; + color: #0f172a; + font-weight: 600; +} diff --git a/client/src/views/Restocking.vue b/client/src/views/Restocking.vue new file mode 100644 index 00000000..9ed8bcd4 --- /dev/null +++ b/client/src/views/Restocking.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/docs/architecture.html b/docs/architecture.html new file mode 100644 index 00000000..d9a943bb --- /dev/null +++ b/docs/architecture.html @@ -0,0 +1,629 @@ + + + + + + Architecture — Factory Inventory Management + + + + +
+

Factory Inventory Management — System Architecture

+

Full-stack demo  ·  Vue 3 + FastAPI + In-memory JSON  ·  3 warehouses  ·  7 data domains

+
+ +
+ + +
+

Tech Stack

+
+
+
Frontend  ·  Port 3000
+ Vue 3 + Composition API + Vite + Axios +
    +
  • 7 page views, 9 reusable components
  • +
  • 3 composables for shared logic
  • +
  • Custom SVG charts (no chart library)
  • +
  • EN / JA internationalization
  • +
  • Singleton filter state (useFilters)
  • +
+
+ +
+
Backend  ·  Port 8001
+ Python + FastAPI + Pydantic v2 + uv +
    +
  • 17 REST endpoints across 6 resource groups
  • +
  • 8 Pydantic response models
  • +
  • CORS open for local development
  • +
  • Auto-generated OpenAPI docs at /docs
  • +
  • Pure filter functions (no mutations)
  • +
+
+ +
+
Data Layer  ·  In-memory
+ JSON files + In-memory + No DB +
    +
  • 7 JSON files loaded at startup
  • +
  • Data resets on server restart
  • +
  • Warehouses: San Francisco, London, Tokyo
  • +
  • 5 product categories
  • +
  • Months: Jan–Dec 2025
  • +
+
+
+
+ + +
+

Request Data Flow

+
+
+ 01 +
User Interaction
+
User changes a filter dropdown or navigates to a view in the browser.
+
+
+
+ 02 +
useFilters Composable
+
Singleton reactive refs update. Any watching component is notified.
+
+
+
+ 03 +
View loadData()
+
watch() triggers loadData(). getCurrentFilters() maps UI state to API params.
+
+
+
+ 04 +
api.js (Axios)
+
Builds URLSearchParams and sends GET to http://localhost:8001/api/…
+
+
+
+ 05 +
FastAPI Endpoint
+
apply_filters() and filter_by_month() run on in-memory lists.
+
+
+
+ 06 +
Pydantic Validation
+
Response serialized through Pydantic model; JSON returned.
+
+
+
+ 07 +
Template Re-render
+
Reactive ref updated; Vue re-renders only affected DOM nodes.
+
+
+
+ + +
+

Frontend Component Structure

+
+
+

Views (Pages)

+
Dashboard.vue  — KPI cards, order health chart, low stock
+
Inventory.vue  — Stock table, search, item modal
+
Orders.vue     — Order table, status breakdown
+
Spending.vue   — Cost analytics, monthly chart
+
Reports.vue    — Quarterly & monthly trends
+
Demand.vue     — Forecast table per SKU
+
Backlog.vue    — Delayed orders, PO creation
+
+ +
+

Components & Composables

+
FilterBar.vue        — 4 dropdowns + reset (sticky)
+
InventoryDetailModal  — SKU detail overlay
+
BacklogDetailModal    — Backlog item detail
+
TasksModal           — Task management
+
ProfileMenu          — User menu + language toggle
+
useFilters.js         — Singleton filter state
+
useI18n.js            — EN/JA translations + currency
+
useAuth.js            — Mock current user & tasks
+
+
+
+ + +
+

Filter System

+
+
+ selectedPeriod + + month=2025-01 +    + selectedLocation + + warehouse=Tokyo +    + selectedCategory + + category=Sensors +    + selectedStatus + + status=Delivered +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ViewWarehouseCategoryStatusMonth / PeriodNotes
DashboardAll 4 filters applied to summary stats
InventoryLocal text search applied on top
OrdersFull filter support
SpendingStatic aggregated data
ReportsPre-aggregated quarterly/monthly
DemandNo filtering
BacklogNo filtering; PO creation available
+
+
+ + +
+

API Endpoints

+
+
+ GET +
+
/api/dashboard/summary
+
KPI totals: inventory value, low stock, pending orders
+
warehousecategorystatusmonth
+
+
+
+ GET +
+
/api/inventory
+
Full inventory list with stock levels
+
warehousecategory
+
+
+
+ GET +
+
/api/inventory/{item_id}
+
Single inventory item by ID
+
+
+
+ GET +
+
/api/orders
+
Order list with item details
+
warehousecategorystatusmonth
+
+
+
+ GET +
+
/api/demand
+
Demand forecasts per SKU with trend direction
+
+
+
+ GET +
+
/api/backlog
+
Delayed orders with days delayed & priority
+
+
+
+ GET +
+
/api/spending/summary
+
Procurement, operational, labor, overhead totals
+
+
+
+ GET +
+
/api/spending/monthly
+
Monthly cost breakdown by category
+
+
+
+ GET +
+
/api/spending/categories
+
Spending share per category with percentages
+
+
+
+ GET +
+
/api/spending/transactions
+
Individual transaction ledger
+
+
+
+ GET +
+
/api/reports/quarterly
+
Q1–Q4 aggregated KPIs: revenue, fulfillment rate
+
+
+
+ GET +
+
/api/reports/monthly-trends
+
Month-over-month order count & revenue
+
+
+
+ GET +
+
/api/tasks
+
User task list
+
+
+
+ POST +
+
/api/tasks
+
Create a new task
+
+
+
+ POST +
+
/api/purchase-orders
+
Create purchase order for a backlog item
+
+
+
+ GET +
+
/api/purchase-orders
+
List all purchase orders
+
+
+
+
+ + +
+

Core Data Models (Pydantic)

+
+
+

InventoryItem

+
idstr
+
skustr
+
namestr
+
categorystr
+
warehousestr
+
quantity_on_handint
+
reorder_pointint
+
unit_costfloat
+
locationstr
+
last_updatedstr
+
+ +
+

Order

+
idstr
+
order_numberstr
+
customerstr
+
itemsList[dict]
+
statusstr
+
order_datestr
+
expected_deliverystr
+
total_valuefloat
+
actual_deliverystropt
+
warehousestropt
+
+ +
+

BacklogItem

+
idstr
+
order_idstr
+
item_skustr
+
item_namestr
+
quantity_neededint
+
quantity_availableint
+
days_delayedint
+
prioritystr
+
has_purchase_orderboolopt
+
+ +
+

DemandForecast

+
idstr
+
item_skustr
+
item_namestr
+
current_demandint
+
forecasted_demandint
+
trendstr
+
periodstr
+
+ +
+

PurchaseOrder

+
idstr
+
backlog_item_idstr
+
supplier_namestr
+
quantityint
+
unit_costfloat
+
expected_delivery_datestr
+
statusstr
+
created_datestr
+
notesstropt
+
+ +
+

DashboardSummary

+
total_inventory_valuefloat
+
low_stock_itemsint
+
pending_ordersint
+
total_backlog_itemsint
+
total_orders_valuefloat
+
+ Pending = Processing + Backordered
+ Low stock = qty ≤ reorder_point +
+
+
+
+ +
+ + + diff --git a/server/data/demand_forecasts.json b/server/data/demand_forecasts.json index e1b38838..cd04e0a1 100644 --- a/server/data/demand_forecasts.json +++ b/server/data/demand_forecasts.json @@ -1,8 +1,8 @@ [ { "id": "1", - "item_sku": "WDG-001", - "item_name": "Industrial Widget Type A", + "item_sku": "PCB-001", + "item_name": "Single Layer PCB Assembly", "current_demand": 300, "forecasted_demand": 450, "trend": "increasing", @@ -10,26 +10,26 @@ }, { "id": "2", - "item_sku": "BRG-102", - "item_name": "Steel Bearing Assembly", + "item_sku": "TMP-201", + "item_name": "Temperature Sensor Module", "current_demand": 150, - "forecasted_demand": 152, - "trend": "stable", + "forecasted_demand": 240, + "trend": "increasing", "period": "Next 30 days" }, { "id": "3", - "item_sku": "GSK-203", - "item_name": "High-Temperature Gasket", + "item_sku": "HMD-202", + "item_name": "Humidity Sensor Module", "current_demand": 500, - "forecasted_demand": 600, - "trend": "increasing", + "forecasted_demand": 510, + "trend": "stable", "period": "Next 30 days" }, { "id": "4", - "item_sku": "MTR-304", - "item_name": "Electric Motor 5HP", + "item_sku": "SRV-301", + "item_name": "Micro Servo Motor", "current_demand": 50, "forecasted_demand": 35, "trend": "decreasing", @@ -37,35 +37,35 @@ }, { "id": "5", - "item_sku": "FLT-405", - "item_name": "Oil Filter Cartridge", + "item_sku": "MCU-401", + "item_name": "32-bit ARM Microcontroller", "current_demand": 800, - "forecasted_demand": 950, + "forecasted_demand": 1100, "trend": "increasing", "period": "Next 30 days" }, { "id": "6", - "item_sku": "VLV-506", - "item_name": "Pressure Relief Valve", + "item_sku": "PSU-501", + "item_name": "5V 10A Switching Power Supply", "current_demand": 120, - "forecasted_demand": 121, + "forecasted_demand": 122, "trend": "stable", "period": "Next 30 days" }, { "id": "7", - "item_sku": "PSU-501", - "item_name": "5V 10A Switching Power Supply", + "item_sku": "PCB-003", + "item_name": "Multi Layer PCB Assembly", "current_demand": 250, - "forecasted_demand": 252, - "trend": "stable", + "forecasted_demand": 400, + "trend": "increasing", "period": "Next 30 days" }, { "id": "8", - "item_sku": "SNR-420", - "item_name": "Temperature Sensor Module", + "item_sku": "PRX-204", + "item_name": "Proximity Sensor", "current_demand": 180, "forecasted_demand": 182, "trend": "stable", @@ -73,11 +73,11 @@ }, { "id": "9", - "item_sku": "CTL-330", - "item_name": "Logic Controller Board", + "item_sku": "MCU-402", + "item_name": "8-bit AVR Microcontroller", "current_demand": 95, - "forecasted_demand": 96, - "trend": "stable", + "forecasted_demand": 65, + "trend": "decreasing", "period": "Next 30 days" } ] diff --git a/server/main.py b/server/main.py index a0c2d8c5..341cbe3b 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") @@ -120,6 +121,17 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class RestockingOrderItem(BaseModel): + sku: str + name: str + quantity: int + unit_cost: float + +class CreateRestockingOrderRequest(BaseModel): + items: List[RestockingOrderItem] + budget: float + warehouse: str = "San Francisco" + # API endpoints @app.get("/") def root(): @@ -304,6 +316,39 @@ def get_monthly_trends(): result.sort(key=lambda x: x['month']) return result +@app.post("/api/restocking-orders", response_model=Order) +def create_restocking_order(request: CreateRestockingOrderRequest): + """Create a restocking order from demand forecast recommendations""" + if not request.items: + raise HTTPException(status_code=400, detail="At least one item is required") + + # Generate order number: RST-YYYY-NNNN (sequence based on existing RST orders) + rst_count = sum(1 for o in orders if o.get("order_number", "").startswith("RST-")) + year = datetime.now().year + order_number = f"RST-{year}-{str(rst_count + 1).zfill(4)}" + + now = datetime.now() + total_value = sum(item.quantity * item.unit_cost for item in request.items) + + new_order = { + "id": str(len(orders) + 1), + "order_number": order_number, + "customer": "Internal Restocking", + "items": [{"sku": i.sku, "name": i.name, "quantity": i.quantity, "unit_price": i.unit_cost} for i in request.items], + "status": "Submitted", + "order_date": now.isoformat(), + "expected_delivery": (now + timedelta(days=14)).isoformat(), + "total_value": round(total_value, 2), + "actual_delivery": None, + "warehouse": request.warehouse, + "category": None, + } + + # Append to in-memory orders list so GET /api/orders immediately returns it + orders.append(new_order) + return new_order + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)