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 @@ -46,6 +46,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

## Code Style
- Always document non-obvious logic changes with comments

## 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 @@ -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
5 changes: 5 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,10 @@ export const api = {
async getPurchaseOrderByBacklogItem(backlogItemId) {
const response = await axios.get(`${API_BASE_URL}/purchase-orders/${backlogItemId}`)
return response.data
},

async createRestockOrder(payload) {
const response = await axios.post(`${API_BASE_URL}/orders`, payload)
return response.data
}
}
32 changes: 31 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,11 +107,13 @@ 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',
leadTimeDays: '{days} days',
quantity: 'Qty',
table: {
orderNumber: 'Order Number',
Expand All @@ -125,7 +128,33 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
leadTime: 'Lead Time'
}
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and order recommended restock quantities from the demand forecast',
budgetLabel: 'Available Budget',
recommendationsTitle: 'Recommended Restock',
noRecommendations: 'No items fit within this budget. Increase the budget to see recommendations.',
totalCost: 'Total Cost',
remaining: 'Remaining Budget',
placeOrder: 'Place Order',
placingOrder: 'Placing order...',
orderPlaced: 'Restocking order {orderNumber} submitted successfully.',
viewInOrders: 'View in Orders',
table: {
sku: 'SKU',
itemName: 'Item Name',
currentDemand: 'Current Demand',
forecastedDemand: 'Forecasted Demand',
recommendedQty: 'Recommended Qty',
unitCost: 'Unit Cost',
lineTotal: 'Line Total',
trend: 'Trend'
}
},

Expand Down Expand Up @@ -204,6 +233,7 @@ export default {
shipped: 'Shipped',
processing: 'Processing',
backordered: 'Backordered',
submitted: 'Submitted',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
Expand Down
32 changes: 31 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,11 +107,13 @@ export default {
title: '注文',
description: '顧客注文の表示と管理',
allOrders: 'すべての注文',
submittedOrders: '提出済み注文',
totalOrders: '総注文数',
totalRevenue: '総収益',
avgOrderValue: '平均注文額',
onTimeDelivery: '定時配達',
itemsCount: '{count}件',
leadTimeDays: '{days}日',
quantity: '数量',
table: {
orderNumber: '注文番号',
Expand All @@ -125,7 +128,33 @@ export default {
totalValue: '合計金額',
status: 'ステータス',
expectedDelivery: '予定配達日',
actualDelivery: '実際の配達日'
actualDelivery: '実際の配達日',
leadTime: 'リードタイム'
}
},

// Restocking
restocking: {
title: '在庫補充',
description: '予算を設定し、需要予測から推奨される補充数量を発注します',
budgetLabel: '利用可能な予算',
recommendationsTitle: '推奨補充',
noRecommendations: 'この予算に収まる品目がありません。予算を増やすと推奨が表示されます。',
totalCost: '合計コスト',
remaining: '残り予算',
placeOrder: '発注する',
placingOrder: '発注中...',
orderPlaced: '在庫補充注文 {orderNumber} が正常に提出されました。',
viewInOrders: '注文で表示',
table: {
sku: 'SKU',
itemName: '品目名',
currentDemand: '現在の需要',
forecastedDemand: '予測需要',
recommendedQty: '推奨数量',
unitCost: '単価',
lineTotal: '小計',
trend: 'トレンド'
}
},

Expand Down Expand Up @@ -204,6 +233,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 @@ -5,6 +5,7 @@ import Dashboard from './views/Dashboard.vue'
import Inventory from './views/Inventory.vue'
import Orders from './views/Orders.vue'
import Demand from './views/Demand.vue'
import Restocking from './views/Restocking.vue'
import Spending from './views/Spending.vue'
import Reports from './views/Reports.vue'

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
81 changes: 78 additions & 3 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<div class="card">
<div class="card-header">
<h3 class="card-title">{{ t('orders.allOrders') }} ({{ orders.length }})</h3>
<h3 class="card-title">{{ t('orders.allOrders') }} ({{ activeOrders.length }})</h3>
</div>
<div class="table-container">
<table class="orders-table">
Expand All @@ -45,7 +45,7 @@
</tr>
</thead>
<tbody>
<tr v-for="order in orders" :key="order.id">
<tr v-for="order in activeOrders" :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 @@ -74,6 +74,48 @@
</table>
</div>
</div>

<div v-if="submittedOrders.length > 0" 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-items">{{ t('orders.table.items') }}</th>
<th class="col-value">{{ t('orders.table.totalValue') }}</th>
<th class="col-date">{{ t('orders.table.orderDate') }}</th>
<th class="col-date">{{ t('orders.table.expectedDelivery') }}</th>
<th class="col-lead-time">{{ t('orders.table.leadTime') }}</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-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-value"><strong>{{ currencySymbol }}{{ order.total_value.toLocaleString() }}</strong></td>
<td class="col-date">{{ formatDate(order.order_date) }}</td>
<td class="col-date">{{ formatDate(order.expected_delivery) }}</td>
<td class="col-lead-time">{{ t('orders.leadTimeDays', { days: getLeadTimeDays(order) }) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
Expand Down Expand Up @@ -129,6 +171,17 @@ export default {
loadOrders()
})

// Orders shown in the main "All Orders" table — submitted restock orders
// are broken out into their own section below.
const activeOrders = computed(() => {
return orders.value.filter(order => order.status !== 'Submitted')
})

// Submitted restock orders, rendered in a dedicated card.
const submittedOrders = computed(() => {
return orders.value.filter(order => order.status === 'Submitted')
})

const getOrdersByStatus = (status) => {
return orders.value.filter(order => order.status === status)
}
Expand All @@ -138,11 +191,20 @@ export default {
'Delivered': 'success',
'Shipped': 'info',
'Processing': 'warning',
'Backordered': 'danger'
'Backordered': 'danger',
'Submitted': 'submitted'
}
return statusMap[status] || 'info'
}

// Lead time in whole days between order placement and expected delivery.
// 86400000 ms = 1 day; Math.round smooths any DST/partial-day drift.
const getLeadTimeDays = (order) => {
return Math.round(
(new Date(order.expected_delivery) - new Date(order.order_date)) / 86400000
)
}

const formatDate = (dateString) => {
const { currentLocale } = useI18n()
const locale = currentLocale.value === 'ja' ? 'ja-JP' : 'en-US'
Expand All @@ -160,8 +222,11 @@ export default {
loading,
error,
orders,
activeOrders,
submittedOrders,
getOrdersByStatus,
getOrderStatusClass,
getLeadTimeDays,
formatDate,
currencySymbol,
translateProductName,
Expand Down Expand Up @@ -203,6 +268,16 @@ export default {
width: 120px;
}

.col-lead-time {
width: 120px;
}

/* Submitted restock orders badge */
.badge.submitted {
background: #e0e7ff;
color: #3730a3;
}

/* Items details styling */
.items-details {
position: relative;
Expand Down
Loading