Add Restocking view, submitted orders, and tasks API#85
Open
takoron wants to merge 2 commits into
Open
Conversation
Frontend - New Restocking view with budget slider that auto-selects items by priority - Orders view shows a "Submitted Orders" section above the existing orders table - api.js exposes getSubmittedOrders / createSubmittedOrder - en/ja locales: restocking section, nav entry, submitted-orders labels Backend - POST/GET /api/submitted-orders: budget-validated restocking orders with computed total_value, max_lead_time_days, and expected_delivery - GET/POST/DELETE/PATCH /api/tasks: simple task list with toggle endpoint - InventoryItem gains lead_time_days (seeded across all inventory rows) Data + docs - Empty seed files: server/data/submitted_orders.json, tasks.json - docs/architecture.html: system architecture diagram - Dashboard screenshots at repo root for reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a new Restocking workflow (frontend view + submitted-order persistence in the in-memory backend) and introduces a basic Tasks API to support the existing/soon-to-be-wired UI, along with required inventory lead-time data.
Changes:
- Added FastAPI endpoints for submitted restocking orders (
GET/POST /api/submitted-orders) and tasks (GET/POST/PATCH/DELETE /api/tasks) plus new JSON seed files. - Introduced a new Restocking Vue view and enhanced Orders to display a “Submitted Orders” section backed by the new endpoint.
- Extended inventory items with required
lead_time_daysand added an architecture documentation page.
Reviewed changes
Copilot reviewed 13 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| server/mock_data.py | Loads new submitted_orders.json and tasks.json fixtures into memory. |
| server/main.py | Adds Pydantic models + new submitted-orders and tasks API routes; adds lead_time_days to InventoryItem. |
| server/data/tasks.json | Adds empty seed list for tasks. |
| server/data/submitted_orders.json | Adds empty seed list for submitted orders. |
| server/data/inventory.json | Seeds lead_time_days on all inventory fixture rows. |
| docs/architecture.html | Adds architecture + API/model documentation page. |
| client/src/views/Restocking.vue | New restocking UI (budget slider + greedy selection + submit order). |
| client/src/views/Orders.vue | Displays submitted orders above the existing orders table and fetches them from the API. |
| client/src/main.js | Registers the new /restocking route. |
| client/src/locales/ja.js | Adds nav + Orders/restocking localization strings (JA). |
| client/src/locales/en.js | Adds nav + Orders/restocking localization strings (EN). |
| client/src/App.vue | Adds nav link to the Restocking view. |
| client/src/api.js | Adds getSubmittedOrders / createSubmittedOrder API client helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+226
to
+250
| @app.post("/api/submitted-orders", response_model=SubmittedOrder, status_code=201) | ||
| def create_submitted_order(req: CreateSubmittedOrderRequest): | ||
| """Create a new submitted restocking order from a list of items + budget.""" | ||
| if not req.items: | ||
| raise HTTPException(status_code=400, detail="At least one item is required") | ||
|
|
||
| total_value = round(sum(it.quantity * it.unit_cost for it in req.items), 2) | ||
| if total_value > req.budget: | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail=f"Total value {total_value} exceeds budget {req.budget}" | ||
| ) | ||
|
|
||
| max_lead = max(it.lead_time_days for it in req.items) | ||
| submitted_at = datetime.now() | ||
| expected_at = submitted_at + timedelta(days=max_lead) | ||
|
|
||
| new_id = str(len(submitted_orders) + 1) | ||
| order_number = f"RST-{submitted_at.year}-{int(new_id):04d}" | ||
|
|
||
| new_order = { | ||
| "id": new_id, | ||
| "order_number": order_number, | ||
| "items": [it.model_dump() for it in req.items], | ||
| "total_value": total_value, |
Comment on lines
+239
to
+245
| max_lead = max(it.lead_time_days for it in req.items) | ||
| submitted_at = datetime.now() | ||
| expected_at = submitted_at + timedelta(days=max_lead) | ||
|
|
||
| new_id = str(len(submitted_orders) + 1) | ||
| order_number = f"RST-{submitted_at.year}-{int(new_id):04d}" | ||
|
|
Comment on lines
+275
to
+280
| new_task = { | ||
| "id": f"task-{int(datetime.now().timestamp() * 1000)}", | ||
| "title": req.title.strip(), | ||
| "priority": req.priority, | ||
| "dueDate": req.dueDate, | ||
| "status": "pending", |
Comment on lines
+220
to
+301
| @app.get("/api/submitted-orders", response_model=List[SubmittedOrder]) | ||
| def get_submitted_orders(): | ||
| """Get all submitted restocking orders (newest first).""" | ||
| # Return a copy in reverse-chronological order without mutating storage. | ||
| return list(reversed(submitted_orders)) | ||
|
|
||
| @app.post("/api/submitted-orders", response_model=SubmittedOrder, status_code=201) | ||
| def create_submitted_order(req: CreateSubmittedOrderRequest): | ||
| """Create a new submitted restocking order from a list of items + budget.""" | ||
| if not req.items: | ||
| raise HTTPException(status_code=400, detail="At least one item is required") | ||
|
|
||
| total_value = round(sum(it.quantity * it.unit_cost for it in req.items), 2) | ||
| if total_value > req.budget: | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail=f"Total value {total_value} exceeds budget {req.budget}" | ||
| ) | ||
|
|
||
| max_lead = max(it.lead_time_days for it in req.items) | ||
| submitted_at = datetime.now() | ||
| expected_at = submitted_at + timedelta(days=max_lead) | ||
|
|
||
| new_id = str(len(submitted_orders) + 1) | ||
| order_number = f"RST-{submitted_at.year}-{int(new_id):04d}" | ||
|
|
||
| new_order = { | ||
| "id": new_id, | ||
| "order_number": order_number, | ||
| "items": [it.model_dump() for it in req.items], | ||
| "total_value": total_value, | ||
| "budget": req.budget, | ||
| "submitted_date": submitted_at.isoformat(timespec='seconds'), | ||
| "expected_delivery": expected_at.isoformat(timespec='seconds'), | ||
| "max_lead_time_days": max_lead, | ||
| "status": "Submitted", | ||
| } | ||
| submitted_orders.append(new_order) | ||
| return new_order | ||
|
|
||
| @app.get("/api/tasks", response_model=List[Task]) | ||
| def get_tasks(): | ||
| """Get all user tasks created via the API (newest first).""" | ||
| # Reverse without mutating storage so newest entries surface first. | ||
| return list(reversed(tasks)) | ||
|
|
||
| @app.post("/api/tasks", response_model=Task, status_code=201) | ||
| def create_task(req: CreateTaskRequest): | ||
| """Create a new task. ID is prefixed with 'task-' so it cannot collide | ||
| with the frontend's hardcoded numeric mock task IDs (1-4 in useAuth.js).""" | ||
| if not req.title.strip(): | ||
| raise HTTPException(status_code=400, detail="Title is required") | ||
| if req.priority not in ("high", "medium", "low"): | ||
| raise HTTPException(status_code=400, detail="Priority must be high, medium, or low") | ||
|
|
||
| new_task = { | ||
| "id": f"task-{int(datetime.now().timestamp() * 1000)}", | ||
| "title": req.title.strip(), | ||
| "priority": req.priority, | ||
| "dueDate": req.dueDate, | ||
| "status": "pending", | ||
| } | ||
| tasks.append(new_task) | ||
| return new_task | ||
|
|
||
| @app.delete("/api/tasks/{task_id}") | ||
| def delete_task(task_id: str): | ||
| """Delete a task by ID.""" | ||
| index = next((i for i, t in enumerate(tasks) if t["id"] == task_id), None) | ||
| if index is None: | ||
| raise HTTPException(status_code=404, detail="Task not found") | ||
| tasks.pop(index) | ||
| return {"success": True, "id": task_id} | ||
|
|
||
| @app.patch("/api/tasks/{task_id}", response_model=Task) | ||
| def toggle_task(task_id: str): | ||
| """Toggle a task's status between 'pending' and 'completed'.""" | ||
| task = next((t for t in tasks if t["id"] == task_id), None) | ||
| if task is None: | ||
| raise HTTPException(status_code=404, detail="Task not found") | ||
| task["status"] = "completed" if task["status"] == "pending" else "pending" | ||
| return task |
| <!-- ================= TECH STACK ================= --> | ||
| <section> | ||
| <h2>Tech Stack</h2> | ||
| <p class="section-sub">Pinned versions taken from package.json and pyproject.toml.</p> |
Comment on lines
+769
to
+777
| <td class="path">/api/purchase-orders</td> | ||
| <td>—</td> | ||
| <td>Create purchase order</td> | ||
| </tr> | ||
| <tr> | ||
| <td><span class="method get">GET</span></td> | ||
| <td class="path">/api/purchase-orders/{id}</td> | ||
| <td>—</td> | ||
| <td>Get PO by backlog item</td> |
Comment on lines
+793
to
+800
| <ul class="stack-list"> | ||
| <li><span class="k">sku</span><span class="v">str</span></li> | ||
| <li><span class="k">name</span><span class="v">str</span></li> | ||
| <li><span class="k">category</span><span class="v">str</span></li> | ||
| <li><span class="k">warehouse</span><span class="v">str</span></li> | ||
| <li><span class="k">quantity_on_hand</span><span class="v">int</span></li> | ||
| <li><span class="k">reorder_point</span><span class="v">int</span></li> | ||
| <li><span class="k">unit_cost</span><span class="v">float</span></li> |
- client: add prettier ^3.8.3 to devDependencies - .claude: add debugger agent, vue-optimize skill, settings.json Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Restocking feature end-to-end, plus a basic Tasks API.
Frontend
Restocking.vueview (router + nav entry, EN/JA locales) with a budget slider that auto-selects items by priority.Orders.vuenow renders a "Submitted Orders" section above the existing orders table, populated from the new endpoint.api.jsexposesgetSubmittedOrders/createSubmittedOrder.Backend
GET/POST /api/submitted-orders— budget-validated restocking orders; the server computestotal_value,max_lead_time_days, andexpected_delivery.GET/POST/DELETE/PATCH /api/tasks— simple task list with a toggle endpoint. Task IDs are prefixedtask-…to avoid colliding with the frontend's hardcoded mock IDs.InventoryItemgains a requiredlead_time_daysfield; all rows ininventory.jsonare seeded.Data & docs
server/data/submitted_orders.json,server/data/tasks.json.docs/architecture.html— system architecture diagram.Notes for review
dueDate(matches an upcoming frontend) while the rest of the API stays snake_case — see inline comment inserver/main.py.🤖 Generated with Claude Code