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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ yarn-error.log*
*~
.DS_Store

# Claude Code local settings (personal overrides, not shared with the team)
.claude/settings.local.json

# UV/Python package managers
uv.lock
*.lock
Expand Down
5 changes: 4 additions & 1 deletion client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
<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
{{ t('nav.reports') }}
</router-link>
</nav>
<LanguageSwitcher />
Expand Down
32 changes: 32 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,38 @@ export const api = {
return response.data
},

async getQuarterlyReports(filters = {}) {
const params = new URLSearchParams()
if (filters.warehouse && filters.warehouse !== 'all') params.append('warehouse', filters.warehouse)
if (filters.category && filters.category !== 'all') params.append('category', filters.category)
if (filters.status && filters.status !== 'all') params.append('status', filters.status)
if (filters.month && filters.month !== 'all') params.append('month', filters.month)

const response = await axios.get(`${API_BASE_URL}/reports/quarterly?${params.toString()}`)
return response.data
},

async getMonthlyTrends(filters = {}) {
const params = new URLSearchParams()
if (filters.warehouse && filters.warehouse !== 'all') params.append('warehouse', filters.warehouse)
if (filters.category && filters.category !== 'all') params.append('category', filters.category)
if (filters.status && filters.status !== 'all') params.append('status', filters.status)
if (filters.month && filters.month !== 'all') params.append('month', filters.month)

const response = await axios.get(`${API_BASE_URL}/reports/monthly-trends?${params.toString()}`)
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 createPurchaseOrder(purchaseOrderData) {
const response = await axios.post(`${API_BASE_URL}/purchase-orders`, purchaseOrderData)
return response.data
Expand Down
57 changes: 56 additions & 1 deletion client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export default {
orders: 'Orders',
finance: 'Finance',
demandForecast: 'Demand Forecast',
restocking: 'Restocking',
reports: 'Reports',
companyName: 'Catalyst Components',
subtitle: 'Inventory Management System'
},
Expand Down Expand Up @@ -125,7 +127,59 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
leadTime: 'Lead Time'
},
submittedOrders: 'Submitted Orders',
leadTimeDays: '{days} days'
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and restock items recommended from demand forecasts',
budgetLabel: 'Available Budget',
recommended: 'Recommended Items',
totalCost: 'Total Cost',
remaining: 'Remaining Budget',
placeOrder: 'Place Order',
placingOrder: 'Placing Order...',
orderSuccess: 'Order submitted successfully',
orderError: 'Failed to submit order',
noItems: 'No items are recommended for this budget',
selectItems: 'Uncheck items to exclude them from the order',
table: {
sku: 'SKU',
itemName: 'Item Name',
quantity: 'Quantity',
unitCost: 'Unit Cost',
lineTotal: 'Line Total',
trend: 'Trend'
}
},

// Reports
reports: {
title: 'Performance Reports',
description: 'View quarterly performance metrics and monthly trends',
quarterlyPerformance: 'Quarterly Performance',
monthlyRevenueTrend: 'Monthly Revenue Trend',
monthOverMonth: 'Month-over-Month Analysis',
totalRevenueYTD: 'Total Revenue (YTD)',
avgMonthlyRevenue: 'Avg Monthly Revenue',
totalOrdersYTD: 'Total Orders (YTD)',
bestQuarter: 'Best Performing Quarter',
table: {
quarter: 'Quarter',
totalOrders: 'Total Orders',
totalRevenue: 'Total Revenue',
avgOrderValue: 'Avg Order Value',
fulfillmentRate: 'Fulfillment Rate',
month: 'Month',
orders: 'Orders',
revenue: 'Revenue',
change: 'Change',
growthRate: 'Growth Rate'
}
},

Expand Down Expand Up @@ -204,6 +258,7 @@ export default {
shipped: 'Shipped',
processing: 'Processing',
backordered: 'Backordered',
submitted: 'Submitted',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
Expand Down
57 changes: 56 additions & 1 deletion client/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export default {
orders: '注文',
finance: '財務',
demandForecast: '需要予測',
restocking: '補充',
reports: 'レポート',
companyName: '触媒コンポーネンツ',
subtitle: '在庫管理システム'
},
Expand Down Expand Up @@ -125,7 +127,59 @@ export default {
totalValue: '合計金額',
status: 'ステータス',
expectedDelivery: '予定配達日',
actualDelivery: '実際の配達日'
actualDelivery: '実際の配達日',
leadTime: 'リードタイム'
},
submittedOrders: '提出済み注文',
leadTimeDays: '{days}日'
},

// Restocking
restocking: {
title: '補充',
description: '予算を設定し、需要予測に基づいて推奨された品目を補充します',
budgetLabel: '利用可能な予算',
recommended: '推奨品目',
totalCost: '合計金額',
remaining: '残り予算',
placeOrder: '注文する',
placingOrder: '注文中...',
orderSuccess: '注文が正常に送信されました',
orderError: '注文の送信に失敗しました',
noItems: 'この予算で推奨される品目はありません',
selectItems: 'チェックを外すと注文から除外されます',
table: {
sku: 'SKU',
itemName: '品目名',
quantity: '数量',
unitCost: '単価',
lineTotal: '小計',
trend: 'トレンド'
}
},

// Reports
reports: {
title: 'パフォーマンスレポート',
description: '四半期業績指標と月次トレンドを表示',
quarterlyPerformance: '四半期業績',
monthlyRevenueTrend: '月次収益トレンド',
monthOverMonth: '月次比較分析',
totalRevenueYTD: '総収益(年初来)',
avgMonthlyRevenue: '平均月次収益',
totalOrdersYTD: '総注文数(年初来)',
bestQuarter: '最高業績四半期',
table: {
quarter: '四半期',
totalOrders: '総注文数',
totalRevenue: '総収益',
avgOrderValue: '平均注文額',
fulfillmentRate: '充足率',
month: '月',
orders: '注文数',
revenue: '収益',
change: '変化',
growthRate: '成長率'
}
},

Expand Down Expand Up @@ -204,6 +258,7 @@ export default {
shipped: '出荷済み',
processing: '処理中',
backordered: 'バックオーダー',
submitted: '提出済み',
inStock: '在庫あり',
lowStock: '在庫僅少',
adequate: '適量'
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
87 changes: 80 additions & 7 deletions client/src/views/Orders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,51 @@
</table>
</div>
</div>

<div class="card" v-if="submittedOrders.length > 0">
<div class="card-header">
<h3 class="card-title">
{{ t('orders.submittedOrders') }} ({{ submittedOrders.length }})
<span class="badge info submitted-badge">{{ t('status.submitted') }}</span>
</h3>
</div>
<div class="table-container">
<table class="submitted-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-date">{{ t('orders.table.orderDate') }}</th>
<th class="col-date">{{ t('orders.table.expectedDelivery') }}</th>
<th class="col-value">{{ t('orders.table.totalValue') }}</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-date">{{ formatDate(order.order_date) }}</td>
<td class="col-date">{{ formatDate(order.expected_delivery) }}</td>
<td class="col-value"><strong>{{ currencySymbol }}{{ order.total_value.toLocaleString() }}</strong></td>
<td class="col-lead-time">{{ t('orders.leadTimeDays', { days: order.lead_time_days }) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
Expand All @@ -95,6 +140,7 @@ export default {
const loading = ref(true)
const error = ref(null)
const orders = ref([])
const submittedOrders = ref([])

// Use shared filters
const {
Expand All @@ -111,22 +157,31 @@ export default {
const filters = getCurrentFilters()
const fetchedOrders = await api.getOrders(filters)

// Sort orders by order_date (earliest first)
orders.value = fetchedOrders.sort((a, b) => {
const dateA = new Date(a.order_date)
const dateB = new Date(b.order_date)
return dateA - dateB
})
// Exclude submitted (restocking) orders from the main list/stats,
// then sort by order_date (earliest first)
orders.value = fetchedOrders
.filter(o => o.status !== 'Submitted')
.sort((a, b) => {
const dateA = new Date(a.order_date)
const dateB = new Date(b.order_date)
return dateA - dateB
})
} catch (err) {
error.value = 'Failed to load orders: ' + err.message
} finally {
loading.value = false
}
}

const loadSubmittedOrders = async () => {
const filters = { ...getCurrentFilters(), status: 'Submitted' }
submittedOrders.value = await api.getOrders(filters)
}

// Watch for filter changes and reload data
watch([selectedPeriod, selectedLocation, selectedCategory, selectedStatus], () => {
loadOrders()
loadSubmittedOrders()
})

const getOrdersByStatus = (status) => {
Expand All @@ -153,13 +208,17 @@ export default {
})
}

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

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

.submitted-orders-table {
table-layout: fixed;
width: 100%;
}

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

.submitted-badge {
margin-left: 0.75rem;
vertical-align: middle;
}
</style>
Loading