Skip to content
Merged
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
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,21 @@ logs/
# Playwright MCP artifacts
.playwright-mcp/

# Playwright test runner artifacts
tests/e2e/playwright-report/
tests/e2e/test-results/

# Local Claude Code settings (per-developer)
.claude/settings.local.json

# Workshop-host scratch files (kept out of the engagement repo)
/Anthropic FDE Training FAQ.pdf
/bedrock_check.stderr.txt
/bedrock_check.stdout.txt
/claude-trainee-setup-guide.md
/testclaude.py
/testclaude.stderr.txt
/testclaude.stdout.txt

# Environment variables
.env
6 changes: 6 additions & 0 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
<router-link to="/reports" :class="{ active: $route.path === '/reports' }">
Reports
</router-link>
<router-link to="/restocking" :class="{ active: $route.path === '/restocking' }">
Restocking
</router-link>
<router-link to="/backlog" :class="{ active: $route.path === '/backlog' }">
Backlog
</router-link>
</nav>
<LanguageSwitcher />
<ProfileMenu
Expand Down
33 changes: 33 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ export const api = {
return response.data
},

async getReportsQuarterly(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 getRestockingRecommendations({ budget, service_level = 0.95, warehouse = 'all', category = 'all' } = {}) {
const params = new URLSearchParams()
if (budget != null) params.append('budget', budget)
params.append('service_level', service_level)
if (warehouse && warehouse !== 'all') params.append('warehouse', warehouse)
if (category && category !== 'all') params.append('category', category)

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

async getReportsMonthlyTrends(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 getTasks() {
const response = await axios.get(`${API_BASE_URL}/tasks`)
return response.data
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/BacklogDetailModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</div>
<div class="summary-card warning">
<div class="summary-label">Days Delayed</div>
<div class="summary-value">{{ backlogItem.days_delayed }} days</div>
<div class="summary-value">{{ pluralize(backlogItem.days_delayed, t('dashboard.inventoryShortages.day'), t('dashboard.inventoryShortages.days')) }}</div>
</div>
</div>

Expand Down Expand Up @@ -87,8 +87,9 @@
<script setup>
import { computed } from 'vue'
import { useI18n } from '../composables/useI18n'
import { pluralize } from '../utils/pluralize'

const { translateProductName } = useI18n()
const { t, translateProductName } = useI18n()

const props = defineProps({
isOpen: {
Expand Down
90 changes: 89 additions & 1 deletion client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {
orders: 'Orders',
finance: 'Finance',
demandForecast: 'Demand Forecast',
companyName: 'Catalyst Components',
companyName: 'Meridian Components',
subtitle: 'Inventory Management System'
},

Expand Down Expand Up @@ -55,6 +55,7 @@ export default {
daysDelayed: 'Days Delayed',
priority: 'Priority',
unitsShort: 'units short',
day: 'day',
days: 'days'
},
topProducts: {
Expand Down Expand Up @@ -188,6 +189,93 @@ export default {
}
},

// Backlog view
backlog: {
title: 'Backlog Management',
subtitle: 'Track and resolve inventory shortages',
loading: 'Loading backlog...',
loadError: 'Failed to load backlog',
highPriority: 'High Priority',
mediumPriority: 'Medium Priority',
lowPriority: 'Low Priority',
totalItems: 'Total Backlog Items',
sectionTitle: 'Backlog Items',
empty: '✓ No backlog items - all orders can be fulfilled!',
table: {
orderId: 'Order ID',
sku: 'SKU',
itemName: 'Item Name',
quantityNeeded: 'Quantity Needed',
quantityAvailable: 'Quantity Available',
shortage: 'Shortage',
daysDelayed: 'Days Delayed',
priority: 'Priority'
},
unitsShort: 'units short'
},

// Restocking view (R2)
restocking: {
title: 'Restocking Recommendations',
subtitle: 'Recommended purchase orders given current stock, demand forecast, and your budget ceiling.',
budgetLabel: 'Budget ceiling',
serviceLevelLabel: 'Service level',
generate: 'Generate recommendations',
loading: 'Calculating recommendations...',
loadError: 'Failed to load recommendations',
noResults: 'No restocking actions needed at the current stock and forecast.',
summary: {
candidates: 'Candidates',
inBudget: 'In budget',
outOfBudget: 'Out of budget',
totalCost: 'Total selected cost',
remaining: 'Budget remaining'
},
table: {
sku: 'SKU',
itemName: 'Item Name',
category: 'Category',
warehouse: 'Warehouse',
onHand: 'On Hand',
reorderPoint: 'Reorder Point',
forecast: 'Forecast',
recommendedQty: 'Recommended Qty',
unitCost: 'Unit Cost',
estCost: 'Est. Cost',
status: 'Status'
},
status: {
inBudget: 'In budget',
outOfBudget: 'Out of budget'
},
note: 'Operator-overridable. Recommendations are computed via an (s, S) policy with newsvendor service-level calibration; coverage is currently limited to SKUs with an active demand forecast.'
},

// Reports view
reports: {
title: 'Performance Reports',
subtitle: 'View quarterly performance metrics and monthly trends',
loading: 'Loading reports...',
loadError: 'Failed to load reports',
quarterlyPerformance: 'Quarterly Performance',
monthlyRevenueTrend: 'Monthly Revenue Trend',
monthOverMonth: 'Month-over-Month Analysis',
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',
totalRevenueYtd: 'Total Revenue (YTD)',
avgMonthlyRevenue: 'Avg Monthly Revenue',
totalOrdersYtd: 'Total Orders (YTD)',
bestPerformingQuarter: 'Best Performing Quarter'
},

// Filters
filters: {
timePeriod: 'Time Period',
Expand Down
90 changes: 89 additions & 1 deletion client/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {
orders: '注文',
finance: '財務',
demandForecast: '需要予測',
companyName: '触媒コンポーネンツ',
companyName: 'メリディアン・コンポーネンツ',
subtitle: '在庫管理システム'
},

Expand Down Expand Up @@ -55,6 +55,7 @@ export default {
daysDelayed: '遅延日数',
priority: '優先度',
unitsShort: '単位不足',
day: '日',
days: '日'
},
topProducts: {
Expand Down Expand Up @@ -188,6 +189,93 @@ export default {
}
},

// Backlog view
backlog: {
title: 'バックログ管理',
subtitle: '在庫不足を追跡し解決',
loading: 'バックログを読み込み中...',
loadError: 'バックログの読み込みに失敗しました',
highPriority: '優先度: 高',
mediumPriority: '優先度: 中',
lowPriority: '優先度: 低',
totalItems: 'バックログアイテム総数',
sectionTitle: 'バックログアイテム',
empty: '✓ バックログアイテムなし - すべての注文を履行できます!',
table: {
orderId: '注文ID',
sku: 'SKU',
itemName: '品目名',
quantityNeeded: '必要数量',
quantityAvailable: '在庫数量',
shortage: '不足',
daysDelayed: '遅延日数',
priority: '優先度'
},
unitsShort: '単位不足'
},

// Restocking view (R2)
restocking: {
title: '補充推奨',
subtitle: '現在の在庫、需要予測、予算上限に基づいて推奨される発注書。',
budgetLabel: '予算上限',
serviceLevelLabel: 'サービスレベル',
generate: '推奨を生成',
loading: '推奨を計算中...',
loadError: '推奨の読み込みに失敗しました',
noResults: '現在の在庫と予測では、補充は必要ありません。',
summary: {
candidates: '候補',
inBudget: '予算内',
outOfBudget: '予算外',
totalCost: '選択合計コスト',
remaining: '予算残高'
},
table: {
sku: 'SKU',
itemName: '品目名',
category: 'カテゴリ',
warehouse: '倉庫',
onHand: '在庫数',
reorderPoint: '発注点',
forecast: '予測',
recommendedQty: '推奨数量',
unitCost: '単価',
estCost: '見積コスト',
status: 'ステータス'
},
status: {
inBudget: '予算内',
outOfBudget: '予算外'
},
note: 'オペレータが調整可能。推奨は(s, S)ポリシーとニュースベンダーモデルのサービスレベル較正で計算されます。現在、需要予測が存在するSKUに限定されています。'
},

// Reports view
reports: {
title: 'パフォーマンスレポート',
subtitle: '四半期パフォーマンス指標と月次トレンドを表示',
loading: 'レポートを読み込み中...',
loadError: 'レポートの読み込みに失敗しました',
quarterlyPerformance: '四半期パフォーマンス',
monthlyRevenueTrend: '月次収益トレンド',
monthOverMonth: '前月比分析',
quarter: '四半期',
totalOrders: '合計注文数',
totalRevenue: '合計収益',
avgOrderValue: '平均注文金額',
fulfillmentRate: '達成率',
month: '月',
orders: '注文数',
revenue: '収益',
change: '変化',
growthRate: '成長率',
totalRevenueYtd: '累計収益 (YTD)',
avgMonthlyRevenue: '月平均収益',
totalOrdersYtd: '累計注文数 (YTD)',
bestPerformingQuarter: '最高業績四半期'
},

// Filters
filters: {
timePeriod: '期間',
Expand Down
6 changes: 5 additions & 1 deletion client/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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'
import Backlog from './views/Backlog.vue'

const router = createRouter({
history: createWebHistory(),
Expand All @@ -16,7 +18,9 @@ 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 },
{ path: '/backlog', component: Backlog }
]
})

Expand Down
17 changes: 17 additions & 0 deletions client/src/utils/pluralize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Tiny pluralization helper.
*
* Returns "<n> <singular|plural>" depending on the value of n.
* Designed for English, where 1 takes the singular form and everything else
* (including 0) takes the plural. For locales without grammatical number
* (e.g. Japanese) the i18n layer should resolve to a single string and this
* helper is unnecessary.
*
* pluralize(1, 'day', 'days') // "1 day"
* pluralize(0, 'day', 'days') // "0 days"
* pluralize(3, 'item', 'items') // "3 items"
*/
export function pluralize(count, singular, plural) {
const n = Number(count) || 0
return `${n} ${n === 1 ? singular : plural}`
}
Loading