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

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

async createRestockOrder(orderData) {
const response = await axios.post(`${API_BASE_URL}/restock-orders`, orderData)
return response.data
}
}
40 changes: 40 additions & 0 deletions 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 @@ -188,6 +189,45 @@ export default {
}
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and order recommended items from the demand forecast',
budgetTitle: 'Available Budget',
budgetHelp: 'Drag to set how much you can spend on restocking',
recommendations: 'Recommended Restock Items',
itemsRecommended: 'Items Recommended',
totalCost: 'Total Cost',
budgetRemaining: 'Budget Remaining',
longestLeadTime: 'Longest Lead Time',
placeOrder: 'Place Order',
placingOrder: 'Placing Order...',
orderPlaced: 'Restock order {orderNumber} submitted successfully.',
viewInOrders: 'View in Orders',
orderFailed: 'Failed to place restock order: {message}',
noRecommendations: 'No items fit within this budget. Increase the budget to see recommendations.',
noShortfall: 'No forecasted shortfalls - nothing needs restocking.',
overBudget: 'Over budget',
submittedOrders: 'Submitted Orders',
noSubmittedOrders: 'No restock orders submitted yet.',
notFiltered: 'Not affected by filters',
leadTime: 'Lead Time',
eta: 'Estimated Arrival',
daysLead: '{days} days',
statusSubmitted: 'Submitted',
table: {
sku: 'SKU',
itemName: 'Item Name',
currentDemand: 'Current',
forecastedDemand: 'Forecast',
shortfall: 'Shortfall',
unitCost: 'Unit Cost',
quantity: 'Order Qty',
lineTotal: 'Line Total',
leadTime: 'Lead Time'
}
},

// Filters
filters: {
timePeriod: 'Time Period',
Expand Down
40 changes: 40 additions & 0 deletions 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 @@ -188,6 +189,45 @@ export default {
}
},

// Restocking
restocking: {
title: '補充発注',
description: '予算を設定し、需要予測から推奨品目を発注します',
budgetTitle: '利用可能予算',
budgetHelp: 'ドラッグして補充に使える金額を設定してください',
recommendations: '推奨補充品目',
itemsRecommended: '推奨品目数',
totalCost: '合計金額',
budgetRemaining: '残り予算',
longestLeadTime: '最長リードタイム',
placeOrder: '発注する',
placingOrder: '発注中...',
orderPlaced: '補充発注 {orderNumber} を送信しました。',
viewInOrders: '注文一覧で確認',
orderFailed: '補充発注に失敗しました: {message}',
noRecommendations: 'この予算内に収まる品目はありません。予算を増やしてください。',
noShortfall: '予測不足はありません。補充は不要です。',
overBudget: '予算超過',
submittedOrders: '送信済み発注',
noSubmittedOrders: '送信済みの補充発注はありません。',
notFiltered: 'フィルタの影響を受けません',
leadTime: 'リードタイム',
eta: '到着予定',
daysLead: '{days}日',
statusSubmitted: '送信済み',
table: {
sku: 'SKU',
itemName: '品目名',
currentDemand: '現在',
forecastedDemand: '予測',
shortfall: '不足数',
unitCost: '単価',
quantity: '発注数',
lineTotal: '小計',
leadTime: 'リードタイム'
}
},

// Filters
filters: {
timePeriod: '期間',
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
102 changes: 100 additions & 2 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,60 @@
</div>
</div>

<div class="card">
<div class="card-header">
<h3 class="card-title">{{ t('restocking.submittedOrders') }} ({{ restockOrders.length }})</h3>
<span class="card-note">{{ t('restocking.notFiltered') }}</span>
</div>
<div v-if="restockError" class="error">{{ restockError }}</div>
<div v-else-if="restockOrders.length === 0" class="empty-restock">
{{ t('restocking.noSubmittedOrders') }}
</div>
<div v-else class="table-container">
<table class="restock-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-lead">{{ t('restocking.leadTime') }}</th>
<th class="col-date">{{ t('restocking.eta') }}</th>
<th class="col-value">{{ t('orders.table.totalValue') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="order in restockOrders" :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.item_sku" class="item-entry">
<span class="item-name">{{ item.item_name }}</span>
<span class="item-meta">
{{ t('orders.quantity') }}: {{ item.quantity }} @ {{ currencySymbol }}{{ item.unit_cost }}
&middot; {{ t('restocking.daysLead', { days: item.lead_time_days }) }}
</span>
</div>
</div>
</details>
</td>
<td class="col-status">
<span class="badge info">{{ t('restocking.statusSubmitted') }}</span>
</td>
<td class="col-date">{{ formatDate(order.order_date) }}</td>
<td class="col-lead">{{ t('restocking.daysLead', { days: order.max_lead_time_days }) }}</td>
<td class="col-date">{{ formatDate(order.expected_delivery) }}</td>
<td class="col-value"><strong>{{ currencySymbol }}{{ order.total_cost.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 @@ -96,6 +150,11 @@ export default {
const error = ref(null)
const orders = ref([])

// Restock orders use isolated state - a failure here must not blank the
// whole page, and they are deliberately not affected by the shared filters
const restockOrders = ref([])
const restockError = ref(null)

// Use shared filters
const {
selectedPeriod,
Expand Down Expand Up @@ -124,7 +183,18 @@ export default {
}
}

// Watch for filter changes and reload data
const loadRestockOrders = async () => {
try {
restockError.value = null
restockOrders.value = await api.getRestockOrders()
} catch (err) {
restockError.value = 'Failed to load submitted orders: ' + err.message
}
}

// Watch for filter changes and reload data. Restock orders are intentionally
// excluded - they have no warehouse, category, customer or matching status,
// so any active filter would empty the section and look like a bug.
watch([selectedPeriod, selectedLocation, selectedCategory, selectedStatus], () => {
loadOrders()
})
Expand Down Expand Up @@ -153,13 +223,18 @@ export default {
})
}

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

return {
t,
loading,
error,
orders,
restockOrders,
restockError,
getOrdersByStatus,
getOrderStatusClass,
formatDate,
Expand All @@ -172,6 +247,29 @@ export default {
</script>

<style scoped>
/* Submitted (restock) orders section */
.restock-orders-table {
table-layout: fixed;
width: 100%;
}

.col-lead {
width: 120px;
}

.card-note {
font-size: 0.813rem;
color: #64748b;
font-weight: 400;
}

.empty-restock {
padding: 2rem;
text-align: center;
color: #64748b;
font-size: 0.938rem;
}

/* Fixed table layout to prevent column shifting */
.orders-table {
table-layout: fixed;
Expand Down
Loading