feat(finance): 完成成本快照运行层、销售事实与人工支出登记
迁移 108-111:快照业务时间与预估转实际、销售事实表、系统成本项、公司人工支出登记。接单平台按工单实际 reward 入账;人工支出支持登记/审批/驳回/报销并写 actual 快照。新增销售毛利页、人工支出页与订单入口。
This commit is contained in:
@@ -31,6 +31,10 @@ import {
|
||||
updateAdminProduct,
|
||||
} from '../../services/admin/admin-cost-center-service.js'
|
||||
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
|
||||
import {
|
||||
syncActualWorkOrderCostSnapshots,
|
||||
syncEstimatedOrderItemCostSnapshots,
|
||||
} from '../../services/cost-center/cost-snapshot-sync-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createJsonHandler, requireAdminPermission, requireAdminRoles } from './session.js'
|
||||
|
||||
@@ -490,4 +494,43 @@ router.post(
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/cost-center/snapshots/sync',
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
async (req) => {
|
||||
const body = bodyOf(req)
|
||||
const mode = String(body.mode || 'both').trim()
|
||||
const estimated = mode === 'actual' ? null : await syncEstimatedOrderItemCostSnapshots(body)
|
||||
const actual = mode === 'estimated' ? null : await syncActualWorkOrderCostSnapshots(body)
|
||||
return { mode, estimated, actual }
|
||||
},
|
||||
{
|
||||
successMessage: '成本快照回填完成',
|
||||
errorMessage: '回填成本快照失败',
|
||||
scope: '[admin/cost-center/snapshots/sync]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
mode?: string
|
||||
estimated?: { createdCount?: number; exceptionCreatedCount?: number } | null
|
||||
actual?: { createdCount?: number } | null
|
||||
}
|
||||
return {
|
||||
action: 'cost_snapshot_synced',
|
||||
targetType: 'cost_snapshot',
|
||||
targetId: '',
|
||||
data: {
|
||||
mode: result.mode || '',
|
||||
estimatedCreated: result.estimated?.createdCount || 0,
|
||||
actualCreated: result.actual?.createdCount || 0,
|
||||
exceptions: result.estimated?.exceptionCreatedCount || 0,
|
||||
dateFrom: req.body?.dateFrom || '',
|
||||
dateTo: req.body?.dateTo || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,13 +1,48 @@
|
||||
import type { Request } from 'express'
|
||||
import { Router } from 'express'
|
||||
|
||||
import {
|
||||
getAdminFinanceSummary,
|
||||
listAdminFinanceTransactions,
|
||||
} from '../../services/admin/admin-finance-service.js'
|
||||
import { createJsonHandler, requireAdminRoles } from './session.js'
|
||||
import type { AdminSession } from '../../services/admin/admin-auth-service.js'
|
||||
import {
|
||||
getSalesProfitOverview,
|
||||
listSalesDailyFacts,
|
||||
rebuildSalesDailyFacts,
|
||||
} from '../../services/finance/sales-facts-service.js'
|
||||
import {
|
||||
approveManualExpense,
|
||||
createManualExpense,
|
||||
getManualExpense,
|
||||
getManualExpenseOverview,
|
||||
listManualExpenseCostItems,
|
||||
listManualExpenses,
|
||||
rejectManualExpense,
|
||||
reimburseManualExpense,
|
||||
} from '../../services/finance/manual-expense-service.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import {
|
||||
createJsonHandler,
|
||||
requireAdminPermission,
|
||||
requireAdminRoles,
|
||||
requireAnyAdminPermission,
|
||||
} from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
function sessionOf(req: Request): AdminSession {
|
||||
return req.adminSession as AdminSession
|
||||
}
|
||||
|
||||
function idOf(req: Request, key: string) {
|
||||
return Number(req.params[key])
|
||||
}
|
||||
|
||||
function bodyOf(req: Request): JsonObject {
|
||||
return req.body && typeof req.body === 'object' ? (req.body as JsonObject) : {}
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/finance/summary',
|
||||
requireAdminRoles(['admin', 'operator', 'finance']),
|
||||
@@ -28,4 +63,179 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/sales-profit/overview',
|
||||
requireAdminRoles(['admin', 'operator', 'finance']),
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => getSalesProfitOverview(req.query as Record<string, unknown>), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取销售毛利失败',
|
||||
scope: '[admin/finance/sales-profit/overview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/sales-facts',
|
||||
requireAdminRoles(['admin', 'operator', 'finance']),
|
||||
requireAdminPermission('finance.view'),
|
||||
createJsonHandler((req) => listSalesDailyFacts(req.query as Record<string, unknown>), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取销售事实失败',
|
||||
scope: '[admin/finance/sales-facts]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/finance/sales-facts/rebuild',
|
||||
requireAdminRoles(['admin', 'operator', 'finance']),
|
||||
requireAdminPermission('finance.config'),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
rebuildSalesDailyFacts(
|
||||
req.body && typeof req.body === 'object' ? (req.body as Record<string, unknown>) : {},
|
||||
),
|
||||
{
|
||||
successMessage: '销售事实已重算',
|
||||
errorMessage: '重算销售事实失败',
|
||||
scope: '[admin/finance/sales-facts/rebuild]',
|
||||
audit: (req, data) => {
|
||||
const result = data as {
|
||||
range?: { fromDate?: string; toDate?: string }
|
||||
insertedCount?: number
|
||||
deletedCount?: number
|
||||
}
|
||||
return {
|
||||
action: 'sales_daily_facts_rebuilt',
|
||||
targetType: 'sales_daily_facts',
|
||||
targetId: '',
|
||||
data: {
|
||||
dateFrom: result.range?.fromDate || req.body?.dateFrom || '',
|
||||
dateTo: result.range?.toDate || req.body?.dateTo || '',
|
||||
insertedCount: result.insertedCount || 0,
|
||||
deletedCount: result.deletedCount || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/manual-expenses/overview',
|
||||
requireAnyAdminPermission(['finance.view', 'fund.view']),
|
||||
createJsonHandler((req) => getManualExpenseOverview(req.query as Record<string, unknown>), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工支出汇总失败',
|
||||
scope: '[admin/finance/manual-expenses/overview]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/manual-expenses',
|
||||
requireAnyAdminPermission(['finance.view', 'fund.view']),
|
||||
createJsonHandler((req) => listManualExpenses(req.query as Record<string, unknown>), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工支出失败',
|
||||
scope: '[admin/finance/manual-expenses]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/manual-expenses/cost-items',
|
||||
requireAnyAdminPermission(['finance.view', 'fund.view']),
|
||||
createJsonHandler(() => listManualExpenseCostItems(), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工支出成本项失败',
|
||||
scope: '[admin/finance/manual-expenses/cost-items]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/finance/manual-expenses/:expenseId',
|
||||
requireAnyAdminPermission(['finance.view', 'fund.view']),
|
||||
createJsonHandler((req) => getManualExpense(idOf(req, 'expenseId')), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取人工支出详情失败',
|
||||
scope: '[admin/finance/manual-expenses/:expenseId]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/finance/manual-expenses',
|
||||
requireAnyAdminPermission(['fund.expense', 'finance.config']),
|
||||
createJsonHandler((req) => createManualExpense(bodyOf(req), sessionOf(req)), {
|
||||
successMessage: '人工支出已登记',
|
||||
errorMessage: '登记人工支出失败',
|
||||
scope: '[admin/finance/manual-expenses]',
|
||||
audit: (req, data) => {
|
||||
const record = data as { id?: number; expenseNo?: string; amountFen?: number }
|
||||
return {
|
||||
action: 'manual_expense_created',
|
||||
targetType: 'manual_expense',
|
||||
targetId: String(record.id || ''),
|
||||
data: {
|
||||
expenseNo: record.expenseNo || '',
|
||||
amountFen: record.amountFen || 0,
|
||||
orderId: req.body?.orderId || '',
|
||||
orderItemId: req.body?.orderItemId || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/finance/manual-expenses/:expenseId/approve',
|
||||
requireAnyAdminPermission(['finance.review']),
|
||||
createJsonHandler(
|
||||
(req) => approveManualExpense(idOf(req, 'expenseId'), bodyOf(req), sessionOf(req)),
|
||||
{
|
||||
successMessage: '人工支出已通过',
|
||||
errorMessage: '审核人工支出失败',
|
||||
scope: '[admin/finance/manual-expenses/:expenseId/approve]',
|
||||
audit: (req, data) => ({
|
||||
action: 'manual_expense_approved',
|
||||
targetType: 'manual_expense',
|
||||
targetId: String((data as { id?: number })?.id || req.params.expenseId || ''),
|
||||
data: { note: req.body?.note || '' },
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/finance/manual-expenses/:expenseId/reject',
|
||||
requireAnyAdminPermission(['finance.review']),
|
||||
createJsonHandler(
|
||||
(req) => rejectManualExpense(idOf(req, 'expenseId'), bodyOf(req), sessionOf(req)),
|
||||
{
|
||||
successMessage: '人工支出已驳回',
|
||||
errorMessage: '驳回人工支出失败',
|
||||
scope: '[admin/finance/manual-expenses/:expenseId/reject]',
|
||||
audit: (req, data) => ({
|
||||
action: 'manual_expense_rejected',
|
||||
targetType: 'manual_expense',
|
||||
targetId: String((data as { id?: number })?.id || req.params.expenseId || ''),
|
||||
data: { reason: req.body?.rejectReason || req.body?.reject_reason || '' },
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/finance/manual-expenses/:expenseId/reimburse',
|
||||
requireAnyAdminPermission(['finance.review']),
|
||||
createJsonHandler((req) => reimburseManualExpense(idOf(req, 'expenseId'), sessionOf(req)), {
|
||||
successMessage: '人工支出已标记报销',
|
||||
errorMessage: '标记报销失败',
|
||||
scope: '[admin/finance/manual-expenses/:expenseId/reimburse]',
|
||||
audit: (req, data) => ({
|
||||
action: 'manual_expense_reimbursed',
|
||||
targetType: 'manual_expense',
|
||||
targetId: String((data as { id?: number })?.id || req.params.expenseId || ''),
|
||||
data: {},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user