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/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<router-link to="/reports" :class="{ active: $route.path === '/reports' }">
Reports
</router-link>
<router-link to="/restocking" :class="{ active: $route.path === '/restocking' }">
{{ t('nav.restocking') }}
</router-link>
</nav>
<LanguageSwitcher />
<ProfileMenu
Expand Down
15 changes: 15 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,20 @@ export const api = {
async getPurchaseOrderByBacklogItem(backlogItemId) {
const response = await axios.get(`${API_BASE_URL}/purchase-orders/${backlogItemId}`)
return response.data
},

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

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

async getRestockingOrders() {
const response = await axios.get(`${API_BASE_URL}/restocking/orders`)
return response.data
}
}
29 changes: 29 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 @@ -311,6 +312,34 @@ export default {
selectLanguage: 'Select Language'
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Recommend items to restock based on demand forecasts and your available budget',
budget: 'Available Budget',
remainingBudget: 'Remaining Budget',
placeOrder: 'Place Order',
orderPlaced: 'Order placed successfully!',
recommendations: 'Recommended Items',
noRecommendations: 'No items fit within the current budget. Try increasing the budget.',
table: {
sku: 'SKU',
itemName: 'Item Name',
trend: 'Trend',
forecastedQty: 'Forecasted Qty',
unitCost: 'Unit Cost',
totalCost: 'Total Cost',
leadTime: 'Lead Time',
days: 'days'
},
totalCost: 'Total Cost',
submittedOrders: 'Submitted Orders',
submittedOrdersDescription: 'Restocking orders placed through the Restocking tab',
noSubmittedOrders: 'No submitted restocking orders yet',
delivery: 'Est. Delivery',
itemsCount: '{count} items'
},

// Common
common: {
loading: 'Loading...',
Expand Down
29 changes: 29 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 @@ -311,6 +312,34 @@ export default {
selectLanguage: '言語を選択'
},

// Restocking
restocking: {
title: '補充',
description: '需要予測と利用可能な予算に基づいて補充すべき商品を推薦します',
budget: '利用可能な予算',
remainingBudget: '残余予算',
placeOrder: '注文を発注',
orderPlaced: '注文が正常に送信されました!',
recommendations: '推奨品目',
noRecommendations: '現在の予算内に収まる品目がありません。予算を増やしてみてください。',
table: {
sku: 'SKU',
itemName: '品目名',
trend: 'トレンド',
forecastedQty: '予測数量',
unitCost: '単価',
totalCost: '合計費用',
leadTime: 'リードタイム',
days: '日'
},
totalCost: '合計費用',
submittedOrders: '送信済み注文',
submittedOrdersDescription: '補充タブから送信された補充注文',
noSubmittedOrders: '送信済みの補充注文はありません',
delivery: '予定配達日',
itemsCount: '{count}件'
},

// Common
common: {
loading: '読み込み中...',
Expand Down
4 changes: 3 additions & 1 deletion 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 @@ -16,7 +17,8 @@ const router = createRouter({
{ path: '/orders', component: Orders },
{ path: '/demand', component: Demand },
{ path: '/spending', component: Spending },
{ path: '/reports', component: Reports }
{ path: '/reports', component: Reports },
{ path: '/restocking', component: Restocking }
]
})

Expand Down
101 changes: 100 additions & 1 deletion client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,57 @@
<div v-if="loading" class="loading">{{ t('common.loading') }}</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<div v-if="restockingOrders.length > 0" class="card">
<div class="card-header">
<h3 class="card-title">
{{ t('restocking.submittedOrders') }}
<span class="badge info" style="margin-left: 0.5rem; vertical-align: middle;">{{ restockingOrders.length }}</span>
</h3>
</div>
<p style="font-size: 0.875rem; color: #64748b; margin-bottom: 1rem;">{{ t('restocking.submittedOrdersDescription') }}</p>
<div class="table-container">
<table class="submitted-orders-table">
<thead>
<tr>
<th class="col-order-number">{{ t('restocking.table.sku') }}</th>
<th class="col-items">{{ t('orders.table.items') }}</th>
<th class="col-total">{{ t('restocking.totalCost') }}</th>
<th class="col-date">{{ t('orders.table.orderDate') }}</th>
<th class="col-status">{{ t('orders.table.status') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="order in restockingOrders" :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('restocking.itemsCount', { count: order.items.length }) }}
</summary>
<div class="items-dropdown">
<div v-for="(item, idx) in order.items" :key="idx" class="submitted-item-entry">
<span class="item-name">{{ item.item_name }}</span>
<span class="item-meta">
Qty: {{ item.forecasted_demand }} &bull; ${{ item.unit_cost }}/unit
</span>
<span class="item-meta">
{{ t('restocking.delivery') }}: {{ formatEstimatedDelivery(order.created_date, item.lead_time_days) }}
</span>
</div>
</div>
</details>
</td>
<td class="col-total"><strong>${{ order.total_cost.toLocaleString() }}</strong></td>
<td class="col-date">{{ formatDate(order.created_date) }}</td>
<td class="col-status">
<span class="badge info">{{ order.status }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>

<div class="stats-grid">
<div class="stat-card success">
<div class="stat-label">{{ t('status.delivered') }}</div>
Expand Down Expand Up @@ -95,6 +146,7 @@ export default {
const loading = ref(true)
const error = ref(null)
const orders = ref([])
const restockingOrders = ref([])

// Use shared filters
const {
Expand Down Expand Up @@ -153,16 +205,41 @@ export default {
})
}

onMounted(loadOrders)
const formatEstimatedDelivery = (createdDate, leadTimeDays) => {
const { currentLocale } = useI18n()
const locale = currentLocale.value === 'ja' ? 'ja-JP' : 'en-US'
const date = new Date(createdDate)
date.setDate(date.getDate() + leadTimeDays)
return date.toLocaleDateString(locale, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}

const loadRestockingOrders = async () => {
try {
restockingOrders.value = await api.getRestockingOrders()
} catch (err) {
console.error('Failed to load restocking orders:', err)
}
}

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

return {
t,
loading,
error,
orders,
restockingOrders,
getOrdersByStatus,
getOrderStatusClass,
formatDate,
formatEstimatedDelivery,
currencySymbol,
translateProductName,
translateCustomerName
Expand Down Expand Up @@ -276,4 +353,26 @@ export default {
font-size: 0.813rem;
color: #64748b;
}

/* Submitted restocking orders table */
.submitted-orders-table {
table-layout: fixed;
width: 100%;
}

.col-total {
width: 140px;
}

.submitted-item-entry {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding: 0.5rem;
border-bottom: 1px solid #f1f5f9;
}

.submitted-item-entry:last-child {
border-bottom: none;
}
</style>
Loading