Files
PayrollManagementSystem/frontend/src/api/businesses.ts
T
liuwenxinandClaude Code 4c32a556de feat(approval): 审批单内容快照与二次分配方案版本化
补齐 code-review 遗留 2 项:审批单此前只固化 ref_id/refer_no 指针,复核者看到
的是业务当前内容;二次分配方案在编辑替换与驳回清空时一律物理删除,方案事实
无处回溯。

- 审批单加 version_no 与 snapshot_json(信封 schema_version/order_type/
  captured_at/data);二次分配、月结、调整单三类提交均固化送审内容,开关关闭
  的自动通过路径同样写快照;详情按 snapshot/live/missing 三态回显,历史单
  只读回退当前内容并显式告警,不回填历史快照
- 二次分配方案行改 append-only 版本化(version_no/is_active,唯一键扩为
  business+version+personnel):编辑替换与驳回清空只置历史,核算与详情默认
  只取生效行,业务详情新增 allocation_history
- 送审内容未变不重提、方案未变不新增版本:判据统一为送审内容,两个
  version_no 语义解耦(方案版本=内容第几版,审批单版本=第几次送审)
- 审批单列表、工作台待办、审批超时预警三处 defer 快照列,避免整列拉取
- 月结提交取行加 for_update,防并发提交算出重复的审批单版本号
- 修复审批类型筛选上界 le=2(调整单无法筛选)、快照金额把空值写成 0.00
- V11 迁移三件套同步(Alembic / db/migration / init-dev.sql)

测试:单元 +16(方案内容比较 10、审批入参 6)、集成 +12;全量 891 passed。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:30:51 +08:00

321 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { request } from './request'
import type { PageData } from './types'
/**
* 业务产品明细行(对应后端 `BusinessItemOut`)。
*
* 数量与金额一律字符串直出 —— 金额红线:不经 float、不 toFixed,
* Decimal 运算收敛在后端 service 层。
*/
export interface BusinessItemRow {
id: number
product_id: number | null
product_name: string
spec_model: string | null
unit: string | null
/** municipal_water 市政水利 / rail_transit 轨道交通 —— 行级折算锚点 */
product_category: string
/** 数字字符串,>0 */
quantity: string
sale_price: string | null
/** 行销售额 = 数量 × 销售单价,后端计算固化 */
sale_amount: string
cost_amount: string | null
guided_price: string | null
remark: string | null
}
/** 业务列表项(对应后端 `BusinessOut`;四个汇总金额均为字符串直出) */
export interface BusinessItem {
id: number
business_no: string
customer_id: number
customer_name: string | null
/** ISO 串(YYYY-MM-DD) */
business_date: string | null
/** 1执行中 2已完成 3已终止 */
status: number
owner_personnel_id: number | null
owner_personnel_name: string | null
assistant_personnel_id: number | null
assistant_personnel_name: string | null
/** 特殊业务标记:1是 0否 */
is_special: number
special_note: string | null
total_sale_amount: string | null
total_cost_amount: string | null
total_ship_amount: string | null
total_received_amount: string | null
total_freight_amount: string | null
remark: string | null
}
/** 二次分配参与人(2026-10-15 补充需求;比例不强制合计 100%) */
export interface SecondaryAllocationRow {
personnel_id: number
personnel_name: string
/** 百分比数字字符串(两位小数) */
ratio: string
}
/** 方案版本行(append-only;编辑替换/驳回清空后旧行仍保留为历史) */
export interface AllocationVersionRow extends SecondaryAllocationRow {
/** 方案版本号(同一业务从 1 递增) */
version_no: number
/** 1 当前生效 0 历史版本 */
is_active: number
create_time: string
}
/** 业务详情:附产品明细行、当前方案与方案版本历史(编辑弹窗一次回显) */
export interface BusinessDetail extends BusinessItem {
items: BusinessItemRow[]
secondary_allocations: SecondaryAllocationRow[]
/** 方案合计/余额提示(余额归属待澄清 #2,仅展示) */
allocation_hint: string | null
/** 方案全部版本(含历史;被驳回方案亦保留) */
allocation_history: AllocationVersionRow[]
}
export interface BusinessQueryParams {
pageNum?: number
pageSize?: number
/** 业务编号/公司名称,模糊匹配 */
keyword?: string
status?: number
customer_id?: number
owner_personnel_id?: number
/** 业务日期起(含),ISO 串 */
date_from?: string
/** 业务日期止(含),ISO 串 */
date_to?: string
}
/** 明细行入参(product_id 空 = 自由录入;金额/数量为数字字符串) */
export interface BusinessItemParams {
product_id?: number | null
product_name: string
spec_model?: string | null
unit?: string | null
product_category: string
quantity: string
sale_price?: string | null
cost_amount?: string | null
guided_price?: string | null
remark?: string | null
}
/**
* 创建业务:主表信息 + 明细行(≥1)。
*
* is_special=1 时 special_note 必填(后端校验,前端同步拦截)。
*/
export interface BusinessCreateParams {
customer_id: number
business_date?: string | null
owner_personnel_id?: number | null
assistant_personnel_id?: number | null
is_special?: number
special_note?: string | null
items: BusinessItemParams[]
/** 二次分配方案(只出现在合同刚建立时;空=不设置) */
secondary_allocations?: { personnel_id: number; ratio: string }[]
remark?: string | null
}
/**
* 编辑业务:明细全量替换并重算汇总。
*
* 不含归属字段 —— 归属调整统一走 `PUT /{id}/ownership`(唯一写归属
* 留痕的入口),编辑表单即使提交归属也会被后端忽略。
*/
export interface BusinessUpdateParams {
customer_id: number
business_date?: string | null
is_special?: number
special_note?: string | null
items: BusinessItemParams[]
/** 二次分配方案(全量替换;空数组=清空) */
secondary_allocations?: { personnel_id: number; ratio: string }[]
remark?: string | null
}
/** 状态流转入参:执行中 → 2已完成 / 3已终止;终止时 reason 必填 */
export interface BusinessStatusParams {
status: number
reason?: string | null
}
/** 归属变更入参(新负责/新协助;前后值写入留痕,不回溯历史) */
export interface BusinessOwnershipParams {
new_owner_personnel_id?: number | null
new_assistant_personnel_id?: number | null
reason?: string | null
}
/** 业务归属变更留痕条目(对应后端 `OwnershipChangeLogOut`;姓名可随人员消失,ID 固化) */
export interface OwnershipLogItem {
id: number
business_id: number
old_owner_personnel_id: number | null
new_owner_personnel_id: number | null
old_assistant_personnel_id: number | null
new_assistant_personnel_id: number | null
old_owner_name: string | null
new_owner_name: string | null
change_reason: string | null
operator_id: number | null
operator_name: string
create_time: string
}
/** 业务状态流转记录(对应后端 `BusinessStatusLogOut`;创建首条 old=new=1) */
export interface StatusLogItem {
id: number
business_id: number
old_status: number
new_status: number
change_reason: string | null
operator_id: number | null
operator_name: string
create_time: string
}
/** 毛利润视图行(对应后端 `GrossProfitRowOut`);成本缺失行派生列为 null */
export interface GrossProfitRowItem {
item_id: number
product_category: string
product_name: string
spec_model: string | null
quantity: string
sale_amount: string
cost_amount: string | null
profit_amount: string | null
profit_rate: string | null
threshold: string
converted_profit: string | null
missing_reason: string | null
}
/** 按产品类别的毛利润汇总(成本缺失行不计入聚合) */
export interface GrossProfitCategory {
product_category: string
sale_amount: string
cost_amount: string
profit_amount: string
/** 分数字符串原样(如 "997/1000"),不做数值化展示 */
profit_rate: string
threshold: string
converted_profit: string
}
/** 毛利润数据视图(对应后端 `GrossProfitOut`) */
export interface GrossProfitView {
business_id: number
/** 参数取值时点(默认今日),ISO 串 */
as_of: string
rows: GrossProfitRowItem[]
categories: GrossProfitCategory[]
missing_count: number
note: string
}
/** 业务状态下拉项(与后端值域一致;执行中=1 已完成=2 已终止=3) */
export const BUSINESS_STATUS_OPTIONS = [
{ label: '执行中', value: 1 },
{ label: '已完成', value: 2 },
{ label: '已终止', value: 3 },
] as const
/** 状态编码 → 展示名 */
export function businessStatusLabel(status: number): string {
return BUSINESS_STATUS_OPTIONS.find((o) => o.value === status)?.label ?? `状态${status}`
}
/** 状态编码 → 标签类型(三色:执行中 primary / 已完成 success / 已终止 danger) */
export function businessStatusTagType(status: number): 'primary' | 'success' | 'danger' | 'info' {
if (status === 2) return 'success'
if (status === 3) return 'danger'
return 'primary'
}
/** 分页查询业务(编号/公司名称模糊,可按状态/客户/归属/日期范围筛选) */
export function listBusinesses(params: BusinessQueryParams): Promise<PageData<BusinessItem>> {
return request({ url: '/v1/business/businesses', method: 'get', params })
}
/** 创建业务(编号自动生成,写首条状态留痕) */
export function createBusiness(data: BusinessCreateParams): Promise<BusinessItem> {
return request({ url: '/v1/business/businesses', method: 'post', data })
}
/** 业务详情(附产品明细行) */
export function getBusinessDetail(businessId: number): Promise<BusinessDetail> {
return request({ url: `/v1/business/businesses/${businessId}`, method: 'get' })
}
/** 编辑业务(明细全量替换;归属不在此端点调整) */
export function updateBusiness(
businessId: number,
data: BusinessUpdateParams,
): Promise<BusinessItem> {
return request({ url: `/v1/business/businesses/${businessId}`, method: 'put', data })
}
/** 删除业务(有发货或回款记录时后端拒绝,应改为已终止) */
export function deleteBusiness(businessId: number): Promise<null> {
return request({ url: `/v1/business/businesses/${businessId}`, method: 'delete' })
}
/** 状态流转(终态不可再变更;终止必填原因) */
export function changeBusinessStatus(
businessId: number,
data: BusinessStatusParams,
): Promise<BusinessItem> {
return request({ url: `/v1/business/businesses/${businessId}/status`, method: 'put', data })
}
/** 调整负责/协助营销人员(同事务写归属变更留痕) */
export function changeBusinessOwnership(
businessId: number,
data: BusinessOwnershipParams,
): Promise<BusinessItem> {
return request({ url: `/v1/business/businesses/${businessId}/ownership`, method: 'put', data })
}
/** 某业务的归属变更留痕(分页,按变更时间倒序) */
export function listOwnershipLogs(
businessId: number,
params: { pageNum?: number; pageSize?: number },
): Promise<PageData<OwnershipLogItem>> {
return request({
url: `/v1/business/businesses/${businessId}/ownership-logs`,
method: 'get',
params,
})
}
/** 某业务的状态流转时间线(分页,按时间正序,从创建起展示) */
export function listStatusLogs(
businessId: number,
params: { pageNum?: number; pageSize?: number },
): Promise<PageData<StatusLogItem>> {
return request({
url: `/v1/business/businesses/${businessId}/status-logs`,
method: 'get',
params,
})
}
/** 毛利润数据视图(as_of 缺省=今日;阈值按该时点参数快照) */
export function getGrossProfit(
businessId: number,
asOf?: string,
): Promise<GrossProfitView> {
return request({
url: `/v1/business/businesses/${businessId}/gross-profit`,
method: 'get',
params: asOf ? { as_of: asOf } : undefined,
})
}