From 06cdf4141f3c85113f20c6a22d2b0a037759661b Mon Sep 17 00:00:00 2001 From: liuwenxin <1009266660@qq.com> Date: Tue, 13 Oct 2026 12:50:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(business):=20=E5=89=8D=E7=AB=AF=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1/=E5=8F=91=E8=B4=A7/=E5=9B=9E=E6=AC=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=89=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 业务页:明细行编辑器、详情抽屉五分区(含状态时间线与毛利润卡片) - 回款页:业务→发货批次级联指定(可清空)、已核算行禁改禁删 - 金额/比率字符串直出;毛利润卡片注明不含运费口径 Co-Authored-By: Claude Code --- frontend/src/api/businesses.ts | 294 ++++ frontend/src/api/receipts.ts | 104 ++ frontend/src/api/shipments.ts | 117 ++ frontend/src/router/index.ts | 19 + .../src/views/business/businesses/index.vue | 1309 +++++++++++++++++ .../src/views/business/receipts/index.vue | 508 +++++++ .../src/views/business/shipments/index.vue | 497 +++++++ 7 files changed, 2848 insertions(+) create mode 100644 frontend/src/api/businesses.ts create mode 100644 frontend/src/api/receipts.ts create mode 100644 frontend/src/api/shipments.ts create mode 100644 frontend/src/views/business/businesses/index.vue create mode 100644 frontend/src/views/business/receipts/index.vue create mode 100644 frontend/src/views/business/shipments/index.vue diff --git a/frontend/src/api/businesses.ts b/frontend/src/api/businesses.ts new file mode 100644 index 0000000..f9c10cf --- /dev/null +++ b/frontend/src/api/businesses.ts @@ -0,0 +1,294 @@ +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 +} + +/** 业务详情:附产品明细行(编辑弹窗一次回显) */ +export interface BusinessDetail extends BusinessItem { + items: BusinessItemRow[] +} + +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[] + 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[] + 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> { + return request({ url: '/v1/business/businesses', method: 'get', params }) +} + +/** 创建业务(编号自动生成,写首条状态留痕) */ +export function createBusiness(data: BusinessCreateParams): Promise { + return request({ url: '/v1/business/businesses', method: 'post', data }) +} + +/** 业务详情(附产品明细行) */ +export function getBusinessDetail(businessId: number): Promise { + return request({ url: `/v1/business/businesses/${businessId}`, method: 'get' }) +} + +/** 编辑业务(明细全量替换;归属不在此端点调整) */ +export function updateBusiness( + businessId: number, + data: BusinessUpdateParams, +): Promise { + return request({ url: `/v1/business/businesses/${businessId}`, method: 'put', data }) +} + +/** 删除业务(有发货或回款记录时后端拒绝,应改为已终止) */ +export function deleteBusiness(businessId: number): Promise { + return request({ url: `/v1/business/businesses/${businessId}`, method: 'delete' }) +} + +/** 状态流转(终态不可再变更;终止必填原因) */ +export function changeBusinessStatus( + businessId: number, + data: BusinessStatusParams, +): Promise { + return request({ url: `/v1/business/businesses/${businessId}/status`, method: 'put', data }) +} + +/** 调整负责/协助营销人员(同事务写归属变更留痕) */ +export function changeBusinessOwnership( + businessId: number, + data: BusinessOwnershipParams, +): Promise { + return request({ url: `/v1/business/businesses/${businessId}/ownership`, method: 'put', data }) +} + +/** 某业务的归属变更留痕(分页,按变更时间倒序) */ +export function listOwnershipLogs( + businessId: number, + params: { pageNum?: number; pageSize?: number }, +): Promise> { + return request({ + url: `/v1/business/businesses/${businessId}/ownership-logs`, + method: 'get', + params, + }) +} + +/** 某业务的状态流转时间线(分页,按时间正序,从创建起展示) */ +export function listStatusLogs( + businessId: number, + params: { pageNum?: number; pageSize?: number }, +): Promise> { + return request({ + url: `/v1/business/businesses/${businessId}/status-logs`, + method: 'get', + params, + }) +} + +/** 毛利润数据视图(as_of 缺省=今日;阈值按该时点参数快照) */ +export function getGrossProfit( + businessId: number, + asOf?: string, +): Promise { + return request({ + url: `/v1/business/businesses/${businessId}/gross-profit`, + method: 'get', + params: asOf ? { as_of: asOf } : undefined, + }) +} diff --git a/frontend/src/api/receipts.ts b/frontend/src/api/receipts.ts new file mode 100644 index 0000000..3dbb437 --- /dev/null +++ b/frontend/src/api/receipts.ts @@ -0,0 +1,104 @@ +import { request } from './request' +import type { PageData } from './types' + +/** + * 回款列表项(对应后端 `ReceiptOut`;字段契约纯蛇形直出)。 + * + * amount 字符串直出(金额红线);payment_days 为派生值:未指定发货 + * 批次时为 null,指定后 = 发货日到回款日的天数(发货日计第 1 天)。 + */ +export interface ReceiptItem { + id: number + receipt_batch_no: string + business_id: number + business_no: string + shipment_id: number | null + shipment_no: string | null + /** 回款到账日,ISO 串(YYYY-MM-DD);M3 核算按此日匹配参数版本 */ + receipt_date: string + /** 回款金额(元),字符串直出 */ + amount: string + /** 以物抵顶标记:1是 0否(绩效按比例折算) */ + is_barter: number + /** 核算状态:0未核算 1已核算(已核算禁改禁删) */ + settlement_status: number + payment_days: number | null + remark: string | null +} + +export interface ReceiptQueryParams { + pageNum?: number + pageSize?: number + business_id?: number + /** 回款到账日起(含),ISO 串 */ + date_from?: string + /** 回款到账日止(含),ISO 串 */ + date_to?: string + is_barter?: number + settlement_status?: number +} + +/** + * 新增回款:业务必选,发货批次可暂不指定(手动指定)。 + * + * shipment_id 须与本回款同属一个业务(后端强校验,数据完整性红线)。 + */ +export interface ReceiptCreateParams { + business_id: number + shipment_id?: number | null + receipt_date: string + /** 数字字符串,非负,最多两位小数 */ + amount: string + is_barter?: number + remark?: string | null +} + +/** + * 编辑回款:全量替换语义;不含 business_id(挂靠业务创建时定死)。 + * + * shipment_id 传 null = 清除指定(回到「暂未指定」)。 + */ +export interface ReceiptUpdateParams { + shipment_id?: number | null + receipt_date: string + amount: string + is_barter?: number + remark?: string | null +} + +/** 以物抵顶筛选项 */ +export const BARTER_OPTIONS = [ + { label: '以物抵顶', value: 1 }, + { label: '货币回款', value: 0 }, +] as const + +/** 核算状态筛选项(与列表标签文案一致) */ +export const SETTLEMENT_OPTIONS = [ + { label: '已核算', value: 1 }, + { label: '未核算', value: 0 }, +] as const + +/** 分页查询回款(附业务号/发货单号与回款天数派生值) */ +export function listReceipts(params: ReceiptQueryParams): Promise> { + return request({ url: '/v1/business/receipts', method: 'get', params }) +} + +/** 新增回款(批次号自动生成 HK 前缀;业务须执行中) */ +export function createReceipt(data: ReceiptCreateParams): Promise { + return request({ url: '/v1/business/receipts', method: 'post', data }) +} + +/** 回款详情(含回款天数派生值) */ +export function getReceiptDetail(receiptId: number): Promise { + return request({ url: `/v1/business/receipts/${receiptId}`, method: 'get' }) +} + +/** 编辑回款(全量替换;已核算回款后端拒绝) */ +export function updateReceipt(receiptId: number, data: ReceiptUpdateParams): Promise { + return request({ url: `/v1/business/receipts/${receiptId}`, method: 'put', data }) +} + +/** 删除回款(已核算/业务非执行中时后端拒绝;业务回款合计自动回退) */ +export function deleteReceipt(receiptId: number): Promise { + return request({ url: `/v1/business/receipts/${receiptId}`, method: 'delete' }) +} diff --git a/frontend/src/api/shipments.ts b/frontend/src/api/shipments.ts new file mode 100644 index 0000000..7feb2eb --- /dev/null +++ b/frontend/src/api/shipments.ts @@ -0,0 +1,117 @@ +import { request } from './request' +import type { PageData } from './types' + +/** + * 发货产品明细行(对应后端 `ShipmentItemOut`)。 + * + * 数量/单价/行金额一律字符串直出 —— 金额红线:不经 float、不 toFixed; + * 行金额 = 数量 × 单价由后端计算固化,不信任前端传值。 + */ +export interface ShipmentItemRow { + id: number + product_id: number | null + product_name: string + spec_model: string | null + unit: string | null + /** 数字字符串,>0 */ + quantity: string + /** 发货单价,可空 = 待人工补价(补价前行金额记 0) */ + sale_price: string | null + /** 行金额,后端按数量×单价计算固化 */ + amount: string +} + +/** 发货单列表项(对应后端 `ShipmentOut`;business_no 为联表附带列) */ +export interface ShipmentItem { + id: number + shipment_no: string + business_id: number + business_no: string | null + /** ISO 串(YYYY-MM-DD);发货日是回款天数计算基准日(发货日计第 1 天) */ + ship_date: string + /** 运费(元),仅记录展示,不计入毛利润口径 */ + freight_amount: string + /** 发货总金额,后端按明细固化 */ + total_amount: string + remark: string | null +} + +/** 发货单详情:附产品明细行(编辑弹窗一次回显) */ +export interface ShipmentDetail extends ShipmentItem { + items: ShipmentItemRow[] +} + +export interface ShipmentQueryParams { + pageNum?: number + pageSize?: number + /** 业务筛选,空 = 全部 */ + business_id?: number + /** 发货日期起(含),ISO 串 */ + ship_date_from?: string + /** 发货日期止(含),ISO 串 */ + ship_date_to?: string +} + +/** 发货明细行入参(product_id 空 = 自由录入;单价可空=待人工补价) */ +export interface ShipmentItemParams { + product_id?: number | null + product_name: string + spec_model?: string | null + unit?: string | null + quantity: string + sale_price?: string | null +} + +/** + * 创建发货单:业务维度 + 明细行(≥1)。 + * + * 业务须处于执行中(后端校验);ship_date 为回款天数计算基准日。 + */ +export interface ShipmentCreateParams { + business_id: number + ship_date: string + freight_amount?: string | null + items: ShipmentItemParams[] + remark?: string | null +} + +/** + * 编辑发货单:明细全量替换。 + * + * 不含 business_id —— 发货单挂靠的业务创建时定死,改挂会让已指定 + * 本发货单的回款语义断裂。 + */ +export interface ShipmentUpdateParams { + ship_date: string + freight_amount?: string | null + items: ShipmentItemParams[] + remark?: string | null +} + +/** 分页查询发货单(附业务编号,可按业务/发货日期范围筛选) */ +export function listShipments(params: ShipmentQueryParams): Promise> { + return request({ url: '/v1/business/shipments', method: 'get', params }) +} + +/** 创建发货单(明细 ≥1;业务须执行中) */ +export function createShipment(data: ShipmentCreateParams): Promise { + return request({ url: '/v1/business/shipments', method: 'post', data }) +} + +/** 发货单详情(附产品明细行) */ +export function getShipmentDetail(shipmentId: number): Promise { + return request({ url: `/v1/business/shipments/${shipmentId}`, method: 'get' }) +} + +/** 编辑发货单(明细全量替换;终态业务的发货单后端拒绝) */ +export function updateShipment( + shipmentId: number, + data: ShipmentUpdateParams, +): Promise { + return request({ url: `/v1/business/shipments/${shipmentId}`, method: 'put', data }) +} + +/** 删除发货单(连带明细并回写业务汇总;被回款批次指定时后端拒绝) */ +export function deleteShipment(shipmentId: number): Promise { + return request({ url: `/v1/business/shipments/${shipmentId}`, method: 'delete' }) +} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index b8f8836..b3fb951 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -89,6 +89,25 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/base/customers/index.vue'), meta: { title: '客户管理', group: '基础数据', order: 10 }, }, + { + // 业务数据组一律为业务操作:登录即可访问,不设 requiresAdmin + path: '/business/businesses', + name: 'business-businesses', + component: () => import('@/views/business/businesses/index.vue'), + meta: { title: '业务管理', group: '业务数据', order: 11 }, + }, + { + path: '/business/shipments', + name: 'business-shipments', + component: () => import('@/views/business/shipments/index.vue'), + meta: { title: '发货管理', group: '业务数据', order: 12 }, + }, + { + path: '/business/receipts', + name: 'business-receipts', + component: () => import('@/views/business/receipts/index.vue'), + meta: { title: '回款管理', group: '业务数据', order: 13 }, + }, { // 业务数据组首个页面:评级计算器是打分工具,不影响绩效金额, // 登录用户即可用,不设 requiresAdmin(区别于系统管理组) diff --git a/frontend/src/views/business/businesses/index.vue b/frontend/src/views/business/businesses/index.vue new file mode 100644 index 0000000..ae3769a --- /dev/null +++ b/frontend/src/views/business/businesses/index.vue @@ -0,0 +1,1309 @@ + + + + + diff --git a/frontend/src/views/business/receipts/index.vue b/frontend/src/views/business/receipts/index.vue new file mode 100644 index 0000000..35a6e80 --- /dev/null +++ b/frontend/src/views/business/receipts/index.vue @@ -0,0 +1,508 @@ + + + + + diff --git a/frontend/src/views/business/shipments/index.vue b/frontend/src/views/business/shipments/index.vue new file mode 100644 index 0000000..2b3b788 --- /dev/null +++ b/frontend/src/views/business/shipments/index.vue @@ -0,0 +1,497 @@ + + + + +