diff --git a/apps/web-antd/src/api/crm/business/index.ts b/apps/web-antd/src/api/crm/business/index.ts index ec544b92d..a1ff09e6f 100644 --- a/apps/web-antd/src/api/crm/business/index.ts +++ b/apps/web-antd/src/api/crm/business/index.ts @@ -40,6 +40,7 @@ export namespace CrmBusinessApi { totalProductPrice: number; totalPrice: number; discountPercent: number; + status?: number; remark: string; creator: string; // 创建人 creatorName?: string; // 创建人名称 @@ -47,6 +48,12 @@ export namespace CrmBusinessApi { updateTime: Date; // 更新时间 products?: BusinessProduct[]; } + + export interface BusinessStatus { + id: number; + statusId: number | undefined; + endStatus: number | undefined; + } } /** 查询商机列表 */ @@ -90,7 +97,7 @@ export function updateBusiness(data: CrmBusinessApi.Business) { } /** 修改商机状态 */ -export function updateBusinessStatus(data: CrmBusinessApi.Business) { +export function updateBusinessStatus(data: CrmBusinessApi.BusinessStatus) { return requestClient.put('/crm/business/update-status', data); } diff --git a/apps/web-antd/src/api/crm/business/status/index.ts b/apps/web-antd/src/api/crm/business/status/index.ts index e1c8cfea3..9445938fd 100644 --- a/apps/web-antd/src/api/crm/business/status/index.ts +++ b/apps/web-antd/src/api/crm/business/status/index.ts @@ -4,46 +4,50 @@ import { requestClient } from '#/api/request'; export namespace CrmBusinessStatusApi { /** 商机状态信息 */ - export interface BusinessStatus { - id: number; - name: string; - percent: number; - } - - /** 商机状态组信息 */ export interface BusinessStatusType { id: number; name: string; - deptIds: number[]; - statuses?: BusinessStatus[]; + percent: number; + sort: number; } - /** 默认商机状态 */ - export const DEFAULT_STATUSES = [ - { - endStatus: 1, - key: '结束', - name: '赢单', - percent: 100, - }, - { - endStatus: 2, - key: '结束', - name: '输单', - percent: 0, - }, - { - endStatus: 3, - key: '结束', - name: '无效', - percent: 0, - }, - ] as const; + /** 商机状态组信息 */ + export interface BusinessStatus { + id: number; + name: string; + deptIds: number[]; + deptNames: string[]; + creator: string; + createTime: Date; + statuses?: BusinessStatusType[]; + } } +/** 默认商机状态 */ +export const DEFAULT_STATUSES = [ + { + endStatus: 1, + key: '结束', + name: '赢单', + percent: 100, + }, + { + endStatus: 2, + key: '结束', + name: '输单', + percent: 0, + }, + { + endStatus: 3, + key: '结束', + name: '无效', + percent: 0, + }, +]; + /** 查询商机状态组列表 */ export function getBusinessStatusPage(params: PageParam) { - return requestClient.get>( + return requestClient.get>( '/crm/business-status/page', { params }, ); @@ -51,21 +55,21 @@ export function getBusinessStatusPage(params: PageParam) { /** 新增商机状态组 */ export function createBusinessStatus( - data: CrmBusinessStatusApi.BusinessStatusType, + data: CrmBusinessStatusApi.BusinessStatus, ) { return requestClient.post('/crm/business-status/create', data); } /** 修改商机状态组 */ export function updateBusinessStatus( - data: CrmBusinessStatusApi.BusinessStatusType, + data: CrmBusinessStatusApi.BusinessStatus, ) { return requestClient.put('/crm/business-status/update', data); } /** 查询商机状态类型详情 */ export function getBusinessStatus(id: number) { - return requestClient.get( + return requestClient.get( `/crm/business-status/get?id=${id}`, ); } @@ -77,14 +81,14 @@ export function deleteBusinessStatus(id: number) { /** 获得商机状态组列表 */ export function getBusinessStatusTypeSimpleList() { - return requestClient.get( + return requestClient.get( '/crm/business-status/type-simple-list', ); } /** 获得商机阶段列表 */ export function getBusinessStatusSimpleList(typeId: number) { - return requestClient.get( + return requestClient.get( '/crm/business-status/status-simple-list', { params: { typeId } }, ); diff --git a/apps/web-antd/src/utils/formatNumber.ts b/apps/web-antd/src/utils/formatNumber.ts index 13fdab6a7..f500183d0 100644 --- a/apps/web-antd/src/utils/formatNumber.ts +++ b/apps/web-antd/src/utils/formatNumber.ts @@ -80,3 +80,107 @@ export function calculateRelativeRate( ((100 * ((value || 0) - reference)) / reference).toFixed(0), ); } + +// ========== ERP 专属方法 ========== + +const ERP_COUNT_DIGIT = 3; +const ERP_PRICE_DIGIT = 2; + +/** + * 【ERP】格式化 Input 数字 + * + * 例如说:库存数量 + * + * @param num 数量 + * @package + * @return 格式化后的数量 + */ +export function erpNumberFormatter( + num: number | string | undefined, + digit: number, +) { + if (num === null || num === undefined) { + return ''; + } + if (typeof num === 'string') { + num = Number.parseFloat(num); + } + // 如果非 number,则直接返回空串 + if (Number.isNaN(num)) { + return ''; + } + return num.toFixed(digit); +} + +/** + * 【ERP】格式化数量,保留三位小数 + * + * 例如说:库存数量 + * + * @param num 数量 + * @return 格式化后的数量 + */ +export function erpCountInputFormatter(num: number | string | undefined) { + return erpNumberFormatter(num, ERP_COUNT_DIGIT); +} + +// noinspection JSCommentMatchesSignature +/** + * 【ERP】格式化数量,保留三位小数 + * + * @param cellValue 数量 + * @return 格式化后的数量 + */ +export function erpCountTableColumnFormatter(cellValue: any) { + return erpNumberFormatter(cellValue, ERP_COUNT_DIGIT); +} + +/** + * 【ERP】格式化金额,保留二位小数 + * + * 例如说:库存数量 + * + * @param num 数量 + * @return 格式化后的数量 + */ +export function erpPriceInputFormatter(num: number | string | undefined) { + return erpNumberFormatter(num, ERP_PRICE_DIGIT); +} + +// noinspection JSCommentMatchesSignature +/** + * 【ERP】格式化金额,保留二位小数 + * + * @param cellValue 数量 + * @return 格式化后的数量 + */ +export function erpPriceTableColumnFormatter(cellValue: any) { + return erpNumberFormatter(cellValue, ERP_PRICE_DIGIT); +} + +/** + * 【ERP】价格计算,四舍五入保留两位小数 + * + * @param price 价格 + * @param count 数量 + * @return 总价格。如果有任一为空,则返回 undefined + */ +export function erpPriceMultiply(price: number, count: number) { + if (price === null || count === null) { + return undefined; + } + return Number.parseFloat((price * count).toFixed(ERP_PRICE_DIGIT)); +} + +/** + * 【ERP】百分比计算,四舍五入保留两位小数 + * + * 如果 total 为 0,则返回 0 + * + * @param value 当前值 + * @param total 总值 + */ +export function erpCalculatePercentage(value: number, total: number) { + if (total === 0) return 0; + return ((value / total) * 100).toFixed(2); +} diff --git a/apps/web-antd/src/views/crm/business/data.ts b/apps/web-antd/src/views/crm/business/data.ts index bf65a743c..d781ef036 100644 --- a/apps/web-antd/src/views/crm/business/data.ts +++ b/apps/web-antd/src/views/crm/business/data.ts @@ -1,5 +1,13 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { formatDateTime } from '@vben/utils'; + +import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getSimpleUserList } from '#/api/system/user'; +import { erpPriceInputFormatter, erpPriceMultiply } from '#/utils'; /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -19,18 +27,49 @@ export function useFormSchema(): VbenFormSchema[] { rules: 'required', }, { - fieldName: 'customerId', - label: '客户', - component: 'Input', + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: () => getSimpleUserList(), + fieldNames: { + label: 'nickname', + value: 'id', + }, + }, rules: 'required', }, { - fieldName: 'totalPrice', - label: '商机金额', - component: 'InputNumber', + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', componentProps: { - min: 0, - placeholder: '请输入商机金额', + api: () => getCustomerSimpleList(), + fieldNames: { + label: 'name', + value: 'id', + }, + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => !values.customerId, + }, + rules: 'required', + }, + { + fieldName: 'statusTypeId', + label: '商机状态组', + component: 'ApiSelect', + componentProps: { + api: () => getBusinessStatusTypeSimpleList(), + fieldNames: { + label: 'name', + value: 'id', + }, + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => !values.id, }, rules: 'required', }, @@ -46,9 +85,43 @@ export function useFormSchema(): VbenFormSchema[] { }, }, { - fieldName: 'remark', - label: '备注', - component: 'Textarea', + fieldName: 'totalProductPrice', + label: '产品总金额', + component: 'InputNumber', + componentProps: { + min: 0, + }, + rules: 'required', + }, + { + fieldName: 'discountPercent', + label: '整单折扣(%)', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + }, + rules: 'required', + }, + { + fieldName: 'totalPrice', + label: '折扣后金额', + component: 'InputNumber', + dependencies: { + triggerFields: ['totalProductPrice', 'discountPercent'], + disabled: () => true, + trigger(values, form) { + const discountPrice = + erpPriceMultiply( + values.totalProductPrice, + values.discountPercent / 100, + ) ?? 0; + form.setFieldValue( + 'totalPrice', + values.totalProductPrice - discountPrice, + ); + }, + }, }, ]; } @@ -143,3 +216,72 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'statusTypeName', + label: '商机组', + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '商机名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'dealTime', + label: '预计成交日期', + content: (data) => formatDateTime(data?.dealTime) as string, + }, + { + field: 'contactNextTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.contactNextTime) as string, + }, + { + field: 'statusTypeName', + label: '商机状态组', + }, + { + field: 'statusName', + label: '商机阶段', + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/business/modules/detail-info.vue b/apps/web-antd/src/views/crm/business/modules/detail-info.vue index da6c78661..a583db684 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail-info.vue @@ -1,4 +1,44 @@ - + + diff --git a/apps/web-antd/src/views/crm/business/modules/detail.vue b/apps/web-antd/src/views/crm/business/modules/detail.vue index 99ad6b6f9..7e140274e 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail.vue @@ -1,7 +1,188 @@ - + diff --git a/apps/web-antd/src/views/crm/business/modules/up-status-form.vue b/apps/web-antd/src/views/crm/business/modules/up-status-form.vue new file mode 100644 index 000000000..c8ea4300f --- /dev/null +++ b/apps/web-antd/src/views/crm/business/modules/up-status-form.vue @@ -0,0 +1,140 @@ + + + diff --git a/apps/web-antd/src/views/crm/business/status/modules/form.vue b/apps/web-antd/src/views/crm/business/status/modules/form.vue index aeb211723..0097e1693 100644 --- a/apps/web-antd/src/views/crm/business/status/modules/form.vue +++ b/apps/web-antd/src/views/crm/business/status/modules/form.vue @@ -18,7 +18,7 @@ import { $t } from '#/locales'; import { useFormSchema } from '../data'; const emit = defineEmits(['success']); -const formData = ref(); +const formData = ref(); const getTitle = computed(() => { return formData.value?.id ? $t('ui.actionTitle.edit', ['商机状态']) @@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); // 提交表单 const data = - (await formApi.getValues()) as CrmBusinessStatusApi.BusinessStatusType; + (await formApi.getValues()) as CrmBusinessStatusApi.BusinessStatus; try { await (formData.value?.id ? updateBusinessStatus(data) @@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({ return; } // 加载数据 - const data = modalApi.getData(); + const data = modalApi.getData(); if (!data || !data.id) { return; } diff --git a/apps/web-antd/src/views/crm/customer/modules/detail.vue b/apps/web-antd/src/views/crm/customer/modules/detail.vue index 1697075eb..8baf235ed 100644 --- a/apps/web-antd/src/views/crm/customer/modules/detail.vue +++ b/apps/web-antd/src/views/crm/customer/modules/detail.vue @@ -212,21 +212,21 @@ onMounted(async () => { - + - + - + - + - +
联系人
- + { @quit-team="handleBack" /> - +
商机
- +
合同
- +
回款
- +