补齐 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>
321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
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,
|
||
})
|
||
}
|