Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified .claude/hooks/post-tool-use.sh
100755 → 100644
Empty file.
34 changes: 29 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,26 @@ Use the Task tool with these specialized subagents for appropriate tasks:
- 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
- **security-auditor**: Use to audit code for security issues (this is a demo with no auth/validation, so flag accordingly)
- **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

### Slash Commands
Project commands live in `.claude/commands/`: `/start`, `/stop`, `/test`, `/optimize`, `/demo-branch`, `/reset-branch`.

### 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)

## 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`
- **Frontend**: Vue 3 + Composition API + Vite (port 3000), vue-router, axios. i18n (en/ja) and client-only mock auth via composables.
- **Backend**: Python FastAPI (port 8001), managed with `uv` (Python >=3.11)
- **Data**: JSON files in `server/data/` loaded via `server/mock_data.py`; regenerate with `server/generate_data.py`

## Quick Start

Expand All @@ -40,6 +44,18 @@ cd client
npm install && npm run dev
```

`./scripts/start.sh` / `stop.sh` start both at once but are macOS/Linux only — on Windows run the two commands above in separate terminals.

## Testing

```bash
cd tests
uv run pytest backend/ -v # all backend tests
uv run pytest backend/test_inventory.py::test_name -v # single test
```

Tests use FastAPI `TestClient` (fixture in `tests/backend/conftest.py`); config in `tests/pytest.ini`. There is no frontend test runner configured.

## Key Patterns

**Filter System**: 4 filters (Time Period, Warehouse, Category, Order Status) apply to all data via query params
Expand All @@ -52,6 +68,8 @@ npm install && npm run dev
- `GET /api/dashboard/summary` - All filters
- `GET /api/demand`, `/api/backlog` - No filters
- `GET /api/spending/*` - Summary, monthly, categories, transactions
- `GET /api/reports/quarterly`, `/api/reports/monthly-trends` - Reports view data
- `GET /api/inventory/{item_id}`, `/api/orders/{order_id}` - Single item (404 if missing)

## Common Issues
1. Use unique keys in v-for (not `index`) - use `sku`, `month`, etc.
Expand All @@ -60,10 +78,16 @@ npm install && npm run dev
4. Inventory filters don't support month (no time dimension)
5. Revenue goals: $800K/month single, $9.6M YTD all months

## Conventions
- Always document non-obvious logic changes with comments

## File Locations
- Views: `client/src/views/*.vue`
- Views: `client/src/views/*.vue` (Dashboard, Inventory, Orders, Demand, Backlog, Spending, Reports)
- Components: `client/src/components/*.vue` (FilterBar, detail modals, profile/language menus)
- Composables: `client/src/composables/` (`useFilters.js`, `useI18n.js`, `useAuth.js`)
- Locales: `client/src/locales/{en,ja}.js`; formatting helpers in `client/src/utils/`
- API Client: `client/src/api.js`
- Backend: `server/main.py`, `server/mock_data.py`
- Backend: `server/main.py` (routes + Pydantic models), `server/mock_data.py`, `server/generate_data.py`
- Data: `server/data/*.json`
- Styles: `client/src/App.vue`

Expand Down
4 changes: 4 additions & 0 deletions client/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@




# CLAUDE.md - Client

This file provides guidance to Claude Code (claude.ai/code) when working with the Vue 3 frontend.
Expand Down
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
},
"allowScripts": {
"esbuild@0.21.5": true
}
}
3 changes: 3 additions & 0 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<router-link to="/demand" :class="{ active: $route.path === '/demand' }">
{{ t('nav.demandForecast') }}
</router-link>
<router-link to="/restocking" :class="{ active: $route.path === '/restocking' }">
{{ t('nav.restocking') }}
</router-link>
<router-link to="/reports" :class="{ active: $route.path === '/reports' }">
Reports
</router-link>
Expand Down
18 changes: 18 additions & 0 deletions client/src/composables/useRestocking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ref } from 'vue'

// Module-level ref (singleton) - shared across the Restocking and Orders views.
// Restock orders are kept client-side only (the app has no POST /orders endpoint),
// so these reset on a full page refresh, consistent with tasks/purchase orders.
const submittedOrders = ref([])

export function useRestocking() {
// Add a newly placed restock order to the front of the shared list
const addSubmittedOrder = (order) => {
submittedOrders.value.unshift(order)
}

return {
submittedOrders,
addSubmittedOrder
}
}
26 changes: 25 additions & 1 deletion client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
orders: 'Orders',
finance: 'Finance',
demandForecast: 'Demand Forecast',
restocking: 'Restocking',
companyName: 'Catalyst Components',
subtitle: 'Inventory Management System'
},
Expand Down Expand Up @@ -106,6 +107,8 @@ export default {
title: 'Orders',
description: 'View and manage customer orders',
allOrders: 'All Orders',
submittedOrders: 'Submitted Orders',
leadTimeDays: '{days} days',
totalOrders: 'Total Orders',
totalRevenue: 'Total Revenue',
avgOrderValue: 'Avg Order Value',
Expand All @@ -125,7 +128,8 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
leadTime: 'Lead Time'
}
},

Expand Down Expand Up @@ -188,6 +192,25 @@ export default {
}
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and restock high-demand items',
budgetLabel: 'Available Budget',
budgetHint: 'Drag to set how much you can spend on restocking',
recommendations: 'Recommended Restock',
noRecommendations: 'No items need restocking for the current filters.',
qtyToOrder: 'Qty to Order',
unitCost: 'Unit Cost',
lineTotal: 'Line Total',
selectedTotal: 'Order Total',
remaining: 'Remaining Budget',
itemsSelected: '{count} items within budget',
placeOrder: 'Place Order',
orderPlaced: 'Restock order {orderNumber} submitted. View it in the Orders tab.',
estimatedNote: 'Estimated unit cost (no matching inventory item); based on the average inventory unit cost.'
},

// Filters
filters: {
timePeriod: 'Time Period',
Expand All @@ -204,6 +227,7 @@ export default {
shipped: 'Shipped',
processing: 'Processing',
backordered: 'Backordered',
submitted: 'Submitted',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
Expand Down
26 changes: 25 additions & 1 deletion client/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
orders: '注文',
finance: '財務',
demandForecast: '需要予測',
restocking: '在庫補充',
companyName: '触媒コンポーネンツ',
subtitle: '在庫管理システム'
},
Expand Down Expand Up @@ -106,6 +107,8 @@ export default {
title: '注文',
description: '顧客注文の表示と管理',
allOrders: 'すべての注文',
submittedOrders: '送信済み注文',
leadTimeDays: '{days}日',
totalOrders: '総注文数',
totalRevenue: '総収益',
avgOrderValue: '平均注文額',
Expand All @@ -125,7 +128,8 @@ export default {
totalValue: '合計金額',
status: 'ステータス',
expectedDelivery: '予定配達日',
actualDelivery: '実際の配達日'
actualDelivery: '実際の配達日',
leadTime: 'リードタイム'
}
},

Expand Down Expand Up @@ -188,6 +192,25 @@ export default {
}
},

// Restocking
restocking: {
title: '在庫補充',
description: '予算を設定して需要の高い品目を補充します',
budgetLabel: '利用可能な予算',
budgetHint: 'スライダーを動かして補充に使える金額を設定します',
recommendations: '推奨補充品目',
noRecommendations: '現在のフィルター条件では補充が必要な品目はありません。',
qtyToOrder: '発注数量',
unitCost: '単価',
lineTotal: '小計',
selectedTotal: '注文合計',
remaining: '残り予算',
itemsSelected: '予算内の品目:{count}件',
placeOrder: '注文する',
orderPlaced: '補充注文 {orderNumber} を送信しました。注文タブで確認できます。',
estimatedNote: '推定単価(一致する在庫品目がないため、在庫の平均単価に基づきます)。'
},

// Filters
filters: {
timePeriod: '期間',
Expand All @@ -204,6 +227,7 @@ export default {
shipped: '出荷済み',
processing: '処理中',
backordered: 'バックオーダー',
submitted: '送信済み',
inStock: '在庫あり',
lowStock: '在庫僅少',
adequate: '適量'
Expand Down
2 changes: 2 additions & 0 deletions client/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -15,6 +16,7 @@ const router = createRouter({
{ path: '/inventory', component: Inventory },
{ path: '/orders', component: Orders },
{ path: '/demand', component: Demand },
{ path: '/restocking', component: Restocking },
{ path: '/spending', component: Spending },
{ path: '/reports', component: Reports }
]
Expand Down
66 changes: 66 additions & 0 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,57 @@
</div>
</div>

<div class="card" v-if="submittedOrders.length">
<div class="card-header">
<h3 class="card-title">{{ t('orders.submittedOrders') }} ({{ submittedOrders.length }})</h3>
</div>
<div class="table-container">
<table class="orders-table">
<thead>
<tr>
<th class="col-order-number">{{ t('orders.table.orderNumber') }}</th>
<th class="col-customer">{{ t('orders.table.customer') }}</th>
<th class="col-items">{{ t('orders.table.items') }}</th>
<th class="col-status">{{ t('orders.table.status') }}</th>
<th class="col-date">{{ t('orders.table.orderDate') }}</th>
<th class="col-date">{{ t('orders.table.leadTime') }}</th>
<th class="col-value">{{ t('orders.table.totalValue') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="order in submittedOrders" :key="order.id">
<td class="col-order-number"><strong>{{ order.order_number }}</strong></td>
<td class="col-customer">{{ order.customer }}</td>
<td class="col-items">
<details class="items-details">
<summary class="items-summary">
{{ t('orders.itemsCount', { count: order.items.length }) }}
</summary>
<div class="items-dropdown">
<div v-for="(item, idx) in order.items" :key="idx" class="item-entry">
<span class="item-name">{{ translateProductName(item.name) }}</span>
<span class="item-meta">{{ t('orders.quantity') }}: {{ item.quantity }} @ {{ currencySymbol }}{{ item.unit_price }}</span>
</div>
</div>
</details>
</td>
<td class="col-status">
<span :class="['badge', getOrderStatusClass(order.status)]">
{{ t(`status.${order.status.toLowerCase()}`) }}
</span>
</td>
<td class="col-date">{{ formatDate(order.order_date) }}</td>
<td class="col-date">
<div class="lead-time">{{ t('orders.leadTimeDays', { days: order.lead_time_days }) }}</div>
<div class="lead-time-date">{{ formatDate(order.expected_delivery) }}</div>
</td>
<td class="col-value"><strong>{{ currencySymbol }}{{ order.total_value.toLocaleString() }}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>

<div class="card">
<div class="card-header">
<h3 class="card-title">{{ t('orders.allOrders') }} ({{ orders.length }})</h3>
Expand Down Expand Up @@ -83,12 +134,16 @@ import { ref, onMounted, watch, computed } from 'vue'
import { api } from '../api'
import { useFilters } from '../composables/useFilters'
import { useI18n } from '../composables/useI18n'
import { useRestocking } from '../composables/useRestocking'

export default {
name: 'Orders',
setup() {
const { t, currentCurrency, translateProductName, translateCustomerName } = useI18n()

// Client-side restock orders submitted from the Restocking tab
const { submittedOrders } = useRestocking()

const currencySymbol = computed(() => {
return currentCurrency.value === 'JPY' ? '¥' : '$'
})
Expand Down Expand Up @@ -160,6 +215,7 @@ export default {
loading,
error,
orders,
submittedOrders,
getOrdersByStatus,
getOrderStatusClass,
formatDate,
Expand Down Expand Up @@ -276,4 +332,14 @@ export default {
font-size: 0.813rem;
color: #64748b;
}

.lead-time {
font-weight: 600;
color: #0f172a;
}

.lead-time-date {
font-size: 0.75rem;
color: #64748b;
}
</style>
Loading