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
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ npm install && npm run dev
3. Update Pydantic models when changing JSON data structure
4. Inventory filters don't support month (no time dimension)
5. Revenue goals: $800K/month single, $9.6M YTD all months
6. Always document non-obvious logic changes with comments

## File Locations
- Views: `client/src/views/*.vue`
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('restocking.title') }}
</router-link>
<router-link to="/reports" :class="{ active: $route.path === '/reports' }">
Reports
</router-link>
Expand Down
15 changes: 15 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ export const api = {
return response.data
},

async getRestockRecommendations(budget) {
const response = await axios.get(`${API_BASE_URL}/restock/recommendations?budget=${budget}`)
return response.data
},

async getSubmittedRestockOrders() {
const response = await axios.get(`${API_BASE_URL}/restock-orders`)
return response.data
},

async createRestockOrder(restockOrderData) {
const response = await axios.post(`${API_BASE_URL}/restock-orders`, restockOrderData)
return response.data
},

async getBacklog() {
const response = await axios.get(`${API_BASE_URL}/backlog`)
return response.data
Expand Down
48 changes: 46 additions & 2 deletions client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,50 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
leadTime: 'Lead Time'
},
submittedOrders: 'Submitted Orders',
submittedEmpty: 'No restocking orders submitted yet. Build one from the Restocking tab.',
leadTimeDays: '{days} days',
units: '{count} units'
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and order the highest-priority items from the demand forecast',
budget: 'Available Budget',
budgetHint: 'Drag to set how much you can spend this cycle',
allocated: 'Allocated',
remaining: 'Remaining',
itemsRecommended: 'Items Recommended',
totalUnits: 'Total Units',
longestLeadTime: 'Longest Lead Time',
leadTimeDays: '{days} days',
recommended: 'Recommended Restock',
recommendedEmpty: 'Budget too small to fund any forecast item.',
partial: 'Partial',
full: 'Full',
unfunded: 'Not Funded By This Budget',
placeOrder: 'Place Order',
placing: 'Submitting...',
orderPlaced: 'Restocking order {orderNumber} submitted. View it in the Orders tab.',
orderFailed: 'Failed to submit restocking order',
table: {
sku: 'SKU',
item: 'Item',
category: 'Category',
trend: 'Trend',
onHand: 'On Hand',
forecast: 'Forecast',
shortfall: 'Shortfall',
orderQty: 'Order Qty',
unitCost: 'Unit Cost',
lineTotal: 'Line Total',
leadTime: 'Lead Time',
coverage: 'Coverage',
shortfallCost: 'Cost to Cover'
}
},

Expand Down Expand Up @@ -206,7 +249,8 @@ export default {
backordered: 'Backordered',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
adequate: 'Adequate',
submitted: 'Submitted'
},

// Trends
Expand Down
48 changes: 46 additions & 2 deletions client/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,50 @@ export default {
totalValue: '合計金額',
status: 'ステータス',
expectedDelivery: '予定配達日',
actualDelivery: '実際の配達日'
actualDelivery: '実際の配達日',
leadTime: 'リードタイム'
},
submittedOrders: '発注済み注文',
submittedEmpty: '補充注文はまだありません。補充タブから作成してください。',
leadTimeDays: '{days}日',
units: '{count}個'
},

// Restocking
restocking: {
title: '補充',
description: '予算を設定し、需要予測から優先度の高い品目を発注します',
budget: '利用可能予算',
budgetHint: 'スライダーで今サイクルの予算を設定',
allocated: '割当額',
remaining: '残額',
itemsRecommended: '推奨品目数',
totalUnits: '合計数量',
longestLeadTime: '最長リードタイム',
leadTimeDays: '{days}日',
recommended: '推奨補充',
recommendedEmpty: '予算が少なすぎるため、対象品目がありません。',
partial: '一部',
full: '全量',
unfunded: 'この予算では未手配',
placeOrder: '発注する',
placing: '送信中...',
orderPlaced: '補充注文 {orderNumber} を送信しました。注文タブで確認できます。',
orderFailed: '補充注文の送信に失敗しました',
table: {
sku: 'SKU',
item: '品目',
category: 'カテゴリ',
trend: '傾向',
onHand: '在庫数',
forecast: '予測',
shortfall: '不足数',
orderQty: '発注数',
unitCost: '単価',
lineTotal: '小計',
leadTime: 'リードタイム',
coverage: '充足',
shortfallCost: '充足必要額'
}
},

Expand Down Expand Up @@ -206,7 +249,8 @@ export default {
backordered: 'バックオーダー',
inStock: '在庫あり',
lowStock: '在庫僅少',
adequate: '適量'
adequate: '適量',
submitted: '発注済み'
},

// Trends
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
80 changes: 78 additions & 2 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,56 @@
</table>
</div>
</div>

<div class="card submitted-orders-card">
<div class="card-header">
<h3 class="card-title">{{ t('orders.submittedOrders') }} ({{ submittedOrders.length }})</h3>
</div>
<div class="table-container">
<table v-if="submittedOrders.length" 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-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-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 in order.items" :key="item.sku" 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>
<span class="item-meta">{{ t('orders.leadTimeDays', { days: item.lead_time_days }) }}</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: order.lead_time_days }) }}</td>
<td class="col-value"><strong>{{ currencySymbol }}{{ order.total_value.toLocaleString() }}</strong></td>
</tr>
</tbody>
</table>
<div v-else class="empty-state">{{ t('orders.submittedEmpty') }}</div>
</div>
</div>
</div>
</div>
</template>
Expand All @@ -95,6 +145,7 @@ export default {
const loading = ref(true)
const error = ref(null)
const orders = ref([])
const submittedOrders = ref([])

// Use shared filters
const {
Expand Down Expand Up @@ -124,6 +175,16 @@ export default {
}
}

// Restocking orders have no warehouse/category dimension, so they are not
// affected by the global filters and are not reloaded on filter changes.
const loadSubmittedOrders = async () => {
try {
submittedOrders.value = await api.getSubmittedRestockOrders()
} catch (err) {
error.value = 'Failed to load submitted orders: ' + err.message
}
}

// Watch for filter changes and reload data
watch([selectedPeriod, selectedLocation, selectedCategory, selectedStatus], () => {
loadOrders()
Expand All @@ -138,7 +199,8 @@ export default {
'Delivered': 'success',
'Shipped': 'info',
'Processing': 'warning',
'Backordered': 'danger'
'Backordered': 'danger',
'Submitted': 'info'
}
return statusMap[status] || 'info'
}
Expand All @@ -153,13 +215,17 @@ export default {
})
}

onMounted(loadOrders)
onMounted(() => {
loadOrders()
loadSubmittedOrders()
})

return {
t,
loading,
error,
orders,
submittedOrders,
getOrdersByStatus,
getOrderStatusClass,
formatDate,
Expand Down Expand Up @@ -276,4 +342,14 @@ export default {
font-size: 0.813rem;
color: #64748b;
}

.submitted-orders-card {
margin-top: 1.5rem;
}

.empty-state {
padding: 2rem;
text-align: center;
color: #64748b;
}
</style>
Loading