From 291d5517ea15c172786b6b2358d9a8b597a52bcb Mon Sep 17 00:00:00 2001 From: Gorinta Date: Mon, 1 Jun 2026 13:27:46 +0530 Subject: [PATCH] Add Restocking tab with budget slider, demand recommendations, and submitted orders section - New Restocking view: budget slider, greedy recommendations by demand growth %, Place Order button - POST /api/orders backend endpoint with 14-day lead time and Submitted status - Orders tab: new Submitted Orders section showing restocking orders - i18n: added restocking and status.submitted keys in en/ja locales Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 129 +++++---- client/CLAUDE.md | 484 +------------------------------- client/src/App.vue | 8 + client/src/api.js | 5 + client/src/locales/en.js | 24 ++ client/src/locales/ja.js | 24 ++ client/src/main.js | 4 +- client/src/views/Orders.vue | 75 ++++- client/src/views/Restocking.vue | 354 +++++++++++++++++++++++ server/CLAUDE.md | 287 ++----------------- server/main.py | 30 ++ 11 files changed, 621 insertions(+), 803 deletions(-) create mode 100644 client/src/views/Restocking.vue diff --git a/CLAUDE.md b/CLAUDE.md index d2086efa..444aa374 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,74 +1,89 @@ # CLAUDE.md -Factory Inventory Management System Demo with GitHub integration - Full-stack application with Vue 3 frontend, Python FastAPI backend, and in-memory mock data (no database). +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Critical Tool Usage Rules +Factory Inventory Management System — full-stack demo app with Vue 3 frontend, Python FastAPI backend, and in-memory mock data (no database). -### Subagents -Use the Task tool with these specialized subagents for appropriate tasks: +## Commands -- **vue-expert**: Use for Vue 3 frontend features, UI components, styling, and client-side functionality - - Examples: Creating components, fixing reactivity issues, performance optimization, complex state management - - **MANDATORY RULE: ANY time you need to create or significantly modify a .vue file, you MUST delegate to vue-expert** -- **code-reviewer**: Use after writing significant code to review quality and best practices -- **Explore**: Use for understanding codebase structure, searching for patterns, or answering questions about how components work -- **general-purpose**: Use for complex multi-step tasks or when other agents don't fit - -### Skills -- **backend-api-test** skill: Use when writing or modifying tests in `tests/backend` directory with pytest and FastAPI TestClient +**Backend** (from `server/`): +```bash +uv sync # Install dependencies +uv run python main.py # Start server on http://localhost:8001 +``` -### MCP Tools -- **ALWAYS use GitHub MCP tools** (`mcp__github__*`) for ALL GitHub operations - - Exception: Local branches only - use `git checkout -b` instead of `mcp__github__create_branch` -- **ALWAYS use Playwright MCP tools** (`mcp__playwright__*`) for browser testing - - Test against: `http://localhost:3000` (frontend), `http://localhost:8001` (API) +**Frontend** (from `client/`): +```bash +npm install && npm run dev # Start dev server on http://localhost:3000 +npm run build # Production build to client/dist/ +``` -## Stack -- **Frontend**: Vue 3 + Composition API + Vite (port 3000) -- **Backend**: Python FastAPI (port 8001) -- **Data**: JSON files in `server/data/` loaded via `server/mock_data.py` +**Tests** (from `tests/`): +```bash +uv run pytest backend/ -v # All 51 tests +uv run pytest backend/test_inventory.py -v # Single file +uv run pytest --cov=../server --cov-report=html # With coverage +``` -## Quick Start +**Windows note:** `scripts/start.sh` is macOS/Linux only. Start backend and frontend in separate terminals manually. -```bash -# Backend -cd server -uv run python main.py +## Architecture -# Frontend -cd client -npm install && npm run dev ``` +client/src/ + api.js # Centralized Axios client — all HTTP calls go here + composables/ # Shared state: useFilters.js (4-filter system), useAuth.js, useI18n.js + views/ # Page components (Dashboard, Inventory, Orders, Spending, Demand, Reports) + components/ # Reusable UI (FilterBar, *DetailModal, TasksModal, ProfileMenu) + +server/ + main.py # All FastAPI endpoints + apply_filters() + filter_by_month() + mock_data.py # Loads server/data/*.json into memory at startup + data/*.json # Source of truth — inventory, orders, spending, demand_forecasts, backlog_items +``` + +Data flow: Vue filter composable → `api.js` query params → FastAPI → in-memory filtering → Pydantic response → Vue computed properties. + +## Critical Tool Usage + +- **vue-expert subagent**: MANDATORY for any `.vue` file creation or significant modification +- **code-reviewer subagent**: Use after writing significant code +- **backend-api-test skill**: Use when writing/modifying tests in `tests/backend/` +- **GitHub MCP** (`mcp__github__*`): Use for ALL GitHub operations (exception: local branch creation with `git checkout -b`) +- **Playwright MCP** (`mcp__playwright__*`): Use for browser testing against `http://localhost:3000` / `http://localhost:8001` ## Key Patterns -**Filter System**: 4 filters (Time Period, Warehouse, Category, Order Status) apply to all data via query params -**Data Flow**: Vue filters → `client/src/api.js` → FastAPI → In-memory filtering → Pydantic validation → Computed properties -**Reactivity**: Raw data in refs (`allOrders`, `inventoryItems`), derived data in computed properties - -## API Endpoints -- `GET /api/inventory` - Filters: warehouse, category -- `GET /api/orders` - Filters: warehouse, category, status, month -- `GET /api/dashboard/summary` - All filters -- `GET /api/demand`, `/api/backlog` - No filters -- `GET /api/spending/*` - Summary, monthly, categories, transactions - -## Common Issues -1. Use unique keys in v-for (not `index`) - use `sku`, `month`, etc. -2. Validate dates before `.getMonth()` calls -3. Update Pydantic models when changing JSON data structure -4. Inventory filters don't support month (no time dimension) -5. Revenue goals: $800K/month single, $9.6M YTD all months - -## File Locations -- Views: `client/src/views/*.vue` -- API Client: `client/src/api.js` -- Backend: `server/main.py`, `server/mock_data.py` -- Data: `server/data/*.json` -- Styles: `client/src/App.vue` +**Filter system**: 4 filters — Time Period, Warehouse, Category, Order Status — passed as query params to every endpoint. Filter values of `'all'` are skipped. Inventory endpoints do **not** support month filtering (no time dimension on inventory data). + +**Backend filtering**: +```python +# Never mutate global data — filter on copies +if warehouse and warehouse != 'all': + results = [r for r in results if r.get('warehouse') == warehouse] +``` + +**Date/quarter filtering**: Supports `2025-01` (month) and `Q1-2025` (quarter) formats. Parse safely and handle null dates. + +**Frontend reactivity**: +- Raw data in `ref()`, derived data in `computed()` — never the reverse +- Always use unique IDs (not array index) as `v-for` keys +- Validate dates before calling `.getMonth()`: `if (!isNaN(date.getTime()))` +- In ` - - -``` - -**Why Composition API:** -- Better code organization by feature -- Easier to extract and reuse logic -- TypeScript support -- Smaller bundle size -- More flexible than Options API - -### Reactive Data Best Practices - -**refs vs computed:** -- Use `ref()` for values that change via assignment -- Use `computed()` for values derived from other reactive data -- computed properties are cached until dependencies change -- Never mutate computed properties - -**Example:** -```javascript -// refs - mutable state -const searchQuery = ref('') -const items = ref([]) - -// computed - derived from refs -const filteredItems = computed(() => { - if (!searchQuery.value) return items.value - return items.value.filter(item => - item.name.toLowerCase().includes(searchQuery.value.toLowerCase()) - ) -}) -``` - -**Accessing ref values:** -- In ` + + diff --git a/server/CLAUDE.md b/server/CLAUDE.md index bca8f787..2037f679 100644 --- a/server/CLAUDE.md +++ b/server/CLAUDE.md @@ -2,285 +2,30 @@ This file provides guidance to Claude Code (claude.ai/code) when working with the FastAPI backend. -## Running the Server +## Commands ```bash -# From server directory -uv run python main.py -# Server runs on http://localhost:8001 -# API docs at http://localhost:8001/docs +uv sync # Install dependencies +uv run python main.py # Start on http://localhost:8001 (API docs at /docs) ``` -## Development Best Practices +## Architecture -### API Design Principles +- `main.py` — all FastAPI endpoints plus `apply_filters()` and `filter_by_month()` helpers. Everything lives here (no separate modules). +- `mock_data.py` — loads `data/*.json` into module-level globals at startup. Data is in-memory; restart to reload. +- `data/*.json` — source of truth: `inventory.json`, `orders.json`, `spending.json`, `demand_forecasts.json`, `backlog_items.json`, `purchase_orders.json`, `transactions.json`. -**RESTful Design:** -- Use appropriate HTTP methods (GET for retrieval, POST for creation, etc.) -- Return proper status codes (200, 201, 404, 400, 500) -- Use plural nouns for resource endpoints (`/api/orders`, not `/api/order`) -- Keep URLs simple and predictable +## Key Patterns -**Request/Response:** -- Always validate input with Pydantic models -- Return consistent response structure -- Include error details in error responses -- Use ISO 8601 for dates (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS) +**Never mutate globals**: Always filter on a copy — `results = [r for r in all_items if ...]`. -### Adding New Endpoints +**Filter params**: All endpoints accept `warehouse`, `category`, `status`, `month` as optional query params. Skip filter when value is `'all'` or `None`. Inventory endpoints do **not** support `month` (no time dimension on inventory data). -**Process:** -1. Define Pydantic model for data validation -2. Create endpoint function with clear name -3. Add route decorator with explicit path -4. Implement business logic -5. Handle errors appropriately -6. Write tests in `tests/backend/` +**Date/quarter formats**: `apply_filters` handles both `2025-01` (month) and `Q1-2025` (quarter). Parse safely and skip records with null dates. -**Example Pattern:** -```python -class MyModel(BaseModel): - id: str - name: str - value: float +**Adding an endpoint**: +1. Define Pydantic response model +2. Add `@app.get` route, filter on copy of data, return typed response +3. Write tests in `tests/backend/` -@app.get("/api/resource", response_model=List[MyModel]) -def get_resources( - filter_param: Optional[str] = None, - category: Optional[str] = None -): - """Get resources with optional filtering.""" - results = all_resources - - if filter_param and filter_param != 'all': - results = [r for r in results if r['field'] == filter_param] - - if category and category != 'all': - results = [r for r in results if r['category'].lower() == category.lower()] - - return results -``` - -### Data Model Best Practices - -**Pydantic Models:** -- Define once, use everywhere -- Make optional fields explicitly `Optional[Type]` -- Use descriptive field names -- Add default values where appropriate -- Keep models close to their usage - -**Model Updates:** -- When adding fields to JSON data, update Pydantic models -- When removing fields, mark as Optional first, then remove -- Consider backwards compatibility -- Update tests when models change - -### Filtering Best Practices - -**Standard Pattern:** -- Accept filter parameters as optional query params -- Check for 'all' value and skip that filter -- Use lowercase comparison for case-insensitive matching -- Apply filters sequentially for code clarity -- Don't mutate original data - filter on copies - -**Filter Implementation:** -```python -def filter_data(data, warehouse=None, category=None): - """Filter data by multiple criteria.""" - filtered = data - - if warehouse and warehouse != 'all': - filtered = [item for item in filtered - if item.get('warehouse') == warehouse] - - if category and category != 'all': - filtered = [item for item in filtered - if item.get('category', '').lower() == category.lower()] - - return filtered -``` - -**Date/Time Filtering:** -- Support both direct month match (2025-01) and quarters (Q1-2025) -- Parse date strings safely -- Handle missing/null dates gracefully -- Consider timezone if adding real database - -### Error Handling - -**Use HTTPException:** -```python -from fastapi import HTTPException - -@app.get("/api/item/{item_id}") -def get_item(item_id: str): - item = find_item(item_id) - if not item: - raise HTTPException( - status_code=404, - detail=f"Item {item_id} not found" - ) - return item -``` - -**Best Practices:** -- Return 404 for "not found" errors -- Return 400 for bad input/validation errors -- Return 500 for server errors (let FastAPI handle these) -- Include helpful error messages -- Log errors for debugging - -### Mock Data Management - -**Pattern:** -- Load all data from JSON files at startup -- Data lives in memory during server runtime -- Changes don't persist (restart reloads from files) -- Keep JSON files well-formatted and validated - -**Adding New Data:** -1. Update JSON file in `server/data/` -2. Update Pydantic model if structure changed -3. Restart server to reload data -4. Verify with API docs (/docs endpoint) - -**Data Consistency:** -- Ensure SKUs in orders reference valid inventory items -- Keep category names consistent across data files -- Use same date format everywhere -- Validate JSON structure before committing - -### CORS Configuration - -**Development:** -- Allow all origins during development (`allow_origins=["*"]`) -- Useful for frontend dev server on different port - -**Production:** -- Restrict to specific origins only -- Example: `allow_origins=["https://yourdomain.com"]` -- Never use wildcard (*) in production -- Configure based on deployment environment - -### Testing API Endpoints - -**Using FastAPI Docs:** -1. Start server -2. Navigate to http://localhost:8001/docs -3. Click endpoint to expand -4. Click "Try it out" -5. Fill in parameters -6. Execute and verify response - -**Using pytest:** -```python -def test_endpoint(client): - response = client.get("/api/endpoint?param=value") - assert response.status_code == 200 - data = response.json() - assert isinstance(data, list) - assert len(data) > 0 -``` - -**What to Test:** -- Successful requests return 200 -- Invalid IDs return 404 -- Filters work correctly -- Response structure matches model -- Calculations are accurate -- Edge cases (empty results, invalid input) - -### Performance Considerations - -**In-Memory Data:** -- Fast reads (no database queries) -- No indexing needed for demo -- All filtering happens in Python -- Reasonable for small datasets (<10K items) - -**If Scaling:** -- Add database (PostgreSQL, MongoDB) -- Implement pagination -- Add caching layer (Redis) -- Use database indexes for common filters -- Consider async database queries - -### Code Organization - -**When to Extract:** -- Filtering logic used in multiple endpoints → Extract to utility function -- Complex business logic → Move to separate module -- Data validation beyond Pydantic → Create custom validators -- Repeated calculations → Extract to helper functions - -**Module Structure for Growth:** -``` -server/ -├── main.py # API endpoints only -├── models.py # Pydantic models -├── services/ # Business logic -│ ├── inventory.py -│ └── orders.py -├── utils/ # Helper functions -│ └── filters.py -└── data/ # JSON data files -``` - -### Common Pitfalls - -**Avoid:** -- ❌ Mutating global data (filter on copies) -- ❌ Missing Pydantic model updates when JSON changes -- ❌ Inconsistent filter parameter names across endpoints -- ❌ Returning raw dict instead of Pydantic model -- ❌ Not handling None/null values in data - -**Do:** -- ✅ Validate all input with Pydantic -- ✅ Return typed responses (response_model) -- ✅ Handle optional parameters gracefully -- ✅ Keep endpoints focused and simple -- ✅ Write tests for new endpoints - -### Debugging - -**Techniques:** -- Use FastAPI's automatic docs for quick testing -- Print statements in endpoint functions (shows in terminal) -- Check Pydantic validation errors in response -- Use Python debugger (`import pdb; pdb.set_trace()`) -- Review JSON data files for structure issues - -**Common Issues:** -- Data not loading → Check JSON file path -- Validation errors → Verify Pydantic model matches data -- Empty results → Check filter logic and data -- 404 errors → Verify route path and HTTP method - -### Security Notes - -**For Production:** -- Add authentication/authorization -- Validate and sanitize all input -- Use HTTPS only -- Implement rate limiting -- Add input size limits -- Use environment variables for sensitive config -- Never commit secrets to git - -**Current State:** -- No authentication (demo only) -- CORS allows all origins -- No rate limiting -- No input validation beyond types -- Suitable for local development only - -## Quick Reference - -**Start server:** `uv run python main.py` -**API docs:** http://localhost:8001/docs -**Run tests:** `cd ../tests && uv run pytest backend/ -v` -**Add endpoint:** Define model → Add route → Write tests -**Add filter:** Add query param → Check 'all' value → Filter data +**Adding data**: Update JSON in `server/data/` → update Pydantic model if structure changed → restart server. diff --git a/server/main.py b/server/main.py index a0c2d8c5..86cb042b 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,12 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class CreateOrderRequest(BaseModel): + customer: str + items: List[dict] + warehouse: Optional[str] = None + category: Optional[str] = None + # API endpoints @app.get("/") def root(): @@ -153,6 +160,29 @@ def get_orders( filtered_orders = filter_by_month(filtered_orders, month) return filtered_orders +@app.post("/api/orders", response_model=Order) +def create_order(request: CreateOrderRequest): + """Create a new restocking order""" + new_id = str(len(orders) + 1) + order_date = datetime.utcnow().isoformat() + expected_delivery = (datetime.utcnow() + timedelta(days=14)).isoformat() + total_value = sum(i.get("quantity", 0) * i.get("unit_price", 0) for i in request.items) + order = { + "id": new_id, + "order_number": f"ORD-RESTOCK-{new_id.zfill(4)}", + "customer": request.customer, + "items": request.items, + "status": "Submitted", + "warehouse": request.warehouse, + "category": request.category, + "order_date": order_date, + "expected_delivery": expected_delivery, + "total_value": round(total_value, 2), + "actual_delivery": None + } + orders.append(order) + return order + @app.get("/api/orders/{order_id}", response_model=Order) def get_order(order_id: str): """Get a specific order"""