From a7f60414a45506b220f585577042ba15fe113c2d Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 23 Oct 2026 11:44:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(finance):=20=E5=AE=A2=E6=9C=8D=E5=8F=AA?= =?UTF-8?q?=E8=83=BD=E4=BD=BF=E7=94=A8=E8=87=AA=E5=B7=B1=E7=9A=84=E5=A4=87?= =?UTF-8?q?=E7=94=A8=E9=87=91=E8=B4=A6=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/admin/admin-fund-service.ts | 17 +++-- .../finance/manual-expense-service.ts | 27 +++++-- .../components/admin/ManualOrderCostModal.tsx | 62 ++++++++++------ .../pages/admin/AdminManualExpensePage.tsx | 70 +++++++++++++------ docs/人工支出登记落地方案-2026-10-17.md | 5 +- docs/成本中心与三块履约模型.md | 2 +- 6 files changed, 126 insertions(+), 57 deletions(-) diff --git a/apps/backend/src/services/admin/admin-fund-service.ts b/apps/backend/src/services/admin/admin-fund-service.ts index 473ac27a..3bc5fd75 100644 --- a/apps/backend/src/services/admin/admin-fund-service.ts +++ b/apps/backend/src/services/admin/admin-fund-service.ts @@ -1507,19 +1507,24 @@ export async function createAdminFundReturn(payload: JsonObject, session: AdminS }) } +export async function getOwnAgentAccountId(userId: number): Promise { + const own = await query( + "SELECT * FROM fund_accounts WHERE owner_admin_user_id = $1 AND account_type = 'agent' AND status <> 'closed' ORDER BY id LIMIT 1", + [userId], + ) + return own.rows[0] ? Number(own.rows[0].id) : 0 +} + async function resolveAgentAccountId(payload: JsonObject, session: AdminSession): Promise { if (session.role === 'support') { - const own = await query( - "SELECT * FROM fund_accounts WHERE owner_admin_user_id = $1 AND account_type = 'agent' LIMIT 1", - [session.userId], - ) - if (!own.rows[0]) { + const ownAccountId = await getOwnAgentAccountId(session.userId) + if (!ownAccountId) { throw createHttpError('当前账号还没有备用金账户', { statusCode: 404, errorCode: 'fund_agent_account_not_found', }) } - return Number(own.rows[0].id) + return ownAccountId } const accountId = toId(payload.agentAccountId) diff --git a/apps/backend/src/services/finance/manual-expense-service.ts b/apps/backend/src/services/finance/manual-expense-service.ts index 78d3ef01..095d6c7d 100644 --- a/apps/backend/src/services/finance/manual-expense-service.ts +++ b/apps/backend/src/services/finance/manual-expense-service.ts @@ -7,7 +7,7 @@ import { resolveFinanceDateRange } from '../../utils/finance-date-range.js' import { generateManualExpenseNo } from '../../utils/random.js' import { nowIso } from '../../utils/time.js' import type { AdminSession } from '../admin/admin-auth-service.js' -import { writeFundExpense } from '../admin/admin-fund-service.js' +import { getOwnAgentAccountId, writeFundExpense } from '../admin/admin-fund-service.js' const EXPENSE_STATUSES = new Set([ 'draft', @@ -372,9 +372,26 @@ export async function createManualExpense(payload: JsonObject, session: AdminSes inferExpenseCategory(costItem?.costType), '支出分类无效', ) - const fundAccountId = normalizeOptionalId( + const requestedFundAccountId = normalizeOptionalId( payload.fundAccountId ?? payload.fund_account_id ?? payload.agentAccountId, ) + let fundAccountId = requestedFundAccountId + if (session.role === 'support') { + const ownAccountId = await getOwnAgentAccountId(session.userId) + if (!ownAccountId) { + throw createHttpError('当前账号还没有备用金账户', { + statusCode: 404, + errorCode: 'fund_agent_account_not_found', + }) + } + if (requestedFundAccountId && requestedFundAccountId !== ownAccountId) { + throw createHttpError('只能使用自己的备用金账户', { + statusCode: 403, + errorCode: 'manual_expense_fund_account_forbidden', + }) + } + fundAccountId = ownAccountId + } if (!fundAccountId) { throw createHttpError('请选择备用金账户,人工支出必须从备用金扣款', { statusCode: 400, @@ -462,10 +479,10 @@ export async function createManualExpense(payload: JsonObject, session: AdminSes export async function createManualOrderCost(payload: JsonObject, session: AdminSession) { const orderId = normalizeId(payload.orderId ?? payload.order_id) const requestedItemId = normalizeOptionalId(payload.orderItemId ?? payload.order_item_id) - const fundAccountId = normalizeOptionalId( + const requestedFundAccountId = normalizeOptionalId( payload.fundAccountId ?? payload.fund_account_id ?? payload.agentAccountId, ) - if (!fundAccountId) { + if (!requestedFundAccountId && session.role !== 'support') { throw createHttpError('请选择备用金账户,人工录单必须从备用金扣款', { statusCode: 400, errorCode: 'manual_order_cost_fund_account_required', @@ -535,7 +552,7 @@ export async function createManualOrderCost(payload: JsonObject, session: AdminS orderId, orderItemId, costItemId, - fundAccountId, + fundAccountId: requestedFundAccountId, amountFen, expenseCategory, payeeName, diff --git a/apps/frontend/src/components/admin/ManualOrderCostModal.tsx b/apps/frontend/src/components/admin/ManualOrderCostModal.tsx index cf8fbb1b..296d1141 100644 --- a/apps/frontend/src/components/admin/ManualOrderCostModal.tsx +++ b/apps/frontend/src/components/admin/ManualOrderCostModal.tsx @@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useEffect, useState } from 'react' import { createAdminManualOrderCost, fetchAdminFundAccounts } from '@/services/admin' -import { hasAdminPermission } from '@/utils/admin-auth' +import { getAdminRole, hasAdminPermission } from '@/utils/admin-auth' export type ManualOrderCostItem = { orderItemId: number @@ -77,6 +77,8 @@ export default function ManualOrderCostModal({ }) const fundAccounts = fundAccountsQuery.data?.data.items || [] const soleFundAccountId = fundAccounts.length === 1 ? fundAccounts[0]?.id : undefined + const isSupport = getAdminRole() === 'support' + const ownFundAccount = isSupport ? fundAccounts[0] : undefined useEffect(() => { if (!open) return @@ -161,25 +163,45 @@ export default function ManualOrderCostModal({ - - + + + + ) : ( + + ({ - value: account.id, - label: `${account.ownerName} · ${account.accountNo} · 余额 ${formatMoney( - account.balanceFen, - )}`, - }))} - /> - + {isSupport ? ( + <> + + + + + + ) : ( + +