From eca1d63842f4c836286e7e8d0b6d82b910ddc929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E5=B1=85?= Date: Fri, 16 Oct 2026 17:41:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(supply-chain):=20=E9=99=90=E4=BB=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9A=84=E6=9F=A5=E7=9C=8B/=E5=AF=BC?= =?UTF-8?q?=E5=87=BA/=E7=94=B3=E8=AF=89=E6=9D=83=E9=99=90=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E7=94=B1=E7=AE=A1=E7=90=86=E5=91=98=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 查看:页面与数据仍走「限价管理 · 可见」;导出改按「限价管理 · 下载」,申诉/审核/生成/导入改按「限价管理 · 操作」(原来是经营管理 edit) - 服务端:下载中心 limit-export 任务与产品档案导出接口的 columnSet=limit 都按「限价管理 · 下载」鉴权 - 前端:无下载权限不展示导出入口(限价明细两个导出、破价订单导出当前筛选/乐捐明细);规则配置仍单独走 breachRuleConfig 节点 - 申诉保留兜底:破价单负责人本人即使无「操作」权限也可申诉本人订单,审核必须「操作」 - 测试:CustomTableView 补导出入口权限用例并修正过期的表头断言;文档补充权限章节 --- .../docs/限价与破价判定规则.md | 20 +++++++++- .../src/app/api/downloads/route.ts | 5 ++- .../product-archive/export/route.ts | 18 +++++++-- .../custom-table/BreachOrderTab.tsx | 10 +++-- .../custom-table/CustomTableView.test.tsx | 12 ++++++ .../custom-table/CustomTableView.tsx | 16 ++++++-- .../management/custom-table/LimitPriceTab.tsx | 38 ++++++++++--------- .../management/custom-table/page.tsx | 9 ++++- .../supply-chain-breach-order.service.ts | 3 +- 9 files changed, 98 insertions(+), 33 deletions(-) diff --git a/furniture-3d-system/docs/限价与破价判定规则.md b/furniture-3d-system/docs/限价与破价判定规则.md index 94259522..515880ec 100644 --- a/furniture-3d-system/docs/限价与破价判定规则.md +++ b/furniture-3d-system/docs/限价与破价判定规则.md @@ -39,7 +39,23 @@ 5. **乐捐**:驳回计 ¥10/单(离职不计);配套审计链(数据快照 + 申诉材料 + 审核记录),扣款执行交财务确认,保留异议通道。 6. **举证期限**:待与运营确认后设置(避免事后无法追溯凭证)。 -## 三、生成与运行 +## 三、权限(由管理员统一管理) + +路径:用户管理后台 → 权限配置 → 经营管理。限价管理相关的查看 / 导出 / 申诉都由管理员在这里开通。 + +| 能力 | 权限节点 | 动作 | +| --- | --- | --- | +| 页面与数据查看(限价明细、破价订单审核) | 限价管理 | 可见 | +| 导出(导出明细 / 导出明细(含图片)/ 破价订单导出 / 乐捐明细) | 限价管理 | 下载 | +| 申诉 · 审核 · 生成 · 导入 | 限价管理 | 操作 | +| 规则配置(页面可见 + 保存参数) | 限价管理 · 规则配置 | 可见 / 操作 | + +- 没有「下载」权限时页面不展示导出入口,服务端导出接口同样鉴权(下载中心任务按「限价管理 · 下载」校验)。 +- 没有「操作」权限时:不显示申诉/审核按钮,服务端也会拒绝申诉、审核、生成、导入。 +- 申诉保留一条兜底:破价单的负责人本人,即使没有「操作」权限,也可以对本人负责的订单发起申诉(审核仍需「操作」)。 +- 没有「规则配置」权限时,规则配置页签不显示、接口不可调用。 + +## 四、生成与运行 1. **按天增量**:限价逻辑上线后,破价订单**按天增量生成**(每天只生成最新一天的破价单并入库),**历史数据不再重复回算**;破价订单审核页每天展示最新破价单。 2. **数据来源**:聚水潭报表库(`jst_Orders` + `jst_OrderItem` / 天猫奇门 `jst_qmcust_orderlist` + `_item`),取 `seller_income_amount`、`buyer_paid_amount`、`qty`、`shop_i_id`、`shop_sku_id`、`pay_date`、退款状态,以及换货识别字段 `item_status`、`item_ext_data`(`item_source`、`sku_uuid`)。报表库查询较慢,逐日增量任务建议按天分片拉取。 @@ -69,7 +85,7 @@ 写入规则:同货号多流程取**审核通过时间最新**的一条;限价为 0 或空的明细剔除;「申请与SPU负责人同价 / 活动限价」不覆盖负责人限价,只作为价格授权;流程「货号」列若填的是商品编码,用商品编码匹配产品档案换算为货号;无货号时同样用商品编码匹配。页面限价的时间按**北京时间墙钟**存(与流程完成时间一致)。 4. **覆盖率**:暂不做未匹配清单与告警(后续优化)。 -## 四、待办 +## 五、待办 - [ ] 限价流程数据接入 + 筛选逻辑(生效时间版本、货号维度) - [ ] 非负责人限价 10-07 规则、负责人触发规则(前两个月同价 / 两个月后按销量)落到判定引擎 diff --git a/furniture-3d-system/src/app/api/downloads/route.ts b/furniture-3d-system/src/app/api/downloads/route.ts index a1bc1347..6558428f 100644 --- a/furniture-3d-system/src/app/api/downloads/route.ts +++ b/furniture-3d-system/src/app/api/downloads/route.ts @@ -22,12 +22,13 @@ export async function POST(request: NextRequest) { } const params = (body?.params && typeof body.params === 'object' ? body.params : {}) as Record - // 任务级权限:产品档案 / 限价明细两类导出都复用产品全量档案的导出权限 + // 任务级权限:产品档案导出走「产品全量档案 · 下载」,限价基础信息导出走「限价管理 · 下载」 if (type === 'archive-export' || type === 'limit-export') { const { supplyChainPermissionSettingsService } = await import( '@/server/services/supply-chain-permission-settings.service' ) - const access = await supplyChainPermissionSettingsService.getUserAccess(ctx.user, 'productArchive') + const target = type === 'limit-export' ? 'limitManagement' : 'productArchive' + const access = await supplyChainPermissionSettingsService.getUserAccess(ctx.user, target) if (!access.download) { return jsonError(ErrorCode.AUTH_PERMISSION_DENIED, '当前账号没有导出权限', 403, ctx.requestId) } diff --git a/furniture-3d-system/src/app/api/supply-chain/product-archive/export/route.ts b/furniture-3d-system/src/app/api/supply-chain/product-archive/export/route.ts index 783a20d3..ffba3834 100644 --- a/furniture-3d-system/src/app/api/supply-chain/product-archive/export/route.ts +++ b/furniture-3d-system/src/app/api/supply-chain/product-archive/export/route.ts @@ -65,7 +65,12 @@ async function createExportResponse(params: { export async function GET(request: NextRequest) { return handleRequest(request, async (ctx) => { - const access = await supplyChainPermissionSettingsService.getUserAccess(ctx.user, 'productArchive') + // 限价界面的导出按「限价管理 · 下载」鉴权;产品档案导出仍按「产品全量档案 · 下载」。 + const isLimitExport = request.nextUrl.searchParams.get('columnSet') === 'limit' + const access = await supplyChainPermissionSettingsService.getUserAccess( + ctx.user, + isLimitExport ? 'limitManagement' : 'productArchive', + ) if (!access.download) { return jsonError(ErrorCode.AUTH_PERMISSION_DENIED, '当前账号没有导出权限', 403, ctx.requestId) } @@ -82,7 +87,7 @@ export async function GET(request: NextRequest) { // 默认含图片列;includeImage=0 时导出不含图片的明细(限价明细「导出明细」用)。 includeImage: request.nextUrl.searchParams.get('includeImage') !== '0', // 限价管理页导出用「限价界面抬头」列单。 - columnSet: request.nextUrl.searchParams.get('columnSet') === 'limit' ? 'limit' : 'archive', + columnSet: isLimitExport ? 'limit' : 'archive', requestId: ctx.requestId, signal: request.signal, }) @@ -93,12 +98,17 @@ export async function GET(request: NextRequest) { // that would exceed the URL length limit. export async function POST(request: NextRequest) { return handleRequest(request, async (ctx) => { - const access = await supplyChainPermissionSettingsService.getUserAccess(ctx.user, 'productArchive') + const body = await request.json().catch(() => null) as Record | null + // 限价界面的导出按「限价管理 · 下载」鉴权;产品档案导出仍按「产品全量档案 · 下载」。 + const isLimitExport = body?.columnSet === 'limit' + const access = await supplyChainPermissionSettingsService.getUserAccess( + ctx.user, + isLimitExport ? 'limitManagement' : 'productArchive', + ) if (!access.download) { return jsonError(ErrorCode.AUTH_PERMISSION_DENIED, '当前账号没有导出权限', 403, ctx.requestId) } - const body = await request.json().catch(() => null) as Record | null const filters = parseProductArchiveFiltersPayload(body?.filters ?? body ?? null) if (!filters.success) { return jsonError(ErrorCode.VALIDATION_INVALID_FORMAT, '筛选条件格式不正确', 400, ctx.requestId) diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/BreachOrderTab.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/BreachOrderTab.tsx index c90d0bba..a790188a 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/BreachOrderTab.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/BreachOrderTab.tsx @@ -160,7 +160,7 @@ function MultiFilterChip({ ) } -export function BreachOrderTab() { +export function BreachOrderTab({ canExport = false }: { canExport?: boolean }) { const [statusTab, setStatusTab] = useState('') const [payload, setPayload] = useState(null) const [loading, setLoading] = useState(true) @@ -645,8 +645,12 @@ export function BreachOrderTab() { - - + {canExport && ( + <> + + + + )} {hasActiveFilters && ( )} diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx index b24d1462..41c31e94 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.test.tsx @@ -213,6 +213,18 @@ describe('限价管理', () => { }) }) + it('导出入口由「限价管理 · 下载」权限控制:无权限不展示导出按钮', async () => { + const withoutExport = render() + await screen.findByText('测试岩板餐桌') + expect(screen.queryByRole('button', { name: '导出明细' })).not.toBeInTheDocument() + withoutExport.unmount() + + render() + await screen.findByText('测试岩板餐桌') + expect(screen.getByRole('button', { name: '导出明细' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: '导出明细(含图片)' })).toBeInTheDocument() + }) + it('限价页按《限价抬头及逻辑》显示分组表头,并展示产品全量档案数据', async () => { render() diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.tsx index 43e1bf6a..cacb7412 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/CustomTableView.tsx @@ -6,7 +6,17 @@ import { LimitPriceTab } from './LimitPriceTab' import { BreachOrderTab } from './BreachOrderTab' import { RuleConfigTab } from './RuleConfigTab' -export function CustomTableView({ userId, canViewRuleConfig = false }: { canEdit: boolean; userId: string; canViewRuleConfig?: boolean }) { +export function CustomTableView({ + userId, + canExport = false, + canViewRuleConfig = false, +}: { + canEdit: boolean + userId: string + /** 「限价管理 · 下载」:没有下载权限时不展示导出入口 */ + canExport?: boolean + canViewRuleConfig?: boolean +}) { const [primaryTab, setPrimaryTab] = useState<'limits' | 'breach' | 'rules'>('limits') return ( @@ -32,8 +42,8 @@ export function CustomTableView({ userId, canViewRuleConfig = false }: { canEdit )} - {primaryTab === 'limits' && } - {primaryTab === 'breach' && } + {primaryTab === 'limits' && } + {primaryTab === 'breach' && } {primaryTab === 'rules' && canViewRuleConfig && } ) diff --git a/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx b/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx index f406eadc..04fcb266 100644 --- a/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx +++ b/furniture-3d-system/src/app/supply-chain/management/custom-table/LimitPriceTab.tsx @@ -132,7 +132,7 @@ function LimitCell({ return {value} } -export function LimitPriceTab({ userId }: { userId: string }) { +export function LimitPriceTab({ userId, canExport = false }: { userId: string; canExport?: boolean }) { const { settings, hydrated, @@ -440,22 +440,26 @@ export function LimitPriceTab({ userId }: { userId: string }) { > - - + {canExport && ( + <> + + + + )} {exportMessage ? {exportMessage} : null} + return ( + + ) } diff --git a/furniture-3d-system/src/server/services/supply-chain-breach-order.service.ts b/furniture-3d-system/src/server/services/supply-chain-breach-order.service.ts index adf3c300..b4260915 100644 --- a/furniture-3d-system/src/server/services/supply-chain-breach-order.service.ts +++ b/furniture-3d-system/src/server/services/supply-chain-breach-order.service.ts @@ -81,7 +81,8 @@ function eachDay(dateFrom: string, dateTo: string) { class SupplyChainBreachOrderService { async resolveViewer(user: AuthUser): Promise { - const access = await supplyChainPermissionSettingsService.getUserAccess(user, 'businessManagement') + // 审核 / 申诉 / 生成 / 导入 统一由「限价管理 · 操作(edit)」控制,管理员在权限配置里开通 + const access = await supplyChainPermissionSettingsService.getUserAccess(user, 'limitManagement') return { id: user.id, name: user.name, canReview: Boolean(access.edit) } }