feat: add executive overview to supply chain new products
This commit is contained in:
parent
5cfbcaa5fe
commit
5441d1786d
@ -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')
|
||||
}
|
||||
|
||||
@ -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': '日常事项',
|
||||
|
||||
@ -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: '新品管理',
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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}/`)
|
||||
}
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user