From fb72b0f0f99b7d4687baf6ccbf201493600d1d4a Mon Sep 17 00:00:00 2001 From: Mohit Mittal Date: Mon, 27 Jul 2026 10:21:02 -0700 Subject: [PATCH 1/3] Add code style guideline for documenting non-obvious logic changes Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 89c307d15..c7ebc5549 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,6 +55,9 @@ npm install && npm run dev - `GET /api/demand`, `/api/backlog` - No filters - `GET /api/spending/*` - Summary, monthly, categories, transactions +## Code Style +- Always document non-obvious logic changes with comments + ## Common Issues 1. Use unique keys in v-for (not `index`) - use `sku`, `month`, etc. 2. Validate dates before `.getMonth()` calls From 617400277bea00f4c90cec68e8652755a1b022ad Mon Sep 17 00:00:00 2001 From: Mohit Mittal Date: Mon, 27 Jul 2026 10:43:33 -0700 Subject: [PATCH 2/3] Add Restocking tab with budget-driven auto-reorder recommendations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New POST /api/restock-orders backend endpoint (FastAPI + Pydantic) that generates RST-YYYY-NNNN order numbers with 14-day lead time and appends to the in-memory orders list - New Restocking.vue view: budget slider ($0–$500K), greedy item selection from inventory below reorder point ranked by demand trend, 8-column recommendations table, and Place Order flow - Orders.vue: adds Submitted stat card, submittedOrders computed from unfiltered orders, and a dedicated Submitted Orders card with lead-time column; fixes status badge for Submitted orders (en/ja i18n keys added) Co-Authored-By: Claude Sonnet 4.6 --- client/src/App.vue | 3 + client/src/api.js | 5 + client/src/locales/en.js | 1 + client/src/locales/ja.js | 1 + client/src/main.js | 4 +- client/src/views/Orders.vue | 87 ++++++- client/src/views/Restocking.vue | 390 ++++++++++++++++++++++++++++++++ server/main.py | 47 ++++ 8 files changed, 528 insertions(+), 10 deletions(-) create mode 100644 client/src/views/Restocking.vue diff --git a/client/src/App.vue b/client/src/App.vue index c2da05a5c..2c6081c38 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -25,6 +25,9 @@ Reports + + Restocking + {{ t('status.backordered') }}
{{ getOrdersByStatus('Backordered').length }}
+
+
Submitted
+
{{ submittedOrders.length }}
+
@@ -74,6 +78,45 @@
+
+
+

Submitted Orders ({{ submittedOrders.length }})

+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
@@ -95,6 +138,8 @@ export default { const loading = ref(true) const error = ref(null) const orders = ref([]) + // allOrders holds unfiltered set so Submitted orders (from restock) always appear + const allOrders = ref([]) // Use shared filters const { @@ -109,14 +154,15 @@ export default { try { loading.value = true const filters = getCurrentFilters() - const fetchedOrders = await api.getOrders(filters) - - // Sort orders by order_date (earliest first) - orders.value = fetchedOrders.sort((a, b) => { - const dateA = new Date(a.order_date) - const dateB = new Date(b.order_date) - return dateA - dateB - }) + // Fetch filtered + unfiltered in parallel; unfiltered needed for Submitted orders section + const [fetchedOrders, allFetched] = await Promise.all([ + api.getOrders(filters), + api.getOrders({}) + ]) + + const sortByDate = (a, b) => new Date(a.order_date) - new Date(b.order_date) + orders.value = fetchedOrders.sort(sortByDate) + allOrders.value = allFetched.sort(sortByDate) } catch (err) { error.value = 'Failed to load orders: ' + err.message } finally { @@ -129,6 +175,15 @@ export default { loadOrders() }) + const submittedOrders = computed(() => + allOrders.value.filter(o => o.status === 'Submitted') + ) + + const getLeadTimeDays = (orderDate, deliveryDate) => { + const msPerDay = 1000 * 60 * 60 * 24 + return Math.round((new Date(deliveryDate) - new Date(orderDate)) / msPerDay) + } + const getOrdersByStatus = (status) => { return orders.value.filter(order => order.status === status) } @@ -138,7 +193,8 @@ export default { 'Delivered': 'success', 'Shipped': 'info', 'Processing': 'warning', - 'Backordered': 'danger' + 'Backordered': 'danger', + 'Submitted': 'stable' } return statusMap[status] || 'info' } @@ -160,9 +216,11 @@ export default { loading, error, orders, + submittedOrders, getOrdersByStatus, getOrderStatusClass, formatDate, + getLeadTimeDays, currencySymbol, translateProductName, translateCustomerName @@ -203,6 +261,17 @@ export default { width: 120px; } +.col-lead { + width: 100px; +} + +/* Submitted orders table has fewer columns so fixed layout needs different widths */ +.submitted-table .col-order-number { width: 160px; } +.submitted-table .col-items { width: 160px; } +.submitted-table .col-date { width: 160px; } +.submitted-table .col-lead { width: 100px; } +.submitted-table .col-value { width: 140px; } + /* Items details styling */ .items-details { position: relative; diff --git a/client/src/views/Restocking.vue b/client/src/views/Restocking.vue new file mode 100644 index 000000000..64f091ca3 --- /dev/null +++ b/client/src/views/Restocking.vue @@ -0,0 +1,390 @@ + + + + + diff --git a/server/main.py b/server/main.py index a0c2d8c5a..85951bdc9 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 RestockOrderItem(BaseModel): + sku: str + name: str + quantity: int + unit_price: float + +class RestockOrderRequest(BaseModel): + items: List[RestockOrderItem] + warehouse: Optional[str] = None + category: Optional[str] = None + # API endpoints @app.get("/") def root(): @@ -304,6 +316,41 @@ def get_monthly_trends(): result.sort(key=lambda x: x['month']) return result +@app.post("/api/restock-orders", status_code=201) +def create_restock_order(request: RestockOrderRequest): + """Create a restocking order from recommended items""" + now = datetime.utcnow() + year = now.year + # Zero-pad to match existing ORD-YYYY-NNNN format + order_number = f"RST-{year}-{str(len(orders) + 1).zfill(4)}" + total_value = sum(item.quantity * item.unit_price for item in request.items) + + new_order = { + "id": str(len(orders) + 1), + "order_number": order_number, + "customer": "Internal Restock", + "items": [ + { + "sku": item.sku, + "name": item.name, + "quantity": item.quantity, + "unit_price": item.unit_price, + } + for item 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": request.category, + } + + orders.append(new_order) + return new_order + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001) From 88968f7df65447669a84a7879bb1ea4049175363 Mon Sep 17 00:00:00 2001 From: Mohit Mittal Date: Mon, 27 Jul 2026 11:45:33 -0700 Subject: [PATCH 3/3] Fix PurchaseOrderModal component and add missing /api/tasks endpoint - Create PurchaseOrderModal.vue: full Options API modal with create mode (form pre-filled with shortage quantity, supplier/cost/delivery fields, live total preview) and view mode for existing POs; emits po-created on successful API call - Register PurchaseOrderModal in Dashboard.vue to resolve Vue component warning - Add POST /api/purchase-orders backend endpoint (models already existed but no route was wired up) - Add GET/POST/DELETE/PATCH /api/tasks endpoints to fix 404 on app mount; in-memory store with IDs starting at 100 to avoid collisions with client mock task IDs Co-Authored-By: Claude Sonnet 4.6 --- client/src/components/PurchaseOrderModal.vue | 801 +++++++++++++++++++ client/src/views/Dashboard.vue | 2 + server/main.py | 74 ++ 3 files changed, 877 insertions(+) create mode 100644 client/src/components/PurchaseOrderModal.vue diff --git a/client/src/components/PurchaseOrderModal.vue b/client/src/components/PurchaseOrderModal.vue new file mode 100644 index 000000000..d7615ab46 --- /dev/null +++ b/client/src/components/PurchaseOrderModal.vue @@ -0,0 +1,801 @@ + + + + + diff --git a/client/src/views/Dashboard.vue b/client/src/views/Dashboard.vue index 437da9c23..5d496419a 100644 --- a/client/src/views/Dashboard.vue +++ b/client/src/views/Dashboard.vue @@ -304,12 +304,14 @@ import { useI18n } from '../composables/useI18n' import { formatCurrency } from '../utils/currency' import ProductDetailModal from '../components/ProductDetailModal.vue' import BacklogDetailModal from '../components/BacklogDetailModal.vue' +import PurchaseOrderModal from '../components/PurchaseOrderModal.vue' export default { name: 'Dashboard', components: { ProductDetailModal, BacklogDetailModal, + PurchaseOrderModal, }, setup() { const { t, currentCurrency, translateProductName, translateWarehouse } = useI18n() diff --git a/server/main.py b/server/main.py index 85951bdc9..5559a3b15 100644 --- a/server/main.py +++ b/server/main.py @@ -121,6 +121,18 @@ class CreatePurchaseOrderRequest(BaseModel): expected_delivery_date: str notes: Optional[str] = None +class Task(BaseModel): + id: str + title: str + priority: str + dueDate: str + status: str + +class CreateTaskRequest(BaseModel): + title: str + priority: str + dueDate: str + class RestockOrderItem(BaseModel): sku: str name: str @@ -351,6 +363,68 @@ def create_restock_order(request: RestockOrderRequest): return new_order +# In-memory tasks store (resets on server restart, consistent with other mock data) +_tasks: list = [] +_task_id_counter = 100 # Start high to avoid collisions with client mock task IDs + + +@app.get("/api/tasks", response_model=List[Task]) +def get_tasks(): + return _tasks + + +@app.post("/api/tasks", response_model=Task, status_code=201) +def create_task(request: CreateTaskRequest): + global _task_id_counter + _task_id_counter += 1 + task = { + "id": str(_task_id_counter), + "title": request.title, + "priority": request.priority, + "dueDate": request.dueDate, + "status": "pending", + } + _tasks.append(task) + return task + + +@app.delete("/api/tasks/{task_id}", status_code=200) +def delete_task(task_id: str): + global _tasks + task = next((t for t in _tasks if t["id"] == task_id), None) + if not task: + raise HTTPException(status_code=404, detail="Task not found") + _tasks = [t for t in _tasks if t["id"] != task_id] + return {"deleted": task_id} + + +@app.patch("/api/tasks/{task_id}", response_model=Task) +def toggle_task(task_id: str): + task = next((t for t in _tasks if t["id"] == task_id), None) + if not task: + raise HTTPException(status_code=404, detail="Task not found") + task["status"] = "completed" if task["status"] == "pending" else "pending" + return task + + +@app.post("/api/purchase-orders", response_model=PurchaseOrder, status_code=201) +def create_purchase_order(request: CreatePurchaseOrderRequest): + now = datetime.utcnow() + new_po = { + "id": f"PO-{now.strftime('%Y%m%d%H%M%S')}-{len(purchase_orders) + 1}", + "backlog_item_id": request.backlog_item_id, + "supplier_name": request.supplier_name, + "quantity": request.quantity, + "unit_cost": request.unit_cost, + "expected_delivery_date": request.expected_delivery_date, + "status": "Pending", + "created_date": now.isoformat(), + "notes": request.notes, + } + purchase_orders.append(new_po) + return new_po + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)