feat: add executive overview to supply chain new products

This commit is contained in:
jingchuan 2026-08-17 18:36:57 +08:00
parent 5cfbcaa5fe
commit 5441d1786d
9 changed files with 73 additions and 39 deletions

View File

@ -1,39 +1,5 @@
import { requireSession } from "@/lib/server-session";
import { OA_BOARD_DEFAULT_TRACKED_STATUSES } from "@/server/services/oa-board.constants";
import {
createEmptyOaBoardDashboard,
oaBoardService,
} from "@/server/services/oa-board.service";
import NewProductManagementDashboard from "./NewProductManagementDashboard";
export const dynamic = "force-dynamic";
import { redirect } from 'next/navigation'
export default async function NewProductManagementPage() {
await requireSession("/login");
const result = await oaBoardService
.getDashboard({
overallStatus: [...OA_BOARD_DEFAULT_TRACKED_STATUSES],
})
.then((dashboard) => ({
dashboard,
loadErrorMessage: null as string | null,
}))
.catch((error) => {
console.error("Failed to load new product management dashboard", error);
return {
dashboard: createEmptyOaBoardDashboard(),
loadErrorMessage:
error instanceof Error
? `新品管理数据读取失败:${error.message}`
: "新品管理数据读取失败,请稍后重试。",
};
});
return (
<NewProductManagementDashboard
dashboard={result.dashboard}
loadErrorMessage={result.loadErrorMessage}
/>
);
redirect('/supply-chain/new-products/overview')
}

View File

@ -90,6 +90,7 @@ export const SUPPLY_CHAIN_ROUTE_TITLES: Record<string, string> = {
'/supply-chain/database/product-archive': '产品全量档案',
'/supply-chain/database/category-config': '类目配置',
'/supply-chain/database/warehouse-config': '仓库配置',
'/supply-chain/new-products/overview': '新品经营总览',
'/supply-chain/new-products/unified-table': '新品开发进度总表',
'/supply-chain/new-products/sample-progress': '打样进度明细',
'/supply-chain/purchase/daily-affairs': '日常事项',

View File

@ -6,6 +6,11 @@ describe('supply chain menu', () => {
expect(items).toEqual(
expect.arrayContaining([
expect.objectContaining({
label: '新品经营总览',
module: '新品管理',
route: '/supply-chain/new-products/overview',
}),
expect.objectContaining({
label: '新品开发进度总表',
module: '新品管理',

View File

@ -54,6 +54,11 @@ export const SUPPLY_CHAIN_MENU: SupplyChainMenuModule[] = [
label: '新品管理',
icon: 'PackagePlus',
sections: [
{
items: [
{ label: '新品经营总览', route: '/supply-chain/new-products/overview', targetKey: 'newProductOverview' },
],
},
{
title: '公共数据源',
items: [

View File

@ -0,0 +1,49 @@
import { redirect } from 'next/navigation'
import NewProductManagementDashboard from '@/app/intake/new-product-management/NewProductManagementDashboard'
import { requireSession } from '@/lib/server-session'
import { OA_BOARD_DEFAULT_TRACKED_STATUSES } from '@/server/services/oa-board.constants'
import {
createEmptyOaBoardDashboard,
oaBoardService,
} from '@/server/services/oa-board.service'
import { supplyChainPermissionSettingsService } from '@/server/services/supply-chain-permission-settings.service'
export const dynamic = 'force-dynamic'
export const metadata = { title: '新品经营总览 · 供应链中心' }
export default async function NewProductOverviewPage() {
const { authUser } = await requireSession('/login')
const access = await supplyChainPermissionSettingsService.getUserAccess(
authUser,
'newProductOverview',
)
if (!access.view) redirect('/supply-chain')
const result = await oaBoardService
.getDashboard({
overallStatus: [...OA_BOARD_DEFAULT_TRACKED_STATUSES],
})
.then((dashboard) => ({
dashboard,
loadErrorMessage: null as string | null,
}))
.catch((error) => {
console.error('Failed to load supply chain new product overview', error)
return {
dashboard: createEmptyOaBoardDashboard(),
loadErrorMessage:
error instanceof Error
? `新品管理数据读取失败:${error.message}`
: '新品管理数据读取失败,请稍后重试。',
}
})
return (
<div className="h-full overflow-y-auto">
<NewProductManagementDashboard
dashboard={result.dashboard}
loadErrorMessage={result.loadErrorMessage}
/>
</div>
)
}

View File

@ -22,6 +22,7 @@ const TARGET_ICONS: Record<SupplyChainPermissionTargetKey, typeof LayoutDashboar
productArchive: Database,
categoryConfig: Database,
warehouseConfig: Warehouse,
newProductOverview: LayoutDashboard,
newProductUnifiedTable: PackagePlus,
newProductSampleProgress: PackagePlus,
purchaseDailyAffairs: ShoppingCart,

View File

@ -35,7 +35,7 @@ const WORKBENCH_NAV_ITEMS = [
{ href: '/draft-groups', icon: PackageOpen, label: '草稿组' },
{ href: '/intake', icon: ArrowDownToLine, label: '产品资料' },
{ href: '/bom-costing', icon: Calculator, label: 'BOM核算' },
{ href: '/intake/new-product-management', icon: ClipboardList, label: '新品管理' },
{ href: '/supply-chain/new-products/overview', icon: ClipboardList, label: '新品管理' },
{ href: '/warehouse', icon: Warehouse, label: '仓库管理' },
{ href: '/warehouse-quality', icon: ClipboardCheck, label: '仓库质检' },
{ href: '/ai-render-workbench', icon: Sparkles, label: 'AI出图' },
@ -49,8 +49,8 @@ function matchesWorkbenchHref(pathname: string, href: string) {
if (href === '/intake') {
return pathname === '/intake' || (pathname.startsWith('/intake/') && !pathname.startsWith('/intake/new-product-management') && !pathname.startsWith('/intake/oa-board') && !pathname.startsWith('/intake/sample-progress'))
}
if (href === '/intake/new-product-management') {
return pathname === href || pathname.startsWith(`${href}/`) || pathname.startsWith('/intake/oa-board') || pathname.startsWith('/intake/sample-progress')
if (href === '/supply-chain/new-products/overview') {
return pathname === href || pathname.startsWith(`${href}/`) || pathname.startsWith('/intake/new-product-management') || pathname.startsWith('/intake/oa-board') || pathname.startsWith('/intake/sample-progress')
}
return pathname === href || pathname.startsWith(`${href}/`)
}

View File

@ -99,6 +99,9 @@ describe('supply chain permission settings', () => {
})
it('maps new product pages to their permission nodes', () => {
expect(
getSupplyChainPermissionTargetForPath('/supply-chain/new-products/overview'),
).toBe('newProductOverview')
expect(
getSupplyChainPermissionTargetForPath('/supply-chain/new-products/unified-table'),
).toBe('newProductUnifiedTable')

View File

@ -19,6 +19,7 @@ export type SupplyChainPermissionTargetKey =
| 'productArchive'
| 'categoryConfig'
| 'warehouseConfig'
| 'newProductOverview'
| 'newProductUnifiedTable'
| 'newProductSampleProgress'
| 'purchaseDailyAffairs'
@ -67,6 +68,7 @@ export const SUPPLY_CHAIN_PERMISSION_TARGETS: SupplyChainPermissionTarget[] = [
{ key: 'productArchive', label: '产品全量档案', description: '商品、供应链和运营档案' },
{ key: 'categoryConfig', label: '类目配置', description: '产品全量档案类目维护' },
{ key: 'warehouseConfig', label: '仓库配置', description: '出入库仓库维护' },
{ key: 'newProductOverview', label: '新品经营总览', description: '新品经营指标和阶段时效分析' },
{ key: 'newProductUnifiedTable', label: '新品开发进度总表', description: '新品流程公共数据源' },
{ key: 'newProductSampleProgress', label: '打样进度明细', description: '新品打样进度公共数据源' },
{ key: 'purchaseDailyAffairs', label: '日常事项', description: '采购日常协作' },
@ -85,6 +87,7 @@ export const SUPPLY_CHAIN_PERMISSION_TARGET_ROUTES: Partial<
productArchive: '/supply-chain/database/product-archive',
categoryConfig: '/supply-chain/database/category-config',
warehouseConfig: '/supply-chain/database/warehouse-config',
newProductOverview: '/supply-chain/new-products/overview',
newProductUnifiedTable: '/supply-chain/new-products/unified-table',
newProductSampleProgress: '/supply-chain/new-products/sample-progress',
purchaseDailyAffairs: '/supply-chain/purchase/daily-affairs',
@ -238,6 +241,7 @@ export function getSupplyChainPermissionTargetForPath(pathname: string) {
if (pathname === '/supply-chain/database/product-archive') return 'productArchive' as const
if (pathname === '/supply-chain/database/category-config') return 'categoryConfig' as const
if (pathname === '/supply-chain/database/warehouse-config') return 'warehouseConfig' as const
if (pathname === '/supply-chain/new-products/overview') return 'newProductOverview' as const
if (pathname === '/supply-chain/new-products/unified-table') return 'newProductUnifiedTable' as const
if (pathname === '/supply-chain/new-products/sample-progress') return 'newProductSampleProgress' as const
if (pathname === '/supply-chain/purchase/daily-affairs') return 'purchaseDailyAffairs' as const