diff --git a/.mcp.json b/.mcp.json index 91163715..f26eb549 100644 --- a/.mcp.json +++ b/.mcp.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/server-github" ], "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GHTOKEN}" } } } diff --git a/CLAUDE.md b/CLAUDE.md index d2086efa..7eb295b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,3 +72,4 @@ npm install && npm run dev - Status: green/blue/yellow/red - Charts: Custom SVG, CSS Grid for layouts - No emojis in UI +- Always document non-obvious logic changes with comments \ No newline at end of file diff --git a/client/src/App.vue b/client/src/App.vue index c2da05a5..71d9913f 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -22,6 +22,9 @@ {{ t('nav.demandForecast') }} + + Restocking + Reports diff --git a/client/src/api.js b/client/src/api.js index 11cb9db7..5797932b 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -102,5 +102,42 @@ export const api = { async getPurchaseOrderByBacklogItem(backlogItemId) { const response = await axios.get(`${API_BASE_URL}/purchase-orders/${backlogItemId}`) return response.data + }, + + async getRestockingRecommendations(budget) { + const response = await axios.get(`${API_BASE_URL}/restocking/recommendations?budget=${budget}`) + return response.data + }, + + async createRestockingOrder(orderData) { + const response = await axios.post(`${API_BASE_URL}/restocking/orders`, orderData) + return response.data + }, + + async getRestockingOrders() { + const response = await axios.get(`${API_BASE_URL}/restocking/orders`) + return response.data + }, + + async getQuarterlyReports(filters = {}) { + const params = new URLSearchParams() + if (filters.warehouse && filters.warehouse !== 'all') params.append('warehouse', filters.warehouse) + if (filters.category && filters.category !== 'all') params.append('category', filters.category) + if (filters.status && filters.status !== 'all') params.append('status', filters.status) + if (filters.month && filters.month !== 'all') params.append('month', filters.month) + + const response = await axios.get(`${API_BASE_URL}/reports/quarterly?${params.toString()}`) + return response.data + }, + + async getMonthlyTrends(filters = {}) { + const params = new URLSearchParams() + if (filters.warehouse && filters.warehouse !== 'all') params.append('warehouse', filters.warehouse) + if (filters.category && filters.category !== 'all') params.append('category', filters.category) + if (filters.status && filters.status !== 'all') params.append('status', filters.status) + if (filters.month && filters.month !== 'all') params.append('month', filters.month) + + const response = await axios.get(`${API_BASE_URL}/reports/monthly-trends?${params.toString()}`) + return response.data } } diff --git a/client/src/locales/en.js b/client/src/locales/en.js index 03a58fe6..25f29bec 100644 --- a/client/src/locales/en.js +++ b/client/src/locales/en.js @@ -188,6 +188,29 @@ export default { } }, + // Reports + reports: { + title: 'Performance Reports', + description: 'View quarterly performance metrics and monthly trends', + quarterlyPerformance: 'Quarterly Performance', + monthlyRevenueTrend: 'Monthly Revenue Trend', + monthOverMonthAnalysis: 'Month-over-Month Analysis', + quarter: 'Quarter', + totalOrders: 'Total Orders', + totalRevenue: 'Total Revenue', + avgOrderValue: 'Avg Order Value', + fulfillmentRate: 'Fulfillment Rate', + month: 'Month', + orders: 'Orders', + revenue: 'Revenue', + change: 'Change', + growthRate: 'Growth Rate', + totalRevenueYTD: 'Total Revenue (YTD)', + avgMonthlyRevenue: 'Avg Monthly Revenue', + totalOrdersYTD: 'Total Orders (YTD)', + bestPerformingQuarter: 'Best Performing Quarter' + }, + // Filters filters: { timePeriod: 'Time Period', diff --git a/client/src/locales/ja.js b/client/src/locales/ja.js index db33223a..a2a17005 100644 --- a/client/src/locales/ja.js +++ b/client/src/locales/ja.js @@ -188,6 +188,29 @@ export default { } }, + // Reports + reports: { + title: 'パフォーマンスレポート', + description: '四半期業績と月別トレンドの表示', + quarterlyPerformance: '四半期業績', + monthlyRevenueTrend: '月別売上トレンド', + monthOverMonthAnalysis: '前月比分析', + quarter: '四半期', + totalOrders: '総注文数', + totalRevenue: '総売上', + avgOrderValue: '平均注文額', + fulfillmentRate: '達成率', + month: '月', + orders: '注文', + revenue: '売上', + change: '変化', + growthRate: '成長率', + totalRevenueYTD: '総売上(年累計)', + avgMonthlyRevenue: '平均月間売上', + totalOrdersYTD: '総注文数(年累計)', + bestPerformingQuarter: '最高業績四半期' + }, + // Filters filters: { timePeriod: '期間', diff --git a/client/src/main.js b/client/src/main.js index 477c2d96..8884eea6 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -7,6 +7,7 @@ import Orders from './views/Orders.vue' import Demand from './views/Demand.vue' import Spending from './views/Spending.vue' import Reports from './views/Reports.vue' +import Restocking from './views/Restocking.vue' const router = createRouter({ history: createWebHistory(), @@ -16,7 +17,8 @@ const router = createRouter({ { path: '/orders', component: Orders }, { path: '/demand', component: Demand }, { path: '/spending', component: Spending }, - { path: '/reports', component: Reports } + { path: '/reports', component: Reports }, + { path: '/restocking', component: Restocking } ] }) diff --git a/client/src/views/Orders.vue b/client/src/views/Orders.vue index 7413f6e6..0dd46434 100644 --- a/client/src/views/Orders.vue +++ b/client/src/views/Orders.vue @@ -8,6 +8,65 @@
{{ t('common.loading') }}
{{ error }}
+ +
+
+

Submitted Orders ({{ restockingOrders.length }})

+

Recently submitted restocking orders with delivery lead times

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{{ t('orders.table.orderNumber') }}Type{{ t('orders.table.items') }}{{ t('orders.table.status') }}{{ t('orders.table.orderDate') }}{{ t('orders.table.expectedDelivery') }}Lead Time{{ t('orders.table.totalValue') }}
+ {{ order.order_number }} + NEW + + {{ order.customer }} + +
+ + {{ t('orders.itemsCount', { count: order.items.length }) }} + +
+
+ {{ translateProductName(item.name) }} + {{ t('orders.quantity') }}: {{ item.quantity }} @ {{ currencySymbol }}{{ item.unit_cost }} +
+
+
+
+ + {{ t(`status.${order.status.toLowerCase()}`) }} + + {{ formatDate(order.order_date) }}{{ formatDate(order.expected_delivery) }} + {{ calculateLeadTime(order.order_date, order.expected_delivery) }} days + {{ currencySymbol }}{{ order.total_value.toLocaleString() }}
+
+
+
{{ t('status.delivered') }}
@@ -95,6 +154,7 @@ export default { const loading = ref(true) const error = ref(null) const orders = ref([]) + const restockingOrders = ref([]) // Use shared filters const { @@ -124,6 +184,20 @@ export default { } } + const loadRestockingOrders = async () => { + try { + const fetchedRestockingOrders = await api.getRestockingOrders() + // Sort by order date (most recent first) + restockingOrders.value = fetchedRestockingOrders.sort((a, b) => { + const dateA = new Date(a.order_date) + const dateB = new Date(b.order_date) + return dateB - dateA + }) + } catch (err) { + console.error('Failed to load restocking orders:', err) + } + } + // Watch for filter changes and reload data watch([selectedPeriod, selectedLocation, selectedCategory, selectedStatus], () => { loadOrders() @@ -153,16 +227,30 @@ export default { }) } - onMounted(loadOrders) + // Calculate lead time in days between two dates + const calculateLeadTime = (orderDate, expectedDelivery) => { + const startDate = new Date(orderDate) + const endDate = new Date(expectedDelivery) + const diffTime = Math.abs(endDate - startDate) + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + return diffDays + } + + onMounted(() => { + loadOrders() + loadRestockingOrders() + }) return { t, loading, error, orders, + restockingOrders, getOrdersByStatus, getOrderStatusClass, formatDate, + calculateLeadTime, currencySymbol, translateProductName, translateCustomerName @@ -172,6 +260,48 @@ export default { diff --git a/docs/architecture.html b/docs/architecture.html new file mode 100644 index 00000000..5de1d9f3 --- /dev/null +++ b/docs/architecture.html @@ -0,0 +1,908 @@ + + + + + + Factory Inventory Management System - Architecture + + + +
+
+

Factory Inventory Management System

+

Architecture Documentation - Full-Stack Demo Application

+
+ +
+ +
+

System Overview

+

A modern full-stack web application demonstrating factory inventory management with real-time data filtering, demand forecasting, and comprehensive reporting capabilities. Built with Vue 3 frontend and Python FastAPI backend using in-memory JSON data for demonstration purposes.

+ +
+ Key Characteristics: Single-page application (SPA) with RESTful API, reactive UI, universal filtering system, and mock data simulation for rapid prototyping. +
+
+ + +
+

Technology Stack

+
+
+

Frontend (Port 3000)

+
+ Vue 3 + Composition API + Vue Router + Axios + Vite + JavaScript +
+
+
+

Backend (Port 8001)

+
+ Python 3.13 + FastAPI + Pydantic + Uvicorn + pytest + CORS Middleware +
+
+
+
+ + +
+

Data Flow Architecture

+
+
+
+
JSON Files
+

server/data/*.json

+
+ +
+
Data Loading
+

mock_data.py

+
+ +
+
FastAPI Backend
+

main.py endpoints

+
+ +
+
Filtering & Validation
+

Pydantic models

+
+ +
+
API Response
+

JSON data

+
+ +
+
Vue Frontend
+

api.js client

+
+ +
+
Reactive UI
+

Components/Views

+
+
+
+
+ + +
+

Backend API Endpoints

+

RESTful API with optional query parameter filtering. All endpoints return JSON and support CORS for cross-origin requests.

+ +
+
+ GET /api/inventory +

Retrieve all inventory items with optional filtering

+ Filters: warehouse, category +
+ +
+ GET /api/inventory/{item_id} +

Get specific inventory item by ID

+
+ +
+ GET /api/orders +

Retrieve all orders with optional filtering

+ Filters: warehouse, category, status, month +
+ +
+ GET /api/orders/{order_id} +

Get specific order by ID

+
+ +
+ GET /api/demand +

Get demand forecasts for inventory planning

+ No filters +
+ +
+ GET /api/backlog +

Get backlog items with purchase order status

+ No filters +
+ +
+ GET /api/dashboard/summary +

Get aggregated dashboard statistics

+ Filters: warehouse, category, status, month +
+ +
+ GET /api/spending/summary +

Get spending summary statistics

+
+ +
+ GET /api/spending/monthly +

Get monthly spending breakdown

+
+ +
+ GET /api/spending/categories +

Get spending by category

+
+ +
+ GET /api/spending/transactions +

Get recent transaction history

+
+ +
+ GET /api/reports/quarterly +

Get quarterly performance reports

+
+ +
+ GET /api/reports/monthly-trends +

Get month-over-month trend analysis

+
+
+
+ + +
+

Data Models (Pydantic)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModelPurposeKey Fields
InventoryItemInventory stock detailssku, name, category, warehouse, quantity_on_hand, reorder_point, unit_cost
OrderCustomer order informationorder_number, customer, items, status, order_date, total_value, warehouse
DemandForecastDemand predictionsitem_sku, current_demand, forecasted_demand, trend, period
BacklogItemDelayed order itemsorder_id, item_sku, quantity_needed, days_delayed, priority, has_purchase_order
PurchaseOrderSupplier ordersbacklog_item_id, supplier_name, quantity, unit_cost, expected_delivery_date, status
+
+ + +
+

Data Storage (JSON Files)

+

All data is stored in JSON format in server/data/ directory and loaded into memory at startup.

+ +
+
+ inventory.json +

9.6 KB - Stock items

+
+
+ orders.json +

151 KB - Order records

+
+
+ demand_forecasts.json +

1.8 KB - Forecasts

+
+
+ backlog_items.json +

929 B - Delayed items

+
+
+ purchase_orders.json +

3 B - PO records

+
+
+ spending.json +

2.5 KB - Spend data

+
+
+ transactions.json +

13.7 KB - Transactions

+
+
+
+ + +
+

Frontend Architecture

+ +

Application Views

+
+
+

Dashboard

+
    +
  • KPI summary cards
  • +
  • Inventory value tracking
  • +
  • Low stock alerts
  • +
  • Pending orders count
  • +
+
+
+

Inventory

+
    +
  • Stock level monitoring
  • +
  • Reorder point tracking
  • +
  • Warehouse filtering
  • +
  • Category grouping
  • +
+
+
+

Orders

+
    +
  • Order status tracking
  • +
  • Delivery date management
  • +
  • Status filtering
  • +
  • Time-based filtering
  • +
+
+
+

Demand

+
    +
  • Forecast visualization
  • +
  • Trend analysis
  • +
  • Backlog management
  • +
  • Purchase order creation
  • +
+
+
+

Spending

+
    +
  • Expense tracking
  • +
  • Monthly breakdowns
  • +
  • Category analysis
  • +
  • Transaction history
  • +
+
+
+

Reports

+
    +
  • Quarterly summaries
  • +
  • Monthly trends
  • +
  • Revenue tracking
  • +
  • Fulfillment rates
  • +
+
+
+ +

Reusable Components

+
+
+

FilterBar

+
    +
  • Global filter controls
  • +
  • Warehouse selector
  • +
  • Category selector
  • +
  • Time period selector
  • +
+
+
+

Modal Components

+
    +
  • BacklogDetailModal
  • +
  • InventoryDetailModal
  • +
  • ProductDetailModal
  • +
  • CostDetailModal
  • +
+
+
+

Utility Components

+
    +
  • ProfileMenu
  • +
  • LanguageSwitcher
  • +
  • TasksModal
  • +
  • ProfileDetailsModal
  • +
+
+
+
+ + +
+

Universal Filter System

+

Four-dimensional filtering system that applies consistently across all views:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filter TypeOptionsApplies To
Time PeriodAll Months, Q1-2025, Q2-2025, Q3-2025, Q4-2025, Individual monthsOrders, Dashboard, Reports
WarehouseAll Warehouses, Main, North, South, East, WestInventory, Orders, Dashboard
CategoryAll Categories, Raw Materials, Components, Finished Goods, PackagingInventory, Orders, Dashboard
Order StatusAll Statuses, Processing, Shipped, Delivered, BackorderedOrders, Dashboard
+
+ + +
+

Key Features

+
+
+

Real-Time Filtering

+

Dynamic data filtering across all views

+
+
+

Reactive UI

+

Vue 3 Composition API with computed properties

+
+
+

RESTful API

+

Clean, predictable endpoint design

+
+
+

Data Validation

+

Pydantic models ensure type safety

+
+
+

SPA Navigation

+

Client-side routing with Vue Router

+
+
+

Mock Data

+

JSON-based in-memory data for rapid prototyping

+
+
+
+ + +
+

Design Patterns & Best Practices

+
+
+

Frontend Patterns

+
    +
  • Composition API for logic reuse
  • +
  • Reactive refs and computed
  • +
  • Centralized API client (api.js)
  • +
  • Composables for shared state
  • +
  • Component-based architecture
  • +
+
+
+

Backend Patterns

+
    +
  • RESTful resource naming
  • +
  • Query parameter filtering
  • +
  • Pydantic validation
  • +
  • Helper functions for filtering
  • +
  • CORS middleware for cross-origin
  • +
+
+
+

Data Flow

+
    +
  • Props down, events up
  • +
  • Computed for derived data
  • +
  • API client abstraction
  • +
  • Loading states
  • +
  • Error handling
  • +
+
+
+

Code Organization

+
    +
  • Clear separation of concerns
  • +
  • Views for pages
  • +
  • Components for reusable UI
  • +
  • Modular backend endpoints
  • +
  • Scoped component styles
  • +
+
+
+
+ + +
+

Project Structure

+
+
+

Frontend (client/)

+
    +
  • src/views/ - Page components
  • +
  • src/components/ - Reusable UI
  • +
  • src/api.js - API client
  • +
  • src/main.js - App entry
  • +
  • src/App.vue - Root component
  • +
+
+
+

Backend (server/)

+
    +
  • main.py - API endpoints
  • +
  • mock_data.py - Data loader
  • +
  • data/*.json - Mock data
  • +
  • pyproject.toml - Dependencies
  • +
+
+
+

Testing (tests/)

+
    +
  • backend/ - API tests
  • +
  • frontend/ - Component tests
  • +
  • pytest configuration
  • +
  • Test fixtures
  • +
+
+
+

Documentation

+
    +
  • README.md - Project overview
  • +
  • CLAUDE.md - AI assistant guide
  • +
  • docs/ - Architecture docs
  • +
  • API docs at /docs endpoint
  • +
+
+
+
+ + +
+

Development Workflow

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TaskCommandNotes
Start Backendcd server && uv run python main.pyRuns on http://localhost:8001
Start Frontendcd client && npm run devRuns on http://localhost:3000
View API DocsNavigate to http://localhost:8001/docsInteractive Swagger UI
Run Backend Testscd tests && pytest backend/ -vFastAPI TestClient
Build Frontendcd client && npm run buildProduction build to dist/
+
+ + +
+

Potential Enhancements

+
+
+

Database Integration

+
    +
  • PostgreSQL or MongoDB
  • +
  • SQLAlchemy ORM
  • +
  • Data persistence
  • +
  • Database migrations
  • +
+
+
+

Authentication

+
    +
  • JWT token auth
  • +
  • User roles and permissions
  • +
  • OAuth integration
  • +
  • Session management
  • +
+
+
+

Real-Time Features

+
    +
  • WebSocket connections
  • +
  • Live inventory updates
  • +
  • Push notifications
  • +
  • Collaborative editing
  • +
+
+
+

Advanced Analytics

+
    +
  • Predictive analytics
  • +
  • Machine learning models
  • +
  • Advanced visualizations
  • +
  • Export to PDF/Excel
  • +
+
+
+
+
+
+ + diff --git a/server/main.py b/server/main.py index a0c2d8c5..9ec59cac 100644 --- a/server/main.py +++ b/server/main.py @@ -3,6 +3,8 @@ from typing import List, Optional from pydantic import BaseModel from mock_data import inventory_items, orders, demand_forecasts, backlog_items, spending_summary, monthly_spending, category_spending, recent_transactions, purchase_orders +from datetime import datetime, timedelta +import uuid app = FastAPI(title="Factory Inventory Management System") @@ -120,6 +122,17 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class RestockingOrderItem(BaseModel): + item_sku: str + item_name: str + quantity: int + unit_cost: float + +class CreateRestockingOrderRequest(BaseModel): + items: List[RestockingOrderItem] + total_cost: float + warehouse: str + # API endpoints @app.get("/") def root(): @@ -304,6 +317,114 @@ def get_monthly_trends(): result.sort(key=lambda x: x['month']) return result +@app.get("/api/restocking/recommendations") +def get_restocking_recommendations(budget: float): + """ + Get restocking recommendations based on budget. + Prioritizes items by highest forecasted demand. + """ + # Get demand forecasts sorted by forecasted demand (highest first) + sorted_forecasts = sorted( + demand_forecasts, + key=lambda x: x.get('forecasted_demand', 0), + reverse=True + ) + + recommendations = [] + remaining_budget = budget + + for forecast in sorted_forecasts: + item_sku = forecast.get('item_sku') + # Find corresponding inventory item to get unit cost + inventory_item = next( + (item for item in inventory_items if item['sku'] == item_sku), + None + ) + + if not inventory_item: + continue + + quantity = forecast.get('forecasted_demand', 0) + unit_cost = inventory_item.get('unit_cost', 0) + total_cost = quantity * unit_cost + + # Only include if within budget + if total_cost <= remaining_budget: + recommendations.append({ + 'item_sku': item_sku, + 'item_name': forecast.get('item_name'), + 'quantity': quantity, + 'unit_cost': unit_cost, + 'total_cost': round(total_cost, 2), + 'current_demand': forecast.get('current_demand'), + 'forecasted_demand': forecast.get('forecasted_demand'), + 'trend': forecast.get('trend'), + 'warehouse': inventory_item.get('warehouse'), + 'category': inventory_item.get('category') + }) + remaining_budget -= total_cost + + return { + 'recommendations': recommendations, + 'total_cost': round(budget - remaining_budget, 2), + 'remaining_budget': round(remaining_budget, 2) + } + +@app.post("/api/restocking/orders") +def create_restocking_order(order_request: CreateRestockingOrderRequest): + """ + Create a new restocking order. + Returns the created order with 14-day delivery lead time. + """ + order_id = str(uuid.uuid4()) + order_number = f"RST-{datetime.now().strftime('%Y%m%d')}-{len(orders) + 1:04d}" + order_date = datetime.now().strftime('%Y-%m-%d') + # Fixed 14-day delivery lead time + expected_delivery = (datetime.now() + timedelta(days=14)).strftime('%Y-%m-%d') + + # Create order items list + items = [ + { + 'sku': item.item_sku, + 'name': item.item_name, + 'quantity': item.quantity, + 'unit_cost': item.unit_cost + } + for item in order_request.items + ] + + new_order = { + 'id': order_id, + 'order_number': order_number, + 'customer': 'Internal Restocking', + 'items': items, + 'status': 'Processing', + 'order_date': order_date, + 'expected_delivery': expected_delivery, + 'total_value': order_request.total_cost, + 'warehouse': order_request.warehouse, + 'category': 'Restocking', + 'order_type': 'restocking' # Special flag to identify restocking orders + } + + # Add to orders list (in-memory, will reset on server restart) + orders.append(new_order) + + return { + 'success': True, + 'order': new_order, + 'lead_time_days': 14 + } + +@app.get("/api/restocking/orders") +def get_restocking_orders(): + """Get all restocking orders""" + restocking_orders = [ + order for order in orders + if order.get('order_type') == 'restocking' + ] + return restocking_orders + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)