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
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ npm install && npm run dev
**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

## Coding Conventions
- **Always document non-obvious logic changes with comments** — explain the *why* for edge cases, workarounds, non-trivial calculations, and ordering dependencies. Match surrounding comment style; don't over-comment obvious code.

## API Endpoints
- `GET /api/inventory` - Filters: warehouse, category
- `GET /api/orders` - Filters: warehouse, category, status, month
Expand Down
3 changes: 3 additions & 0 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<router-link to="/orders" :class="{ active: $route.path === '/orders' }">
{{ t('nav.orders') }}
</router-link>
<router-link to="/restocking" :class="{ active: $route.path === '/restocking' }">
{{ t('nav.restocking') }}
</router-link>
<router-link to="/spending" :class="{ active: $route.path === '/spending' }">
{{ t('nav.finance') }}
</router-link>
Expand Down
5 changes: 5 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export const api = {
return response.data
},

async createOrder(orderData) {
const response = await axios.post(`${API_BASE_URL}/orders`, orderData)
return response.data
},

async getDemandForecasts() {
const response = await axios.get(`${API_BASE_URL}/demand`)
return response.data
Expand Down
38 changes: 37 additions & 1 deletion client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
overview: 'Overview',
inventory: 'Inventory',
orders: 'Orders',
restocking: 'Restocking',
finance: 'Finance',
demandForecast: 'Demand Forecast',
companyName: 'Catalyst Components',
Expand Down Expand Up @@ -106,12 +107,14 @@ export default {
title: 'Orders',
description: 'View and manage customer orders',
allOrders: 'All Orders',
submittedOrders: 'Submitted Orders',
totalOrders: 'Total Orders',
totalRevenue: 'Total Revenue',
avgOrderValue: 'Avg Order Value',
onTimeDelivery: 'On-Time Delivery',
itemsCount: '{count} items',
quantity: 'Qty',
leadTimeDays: '{days} days',
table: {
orderNumber: 'Order Number',
orderId: 'Order ID',
Expand All @@ -125,7 +128,39 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
leadTime: 'Lead Time'
}
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Turn demand forecasts into a budget-constrained restock order',
budget: 'Budget',
recommendedTotal: 'Recommended Total',
remaining: 'Remaining',
itemsSelected: 'Items Selected',
setBudget: 'Set Budget',
availableBudget: 'Available Budget',
recommendedRestock: 'Recommended Restock',
placeOrder: 'Place Order',
placing: 'Placing order...',
empty: 'No items fit within the current budget. Increase the budget to see recommendations.',
overBudget: 'Selected total exceeds the available budget. Reduce quantities or deselect items.',
withinBudget: 'Within budget',
success: 'Order {orderNumber} submitted successfully.',
leadTimeDays: '{days} days',
table: {
sku: 'SKU',
item: 'Item',
trend: 'Trend',
unitCost: 'Unit Cost',
projectedGap: 'Projected Gap',
include: 'Include',
quantity: 'Quantity',
lineTotal: 'Line Total',
leadTime: 'Lead Time'
}
},

Expand Down Expand Up @@ -204,6 +239,7 @@ export default {
shipped: 'Shipped',
processing: 'Processing',
backordered: 'Backordered',
submitted: 'Submitted',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
Expand Down
38 changes: 37 additions & 1 deletion client/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
overview: '概要',
inventory: '在庫',
orders: '注文',
restocking: '補充',
finance: '財務',
demandForecast: '需要予測',
companyName: '触媒コンポーネンツ',
Expand Down Expand Up @@ -106,12 +107,14 @@ export default {
title: '注文',
description: '顧客注文の表示と管理',
allOrders: 'すべての注文',
submittedOrders: '送信済み注文',
totalOrders: '総注文数',
totalRevenue: '総収益',
avgOrderValue: '平均注文額',
onTimeDelivery: '定時配達',
itemsCount: '{count}件',
quantity: '数量',
leadTimeDays: '{days}日',
table: {
orderNumber: '注文番号',
orderId: '注文ID',
Expand All @@ -125,7 +128,39 @@ export default {
totalValue: '合計金額',
status: 'ステータス',
expectedDelivery: '予定配達日',
actualDelivery: '実際の配達日'
actualDelivery: '実際の配達日',
leadTime: 'リードタイム'
}
},

// Restocking
restocking: {
title: '補充',
description: '需要予測を予算制約付きの補充注文に変換します',
budget: '予算',
recommendedTotal: '推奨合計',
remaining: '残額',
itemsSelected: '選択品目数',
setBudget: '予算を設定',
availableBudget: '利用可能予算',
recommendedRestock: '推奨補充',
placeOrder: '注文する',
placing: '注文送信中...',
empty: '現在の予算内に収まる品目がありません。予算を増やすと推奨が表示されます。',
overBudget: '選択合計が利用可能予算を超えています。数量を減らすか品目の選択を解除してください。',
withinBudget: '予算内',
success: '注文 {orderNumber} が正常に送信されました。',
leadTimeDays: '{days}日',
table: {
sku: 'SKU',
item: '品目',
trend: 'トレンド',
unitCost: '単価',
projectedGap: '予測ギャップ',
include: '含める',
quantity: '数量',
lineTotal: '小計',
leadTime: 'リードタイム'
}
},

Expand Down Expand Up @@ -204,6 +239,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 @@ -4,6 +4,7 @@ import App from './App.vue'
import Dashboard from './views/Dashboard.vue'
import Inventory from './views/Inventory.vue'
import Orders from './views/Orders.vue'
import Restocking from './views/Restocking.vue'
import Demand from './views/Demand.vue'
import Spending from './views/Spending.vue'
import Reports from './views/Reports.vue'
Expand All @@ -14,6 +15,7 @@ const router = createRouter({
{ path: '/', component: Dashboard },
{ path: '/inventory', component: Inventory },
{ path: '/orders', component: Orders },
{ path: '/restocking', component: Restocking },
{ path: '/demand', component: Demand },
{ path: '/spending', component: Spending },
{ path: '/reports', component: Reports }
Expand Down
79 changes: 76 additions & 3 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,61 @@
</div>
</div>

<!-- Submitted orders card: only shown when restocking orders are present -->
<div v-if="submittedOrders.length" class="card">
<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.expectedDelivery') }}</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">{{ translateCustomerName(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">{{ formatDate(order.expected_delivery) }}</td>
<td class="col-date">{{ t('orders.leadTimeDays', { days: getLeadTimeDays(order) }) }}</td>
<td class="col-value"><strong>{{ currencySymbol }}{{ order.total_value.toLocaleString() }}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>

<!-- All Orders card: excludes Submitted orders to avoid double-listing -->
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ t('orders.allOrders') }} ({{ orders.length }})</h3>
<h3 class="card-title">{{ t('orders.allOrders') }} ({{ mainOrders.length }})</h3>
</div>
<div class="table-container">
<table class="orders-table">
Expand All @@ -45,7 +97,7 @@
</tr>
</thead>
<tbody>
<tr v-for="order in orders" :key="order.id">
<tr v-for="order in mainOrders" :key="order.id">
<td class="col-order-number"><strong>{{ order.order_number }}</strong></td>
<td class="col-customer">{{ translateCustomerName(order.customer) }}</td>
<td class="col-items">
Expand Down Expand Up @@ -138,11 +190,29 @@ export default {
'Delivered': 'success',
'Shipped': 'info',
'Processing': 'warning',
'Backordered': 'danger'
'Backordered': 'danger',
// Submitted status used for restocking orders placed via the Restocking tab
'Submitted': 'info'
}
return statusMap[status] || 'info'
}

// Orders placed via the Restocking tab arrive with status 'Submitted'
const submittedOrders = computed(() => orders.value.filter(o => o.status === 'Submitted'))
// All other orders (avoids double-listing in the main table)
const mainOrders = computed(() => orders.value.filter(o => o.status !== 'Submitted'))

/**
* Derives lead time in days from order_date → expected_delivery.
* Returns 0 for invalid/missing dates rather than NaN.
*/
const getLeadTimeDays = (order) => {
const start = new Date(order.order_date)
const end = new Date(order.expected_delivery)
const diff = Math.round((end - start) / 86400000)
return Number.isFinite(diff) ? diff : 0
}

const formatDate = (dateString) => {
const { currentLocale } = useI18n()
const locale = currentLocale.value === 'ja' ? 'ja-JP' : 'en-US'
Expand All @@ -160,8 +230,11 @@ export default {
loading,
error,
orders,
submittedOrders,
mainOrders,
getOrdersByStatus,
getOrderStatusClass,
getLeadTimeDays,
formatDate,
currencySymbol,
translateProductName,
Expand Down
Loading