diff --git a/src/pages/order3/SaleTask/api.ts b/src/pages/order3/SaleTask/api.ts index 6585a71..0cf9da6 100644 --- a/src/pages/order3/SaleTask/api.ts +++ b/src/pages/order3/SaleTask/api.ts @@ -25,6 +25,7 @@ export interface GetSaleTaskApiRes { testDriveTaskCount: number; seriesTaskCount: number; vehicleGrossProfitTask: number; + totalGrossProfitTask: number; } export interface SeriesTaskItem { @@ -71,6 +72,8 @@ export interface ShopTaskItem { seriesTaskCount: number; vehicleGrossProfitTask: number; taskId?: number; + orderTaskApplyId?: number; + orderShopTaskId?: number; } /** 月度零售任务列表 */ @@ -81,8 +84,8 @@ export function getSaleTaskApi( } export interface GetShopSaleTaskReq { - shopId: number; - taskDate: number; + shopId?: number; + taskDate?: number; } /** 门店零售任务详情 */ @@ -109,6 +112,54 @@ export function submitSaleTask(params: { id: number }): PromiseResp { return request.post(`${ORDER3_HOST}/erp/sales/task/submit`, params); } +export interface AutoAssignItem { + shopId: number; + taskCount: number; + newEnergyTaskCount: number; + fuelVehicleTaskCount: number; + tackCarTaskCount: number; +} + +export interface AutoAssignSaleTaskReq { + id: number; + assignTask: boolean; + shopTaskList: AutoAssignItem[]; +} + +/** 自动分配零售任务 */ +export function autoAssignSaleTask( + params: AutoAssignSaleTaskReq +): PromiseResp { + return request.post(`${ORDER3_HOST}/erp/sales/task/auto/assign`, params); +} + +interface BatchSetSaleTaskItem { + shopIdList: number[]; + taskAims: number; +} + +export interface BatchSetSaleTaskReq { + grossProfitTaskList?: BatchSetSaleTaskItem[]; + tackCarTaskList?: BatchSetSaleTaskItem[]; + testDriveTaskList?: BatchSetSaleTaskItem[]; + assignTask: boolean; + orderTaskApplyId: number; +} + +/** 批量设置零售任务 */ +export function batchSetSaleTask( + params: BatchSetSaleTaskReq +): PromiseResp { + return request.post(`${ORDER3_HOST}/erp/sales/task/batch/shop/set`, params); +} + +/** 自动分配单个门店的零售任务 */ +export function autoAssignOneShop( + params: ShopTaskItem +): PromiseResp { + return request.post(`${ORDER3_HOST}/erp/sales/task/auto/assign/shop`, params); +} + export interface BrandItem { id: number; initial: string; @@ -148,9 +199,10 @@ export interface PreviewTaskReq { secondManageId?: number; thirdManageId?: number; taskId?: number; - orderTaskApprovalType: number; - id: number; + orderTaskApprovalType?: number; + id?: number; token?: string; + keywords?: string; } export interface TaskListItem { @@ -188,7 +240,9 @@ export interface PreviewTaskRes { } /** 预览任务 */ -export function previewTask(params: PreviewTaskReq): PromiseResp { +export function previewTask( + params: PreviewTaskReq +): PromiseResp { return request.get(`${ORDER3_HOST}/erp/sales/task/approve/info`, { params, }); diff --git a/src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx b/src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx index ffd54ed..281be28 100644 --- a/src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx +++ b/src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx @@ -4,8 +4,7 @@ import { observer } from "mobx-react-lite"; import * as API from "../api"; import useInitial from "@/hooks/useInitail"; import ModifiedTableCell from "./ModifiedTableCell"; - -const { Column } = Table; +import { ColumnsType } from "antd/es/table"; // 查看销顾任务弹框 interface AdviserTaskPreviewProps { @@ -31,73 +30,100 @@ const AdviserTaskPreview = ({ showSeriesModal(record); }; + const columns: ColumnsType = [ + { + title: "姓名", + width: 100, + dataIndex: "dataName", + filterSearch: true, + onFilter: ( + value: string | number | boolean, + record: API.TaskListItem + ) => { + return record.dataName.startsWith(value.toString()); + }, + }, + { + title: "零售任务(台)", + children: [ + { + title: "合计", + dataIndex: "taskCount", + key: "taskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "taskCount"); + }, + }, + { + title: "新能源车", + dataIndex: "newEnergyTaskCount", + key: "newEnergyTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "newEnergyTaskCount"); + }, + }, + { + title: "传统燃油车", + dataIndex: "fuelVehicleTaskCount", + key: "fuelVehicleTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "fuelVehicleTaskCount"); + }, + }, + ], + }, + { + title: "单车毛利任务(元)", + dataIndex: "vehicleGrossProfitTask", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "vehicleGrossProfitTask"); + }, + }, + { + title: "线索到店成交(台)", + width: 100, + dataIndex: "clueDealTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "clueDealTaskCount"); + }, + }, + { + title: "首客试驾成交(台)", + width: 100, + dataIndex: "testDriveTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "testDriveTaskCount"); + }, + }, + { + title: "攻坚车任务(台)", + width: 100, + dataIndex: "tackCarTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "tackCarTaskCount"); + }, + }, + { + title: "车系任务(台)", + width: 100, + dataIndex: "seriesTaskCount", + render: (text: string, record: API.TaskListItem) => { + if (record.dataId === -999) return text; + return handlePreviewSeriesTask(record)}>{text}; + }, + }, + ]; + return ( - - { - return ModifiedTableCell(record, "taskCount"); - }} - /> - { - return ModifiedTableCell(record, "newEnergyTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "fuelVehicleTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "vehicleGrossProfitTask"); - }} - /> - { - return ModifiedTableCell(record, "clueDealTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "testDriveTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "tackCarTaskCount"); - }} - /> - { - if (record.dataId === -999) return text; - return handlePreviewSeriesTask(record)}>{text}; - }} - /> -
+ /> ); }; diff --git a/src/pages/order3/SaleTask/components/EntryTaskPreview.tsx b/src/pages/order3/SaleTask/components/EntryTaskPreview.tsx index 1b1fb70..4e3ea1c 100644 --- a/src/pages/order3/SaleTask/components/EntryTaskPreview.tsx +++ b/src/pages/order3/SaleTask/components/EntryTaskPreview.tsx @@ -1,17 +1,27 @@ import React, { useState } from "react"; -import { Card, Radio, RadioChangeEvent, Row, Table } from "antd"; +import { + Card, + DatePicker, + Input, + Radio, + RadioChangeEvent, + Row, + Table, +} from "antd"; import { observer } from "mobx-react-lite"; import * as API from "../api"; import { OrderTaskApprovalType } from "../entity"; import useInitial from "@/hooks/useInitail"; import ModifiedTableCell from "./ModifiedTableCell"; +import { ColumnsType } from "antd/es/table"; +import { Moment } from "moment"; -const { Column } = Table; const RadioButton = Radio.Button; const RadioGroup = Radio.Group; // 预览任务入口弹框 interface EntryTaskPreviewProps { + month: Moment; params: any; // API.PreviewTaskReq showAdviserModal: ( record: API.TaskListItem, @@ -24,11 +34,13 @@ interface EntryTaskPreviewProps { } const EntryTaskPreview = ({ + month, params, showAdviserModal, showSeriesModal, }: EntryTaskPreviewProps) => { const [type, setType] = useState(OrderTaskApprovalType.门店维度); + const [keywords, setKeywords] = useState(""); const { data, loading, setParams } = useInitial< API.PreviewTaskRes, @@ -40,15 +52,13 @@ const EntryTaskPreview = ({ if (value === 99) { setType(OrderTaskApprovalType.新车一级管理维度); setParams( - // @ts-ignore { orderTaskApprovalType: OrderTaskApprovalType.新车一级管理维度 }, true ); return; } setType(value); - // @ts-ignore - setParams({ orderTaskApprovalType: value }, true); + setParams({ orderTaskApprovalType: value, keywords }, true); }; // 查看顾问任务 @@ -67,130 +77,171 @@ const EntryTaskPreview = ({ showSeriesModal(record, type); }; - return ( - - - - 门店 - - - 销顾 - - 销售管理 - - {type !== OrderTaskApprovalType.门店维度 && - type !== OrderTaskApprovalType.销售顾问维度 && ( - - - 销售一级管理 - - - 销售二级管理 - - - 销售三级管理 - - - )} - - } - > - - { - return record.dataName.startsWith(value.toString()); - }} - /> - { + const columns: ColumnsType = [ + { + title: type === OrderTaskApprovalType.门店维度 ? "门店" : "姓名", + width: type === OrderTaskApprovalType.门店维度 ? 150 : 100, + dataIndex: "dataName", + filterSearch: true, + onFilter: ( + value: string | number | boolean, + record: API.TaskListItem + ) => { + return record.dataName.startsWith(value.toString()); + }, + }, + { + title: "零售任务(台)", + children: [ + { + title: "合计", + dataIndex: "taskCount", + key: "taskCount", + render: (text: string, record: API.TaskListItem) => { return ModifiedTableCell(record, "taskCount"); - }} - /> - { + }, + }, + { + title: "新能源车", + dataIndex: "newEnergyTaskCount", + key: "newEnergyTaskCount", + render: (text: string, record: API.TaskListItem) => { return ModifiedTableCell(record, "newEnergyTaskCount"); - }} - /> - { + }, + }, + { + title: "传统燃油车", + dataIndex: "fuelVehicleTaskCount", + key: "fuelVehicleTaskCount", + render: (text: string, record: API.TaskListItem) => { return ModifiedTableCell(record, "fuelVehicleTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "vehicleGrossProfitTask"); - }} - /> - { - return ModifiedTableCell(record, "clueDealTaskCount"); - }} - /> - { - return ModifiedTableCell(record, "testDriveTaskCount"); - }} + }, + }, + ], + }, + { + title: "单车毛利任务(元)", + dataIndex: "vehicleGrossProfitTask", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "vehicleGrossProfitTask"); + }, + }, + { + title: "线索到店成交(台)", + width: 100, + dataIndex: "clueDealTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "clueDealTaskCount"); + }, + }, + { + title: "首客试驾成交(台)", + width: 100, + dataIndex: "testDriveTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "testDriveTaskCount"); + }, + }, + { + title: "攻坚车任务(台)", + width: 100, + dataIndex: "tackCarTaskCount", + render: (text: string, record: API.TaskListItem) => { + return ModifiedTableCell(record, "tackCarTaskCount"); + }, + }, + { + title: "车系任务(台)", + width: 100, + dataIndex: "seriesTaskCount", + render: (text: string, record: API.TaskListItem) => { + if (record.dataId === -999) return text; + return handlePreviewSeriesTask(record)}>{text}; + }, + }, + ]; + + const extraColumns: ColumnsType = [ + { + title: "销顾任务", + render: (text: string, record: API.TaskListItem) => { + if (record.dataId === -999) { + return "-"; + } + return handlePreviewAdviserTask(record)}>查看; + }, + }, + ]; + + return ( + <> + + - { - return ModifiedTableCell(record, "tackCarTaskCount"); + setKeywords(e.target.value)} + onSearch={(v) => { + setParams({ keywords: v }, true); }} /> - { - if (record.dataId === -999) return text; - return ( - handlePreviewSeriesTask(record)}>{text} - ); - }} + + + + + 门店 + + + 销顾 + + 销售管理层 + + {type !== OrderTaskApprovalType.门店维度 && + type !== OrderTaskApprovalType.销售顾问维度 && ( + + + 销售一级管理 + + + 销售二级管理 + + + 销售三级管理 + + + )} + + } + > +
- {type === OrderTaskApprovalType.门店维度 && ( - { - if (record.dataId === -999) return "-"; - return ( - handlePreviewAdviserTask(record)}>查看 - ); - }} - /> - )} -
-
+ + ); }; diff --git a/src/pages/order3/SaleTask/components/SaleTaskAutoAssign.tsx b/src/pages/order3/SaleTask/components/SaleTaskAutoAssign.tsx new file mode 100644 index 0000000..ebdc0be --- /dev/null +++ b/src/pages/order3/SaleTask/components/SaleTaskAutoAssign.tsx @@ -0,0 +1,280 @@ +import React, { useContext, useEffect, useRef, useState } from "react"; +import { + Table, + Form, + InputRef, + Row, + Button, + message, + Modal, + InputNumber, +} from "antd"; +import type { FormInstance } from "antd/es/form"; +import * as API from "../api"; +import "./index.less"; +import { MAX_NUM } from "../entity"; + +type EditableTableProps = Parameters[0]; +type ColumnTypes = Exclude; +interface Item { + id: string; + shopName: string; + taskCount: number; + newEnergyTaskCount: number; + fuelVehicleTaskCount: number; + tackCarTaskCount: number; +} +interface EditableRowProps { + index: number; +} +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + children: React.ReactNode; + dataIndex: keyof Item; + record: Item; + handleSave: (record: Item) => void; +} + +const defaultColumns: (ColumnTypes[number] & { + editable?: boolean; + dataIndex: string; +})[] = [ + { + title: "门店", + dataIndex: "shopName", + editable: false, + }, + { + title: "零售任务(台)", + dataIndex: "taskCount", + editable: true, + }, + { + title: "新能源车任务(台)", + dataIndex: "newEnergyTaskCount", + editable: true, + }, + { + title: "传统燃油车任务(台)", + dataIndex: "fuelVehicleTaskCount", + editable: false, + }, + { + title: "攻坚车任务(台)", + dataIndex: "tackCarTaskCount", + editable: true, + }, +]; + +interface SaleTaskAutoAssignProps { + id: number; + value?: API.ShopTaskItem[]; + onCancel: () => void; + onRefresh: () => void; +} + +export default function SaleTaskAutoAssign(props: SaleTaskAutoAssignProps) { + const EditableContext = React.createContext | null>(null); + const [dataSource, setDataSource] = useState([]); + + useEffect(() => { + setDataSource(props.value ? [...props.value] : []); + }, [props.value]); + + const EditableRow: React.FC = ({ index, ...props }) => { + const [form] = Form.useForm(); + return ( +
+ + + +
+ ); + }; + + const EditableCell: React.FC = ({ + title, + editable, + children, + dataIndex, + record, + handleSave, + ...restProps + }) => { + const [editing, setEditing] = useState(false); + const inputRef = useRef(null); + const form = useContext(EditableContext)!; + + useEffect(() => { + if (editing) { + inputRef.current!.focus(); + } + }, [editing]); + + const toggleEdit = () => { + setEditing(!editing); + form.setFieldsValue({ [dataIndex]: record[dataIndex] }); + }; + + const save = async () => { + try { + const values = await form.validateFields(); + toggleEdit(); + handleSave({ ...record, ...values }); + } catch (errInfo) { + console.log("Save failed:", errInfo); + } + }; + + let childNode = children; + + if (editable) { + childNode = editing ? ( + + + + ) : ( +
+ {children} +
+ ); + } + + return {childNode}; + }; + + const handleSave = (row: API.ShopTaskItem) => { + const newData = [...dataSource]; + const index = newData.findIndex((item) => row.id === item.id); + const item = newData[index]; + if (row.taskCount !== 0 && row.newEnergyTaskCount > row.taskCount) { + message.warn("新能源车任务台数不得超过零售任务台数"); + return; + } + const newRow = { + ...item, + ...row, + fuelVehicleTaskCount: row.taskCount - row.newEnergyTaskCount, + }; + if (row.taskCount === 0) { + newRow.taskCount = 0; + newRow.newEnergyTaskCount = 0; + newRow.fuelVehicleTaskCount = 0; + } + newData.splice(index, 1, newRow); + setDataSource(newData); + }; + + const autoAssignSaleTask = (isAssignToAdviser: boolean) => { + Modal.confirm({ + title: isAssignToAdviser ? ( + + 确认分配到 + 全部门店和顾问 + 吗? + + ) : ( + + 确认分配到 + 全部门店 + 吗? + + ), + zIndex: 1002, + onOk: async () => { + const hide = message.loading("分配中,请稍候", 0); + API.autoAssignSaleTask({ + id: props.id, + shopTaskList: dataSource.map((item) => ({ + shopId: item.shopId, + taskCount: item.taskCount, + newEnergyTaskCount: item.newEnergyTaskCount, + fuelVehicleTaskCount: item.fuelVehicleTaskCount, + tackCarTaskCount: item.tackCarTaskCount, + })), + assignTask: isAssignToAdviser, + }) + .then((res) => { + message.success("分配成功"); + props.onRefresh(); + }) + .catch((error: any) => { + message.error(error.message ?? "请求失败"); + }) + .finally(() => { + hide(); + }); + }, + }); + }; + + const components = { + body: { + row: EditableRow, + cell: EditableCell, + }, + }; + + const columns = defaultColumns.map((col) => { + if (!col.editable) { + return col; + } + return { + ...col, + onCell: (record: API.ShopTaskItem) => ({ + record, + editable: col.editable, + dataIndex: col.dataIndex, + title: col.title, + handleSave, + }), + }; + }); + + return ( + <> + "editable-row"} + bordered + rowKey="id" + dataSource={dataSource} + columns={columns as ColumnTypes} + /> + + + + + + + ); +} diff --git a/src/pages/order3/SaleTask/components/SaleTaskBatchSet.tsx b/src/pages/order3/SaleTask/components/SaleTaskBatchSet.tsx new file mode 100644 index 0000000..74720fd --- /dev/null +++ b/src/pages/order3/SaleTask/components/SaleTaskBatchSet.tsx @@ -0,0 +1,310 @@ +import { PlusOutlined } from "@ant-design/icons"; +import { + Button, + Card, + Col, + Form, + InputNumber, + Modal, + Row, + message, +} from "antd"; +import "./index.less"; +import React, { useState } from "react"; +import { MAX_NUM } from "../entity"; +import * as API from "../api"; +import ShopSelectNew from "@/components/ShopSelectNew"; +import { isArray } from "lodash"; + +interface SaleTaskBatchSetProps { + id: number; + onCancel: () => void; + onRefresh: () => void; +} + +export default function SaleTaskBatchSet(props: SaleTaskBatchSetProps) { + const [form] = Form.useForm(); + // 过滤各项已经选择的门店 + // const [selectedShopIds, setSelectedShopIds] = useState({ + // grossProfit: [], + // tackCar: [], + // testDrive: [], + // }); + + const batchSetSaleTask = async (isAssignToAdviser: boolean) => { + await form.validateFields(); + const values = form.getFieldsValue(); + if ( + Object.values(values).every( + (item) => !item || (isArray(item) && item.length === 0) + ) + ) { + message.warn("请设置任务后再进行分配"); + return; + } + const newValues = {}; + Array.from(Object.keys(values)).forEach((valueKey: any) => { + if (values[valueKey]) { + newValues[valueKey] = values[valueKey].map((valueItem: any) => ({ + taskAims: valueItem.taskAims, + shopIdList: valueItem.shopIdList.map( + (shopItem: any) => shopItem.shopId + ), + })); + } + }); + Modal.confirm({ + title: isAssignToAdviser ? ( + + 确认分配到 + 全部门店和顾问 + 吗? + + ) : ( + + 确认分配到 + 全部门店 + 吗? + + ), + zIndex: 1002, + onOk: async () => { + const hide = message.loading("分配中,请稍候", 0); + API.batchSetSaleTask({ + assignTask: isAssignToAdviser, + orderTaskApplyId: props.id, + ...newValues, + }) + .then((res) => { + message.success("分配成功"); + props.onRefresh(); + }) + .catch((error: any) => { + message.error(error.message ?? "请求失败"); + }) + .finally(() => { + hide(); + }); + }, + }); + }; + + // const handleFormChange = (changedValues: any) => { + // const labelKey: any = Object.keys(changedValues)[0]; + // const list: any = Object.values(changedValues)[0]; + // console.log(list); + // if (!list[0]) return; + // if (Object.keys(list[0])[0] === "shopIdList") { + // const newSelectedIds = { ...selectedShopIds }; + // newSelectedIds[labelKey] = Object.values(list[0])[0]; + // setSelectedShopIds(newSelectedIds); + // } + // }; + + return ( + + + {(fields, { add, remove }) => ( + + +

单车毛利任务

+ +
+ {fields.map(({ key, name, ...restField }) => ( + +
+ + `${value}元`} + parser={(value: any) => value.replace("元", "")} + min={0} + max={MAX_NUM} + style={{ width: "100%" }} + precision={2} + placeholder="请填写单车毛利任务" + /> + + + + + + + + + + + + ))} + + )} + + + {(fields, { add, remove }) => ( + + +

首客试驾成交

+ +
+ {fields.map(({ key, name, ...restField }) => ( + +
+ + `${value}台`} + parser={(value: any) => value.replace("台", "")} + min={0} + max={MAX_NUM} + style={{ width: "100%" }} + precision={0} + placeholder="请填写首客试驾成交" + /> + + + + + + + + + + + + ))} + + )} + + + {(fields, { add, remove }) => ( + + +

攻坚车任务

+ +
+ {fields.map(({ key, name, ...restField }) => ( + +
+ + `${value}台`} + parser={(value: any) => value.replace("台", "")} + min={0} + max={MAX_NUM} + style={{ width: "100%" }} + precision={0} + placeholder="请填写攻坚车任务" + /> + + + + + + + + + + + + ))} + + )} + + + + + + + + ); +} diff --git a/src/pages/order3/SaleTask/components/index.less b/src/pages/order3/SaleTask/components/index.less new file mode 100644 index 0000000..b30679b --- /dev/null +++ b/src/pages/order3/SaleTask/components/index.less @@ -0,0 +1,11 @@ +.editable-row:hover .editable-cell-value-wrap { + border: 1px solid #d9d9d9; +} +.editable-cell-value-wrap { + border: 1px solid transparent; + cursor: pointer; +} +.tip { + color: #ff4d4f; + font-weight: bold; +} diff --git a/src/pages/order3/SaleTask/index.tsx b/src/pages/order3/SaleTask/index.tsx index 15b5444..23517c3 100644 --- a/src/pages/order3/SaleTask/index.tsx +++ b/src/pages/order3/SaleTask/index.tsx @@ -15,14 +15,15 @@ import { history } from "umi"; import moment, { Moment } from "moment"; import useInitial from "@/hooks/useInitail"; import { Provider, useStore } from "./store"; -import { default as ApprovalProgressModal } from "@/pages/stock/AdvanceProgress/components/ApproveModal"; +import ApprovalProgressModal from "@/pages/stock/AdvanceProgress/components/ApproveModal"; import EntryTaskPreview from "./components/EntryTaskPreview"; import { OrderTaskApprovalType } from "./entity"; import AdviserTaskPreview from "./components/AdviserTaskPreview"; import SeriesTaskPreview from "./components/SeriesTaskPreview"; import ApproveModal from "@/pages/order3/Common/ApproveModal"; - -const { Column } = Table; +import SaleTaskAutoAssign from "./components/SaleTaskAutoAssign"; +import SaleTaskBatchSet from "./components/SaleTaskBatchSet"; +import { ColumnsType } from "antd/es/table"; export default () => ( @@ -42,6 +43,9 @@ function SaleTaskList() { const [stpVisible, setStpVisible] = useState(false); const [seriesTaskParams, setSeriesTaskParams] = useState({}); + const [autoVisible, setAutoVisible] = useState(false); + const [batchVisible, setBatchVisible] = useState(false); + const { data, loading, setParams } = useInitial( API.getSaleTaskApi, {} as API.GetSaleTaskApiRes, @@ -71,6 +75,19 @@ function SaleTaskList() { }); }; + // 查看销顾任务 + const goToAdviserPage = (record: API.ShopTaskItem) => { + history.push({ + pathname: "/order3/saleTask/edit", + query: { + readOnly: isReadOnly ? "1" : "0", + shopId: String(record.shopId), + taskDate: String(targetMonth.valueOf()), + currTab: "2", + }, + }); + }; + // 查看流程进度 const viewProcess = () => { setApprove({ @@ -128,30 +145,242 @@ function SaleTaskList() { setEtpVisible(true); }; + // 销顾任务 + const showAdviserModal = ( + record: API.TaskListItem, + type: OrderTaskApprovalType + ) => { + const params: any = { + id: data.id, + taskId: record.id, + orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 + }; + switch (type) { + case OrderTaskApprovalType.门店维度: + params.shopId = record.dataId; + break; + case OrderTaskApprovalType.销售顾问维度: + params.staffId = record.dataId; + break; + case OrderTaskApprovalType.新车一级管理维度: + params.firstManageId = record.dataId; + break; + case OrderTaskApprovalType.新车二级管理维度: + params.secondManageId = record.dataId; + break; + case OrderTaskApprovalType.新车三级管理维度: + params.thirdManageId = record.dataId; + break; + default: + break; + } + setAdviserTaskParams(params); + setAtpVisible(true); + }; + + // 销顾任务--查看车系任务 + const showSeriesModalByAdviser = (record: API.TaskListItem) => { + const params = { ...adviserTaskParams } as any; + params.taskId = record.id; + params.orderTaskApprovalType = OrderTaskApprovalType.车系; + params.staffId = record.dataId; + setSeriesTaskParams(params); + setStpVisible(true); + }; + + // 车系任务 + const showSeriesModal = ( + record: API.TaskListItem, + type: OrderTaskApprovalType + ) => { + const params: any = { + id: data.id, + taskId: record.id, + orderTaskApprovalType: OrderTaskApprovalType.车系, + }; + switch (type) { + case OrderTaskApprovalType.门店维度: + params.shopId = record.dataId; + break; + case OrderTaskApprovalType.销售顾问维度: + params.staffId = record.dataId; + break; + case OrderTaskApprovalType.新车一级管理维度: + params.firstManageId = record.dataId; + break; + case OrderTaskApprovalType.新车二级管理维度: + params.secondManageId = record.dataId; + break; + case OrderTaskApprovalType.新车三级管理维度: + params.thirdManageId = record.dataId; + break; + default: + break; + } + setSeriesTaskParams(params); + setStpVisible(true); + }; + + const handleAutoAssignRefresh = () => { + setAutoVisible(false); + setParams({}, true); + }; + const handleBatchSetRefresh = () => { + setBatchVisible(false); + setParams({}, true); + }; + + const columns: ColumnsType = [ + { + title: "门店", + width: 150, + dataIndex: "shopName", + }, + { + title: "零售任务(台)", + children: [ + { + title: "合计", + dataIndex: "taskCount", + key: "taskCount", + }, + { + title: "新能源车", + dataIndex: "newEnergyTaskCount", + key: "newEnergyTaskCount", + }, + { + title: "传统燃油车", + dataIndex: "fuelVehicleTaskCount", + key: "fuelVehicleTaskCount", + }, + ], + }, + { + title: "毛利任务(元)", + children: [ + { + title: "合计", + dataIndex: "grossProfitTaskTotal", + key: "grossProfitTaskTotal", + render: (text: string, record: API.ShopTaskItem) => { + return (record.taskCount * record.vehicleGrossProfitTask).toFixed( + 2 + ); + }, + }, + { + title: "单车", + dataIndex: "vehicleGrossProfitTask", + key: "vehicleGrossProfitTask", + }, + ], + }, + { + title: "线索到店成交(台)", + width: 100, + dataIndex: "clueDealTaskCount", + key: "clueDealTaskCount", + }, + { + title: "首客试驾成交(台)", + width: 100, + dataIndex: "testDriveTaskCount", + key: "testDriveTaskCount", + }, + { + title: "攻坚车任务(台)", + width: 100, + dataIndex: "tackCarTaskCount", + key: "tackCarTaskCount", + }, + { + title: "车系任务(台)", + width: 100, + dataIndex: "seriesTaskCount", + key: "seriesTaskCount", + }, + { + title: "销顾任务", + render: (text: string, record: API.ShopTaskItem) => { + return ( + { + goToAdviserPage(record); + }} + > + 查看 + + ); + }, + }, + { + title: "操作", + render: (text: string, record: API.ShopTaskItem) => { + return ( + { + goToEditPage(record); + }} + > + {isReadOnly ? "查看" : "编辑"} + + ); + }, + }, + ]; + return ( - - - { - setParams({ shopName: v }, true); - }} - /> + + + + { + setParams({ shopName: v }, true); + }} + onBlur={(e) => { + if (e.target.value.trim() === "") { + setParams({ shopName: "" }, true); + } + }} + /> + + {!isReadOnly && ( + + + + + )}
- - 合计 - - + 合计 + {data.totalTaskCount} - + {data.newEnergyTaskCount} - + {data.fuelVehicleTaskCount} - - {data.vehicleGrossProfitTask} + + {data.totalGrossProfitTask} - + - + {data.clueDealTaskCount} - + {data.testDriveTaskCount} - + {data.tackCarTaskCount} - + {data.seriesTaskCount} - + + ); }} - > - - - - - - - - - - { - return ( - { - goToEditPage(record); - }} - > - {isReadOnly ? "查看" : "编辑"} - - ); - }} - /> -
+ /> {data.revoke ? ( @@ -271,82 +473,11 @@ function SaleTaskList() { destroyOnClose footer={null} > - - - {/* { - // todo setParams({ shopName: v }, true); - }} - /> */} - { - const params: any = { - id: data.id, - taskId: record.id, - orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 - }; - switch (type) { - case OrderTaskApprovalType.门店维度: - params.shopId = record.dataId; - break; - case OrderTaskApprovalType.销售顾问维度: - params.staffId = record.dataId; - break; - case OrderTaskApprovalType.新车一级管理维度: - params.firstManageId = record.dataId; - break; - case OrderTaskApprovalType.新车二级管理维度: - params.secondManageId = record.dataId; - break; - case OrderTaskApprovalType.新车三级管理维度: - params.thirdManageId = record.dataId; - break; - default: - break; - } - setAdviserTaskParams(params); - setAtpVisible(true); - }} - showSeriesModal={(record, type) => { - const params: any = { - id: data.id, - taskId: record.id, - orderTaskApprovalType: OrderTaskApprovalType.车系, - }; - switch (type) { - case OrderTaskApprovalType.门店维度: - params.shopId = record.dataId; - break; - case OrderTaskApprovalType.销售顾问维度: - params.staffId = record.dataId; - break; - case OrderTaskApprovalType.新车一级管理维度: - params.firstManageId = record.dataId; - break; - case OrderTaskApprovalType.新车二级管理维度: - params.secondManageId = record.dataId; - break; - case OrderTaskApprovalType.新车三级管理维度: - params.thirdManageId = record.dataId; - break; - default: - break; - } - setSeriesTaskParams(params); - setStpVisible(true); - }} + showAdviserModal={showAdviserModal} + showSeriesModal={showSeriesModal} /> { - const params = { ...adviserTaskParams } as any; - params.taskId = record.id; - params.orderTaskApprovalType = OrderTaskApprovalType.车系; - params.staffId = record.dataId; - setSeriesTaskParams(params); - setStpVisible(true); - }} + showSeriesModal={showSeriesModalByAdviser} /> + setAutoVisible(false)} + destroyOnClose + footer={null} + maskClosable={false} + > + setAutoVisible(false)} + onRefresh={handleAutoAssignRefresh} + /> + + setBatchVisible(false)} + destroyOnClose + footer={null} + maskClosable={false} + > + setBatchVisible(false)} + onRefresh={handleBatchSetRefresh} + /> + void) => void; @@ -14,9 +13,10 @@ interface AdviserTaskEditForm { interface AdviserTaskProps { form: any; + onRefresh: () => void; } -export default function AdviserTask({ form }: AdviserTaskProps) { +export default function AdviserTask({ form, onRefresh }: AdviserTaskProps) { const adviserTaskEditRef = useRef(null); const { shopTaskItem, @@ -48,6 +48,10 @@ export default function AdviserTask({ form }: AdviserTaskProps) { // 前端更新编辑后的顾问分配任务 const onOk = () => { + if (isReadOnly) { + setVisible(false); + return; + } adviserTaskEditRef.current?.submit((newTask) => { setShopTaskItem(newTask); setAdvisersFiltered(newTask?.staffTaskList); // 刷新列表 @@ -66,12 +70,96 @@ export default function AdviserTask({ form }: AdviserTaskProps) { .run({ ...other, id: taskId, ...shopFormValue }) .then(() => { message.success("保存草稿成功"); + onRefresh(); }) .catch((error: any) => { message.error(error.message ?? "请求失败"); }); }; + const columns: ColumnsType = [ + { + title: "销售顾问", + width: 100, + dataIndex: "staffName", + }, + { + title: "零售任务(台)", + children: [ + { + title: "合计", + dataIndex: "taskCount", + key: "taskCount", + }, + { + title: "新能源车", + dataIndex: "newEnergyTaskCount", + key: "newEnergyTaskCount", + }, + { + title: "传统燃油车", + dataIndex: "fuelVehicleTaskCount", + key: "fuelVehicleTaskCount", + }, + ], + }, + { + title: "毛利任务(元)", + children: [ + { + title: "合计", + dataIndex: "grossProfitTaskTotal", + key: "grossProfitTaskTotal", + render: (text: string, record: API.StaffTaskItem) => { + return (record.taskCount * record.vehicleGrossProfitTask).toFixed( + 2 + ); + }, + }, + { + title: "单车", + dataIndex: "vehicleGrossProfitTask", + key: "vehicleGrossProfitTask", + }, + ], + }, + { + title: "线索到店成交(台)", + width: 100, + dataIndex: "clueDealTaskCount", + }, + { + title: "首客试驾成交(台)", + width: 100, + dataIndex: "testDriveTaskCount", + }, + { + title: "攻坚车任务(台)", + width: 100, + dataIndex: "tackCarTaskCount", + }, + { + title: "车系任务(台)", + width: 100, + dataIndex: "seriesTaskCount", + }, + { + title: "操作", + render: (text: string, record: API.StaffTaskItem) => { + return ( + { + setEditAdviser(cloneDeep(record)); // 注意对象引用 + setVisible(true); + }} + > + {isReadOnly ? "查看" : "编辑"} + + ); + }, + }, + ]; + return ( <> - - - - - - - - - - {!isReadOnly && ( - { - return ( - { - setEditAdviser(cloneDeep(record)); // 注意对象引用 - setVisible(true); - }} - > - 编辑 - - ); - }} - /> - )} -
+ /> {!isReadOnly && ( diff --git a/src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTaskEdit.tsx b/src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTaskEdit.tsx index 20e034c..d1343f7 100644 --- a/src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTaskEdit.tsx +++ b/src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTaskEdit.tsx @@ -124,6 +124,7 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { form={form} initialValues={editAdviser} onFinish={onFinish} + disabled={isReadOnly} > @@ -164,7 +164,6 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={2} /> @@ -204,7 +203,6 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -237,13 +235,12 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -267,7 +263,6 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -278,7 +273,6 @@ function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> diff --git a/src/pages/order3/SaleTask/subpages/TaskEdit/components/ShopTask.tsx b/src/pages/order3/SaleTask/subpages/TaskEdit/components/ShopTask.tsx index 64e54d9..5cbf9be 100644 --- a/src/pages/order3/SaleTask/subpages/TaskEdit/components/ShopTask.tsx +++ b/src/pages/order3/SaleTask/subpages/TaskEdit/components/ShopTask.tsx @@ -8,6 +8,7 @@ import { Button, Row, Tag, + Modal, } from "antd"; import { PlusOutlined } from "@ant-design/icons"; import * as API from "../../../api"; @@ -18,14 +19,16 @@ import EditableCell from "./EditableCell"; import SeriesModal from "./SeriesModal"; import { history, useRequest } from "umi"; import { MAX_NUM } from "../../../entity"; +import "../../../components/index.less"; const { Column } = Table; interface ShopTaskProps { form: any; + onRefresh: () => void; } -export default function ShopTask({ form }: ShopTaskProps) { +export default function ShopTask({ form, onRefresh }: ShopTaskProps) { const { shopTaskItem, isReadOnly, @@ -96,12 +99,49 @@ export default function ShopTask({ form }: ShopTaskProps) { .run({ ...other, ...values, id: taskId }) .then(() => { message.success("保存草稿成功"); + onRefresh(); }) .catch((error: any) => { message.error(error.message ?? "请求失败"); }); }; + // 分配到门店和顾问 + const autoAssignOneShop = async () => { + await form.validateFields(); + const values = form.getFieldsValue(); + Modal.confirm({ + title: ( + + 确认分配到 + 此门店和顾问 + 吗? + + ), + zIndex: 1002, + onOk: async () => { + const hide = message.loading("分配中,请稍候", 0); + const { taskId, id, ...other } = shopTaskItem!; + API.autoAssignOneShop({ + ...other, + ...values, + orderTaskApplyId: taskId, + orderShopTaskId: id, + }) + .then((res) => { + message.success("分配成功"); + onRefresh(); + }) + .catch((error: any) => { + message.error(error.message ?? "请求失败"); + }) + .finally(() => { + hide(); + }); + }, + }); + }; + const layout = { labelCol: { span: 5 }, wrapperCol: { span: 19 }, @@ -121,6 +161,7 @@ export default function ShopTask({ form }: ShopTaskProps) { labelAlign="left" form={form} initialValues={shopTaskItem!} + disabled={isReadOnly} > @@ -161,7 +201,6 @@ export default function ShopTask({ form }: ShopTaskProps) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={2} /> @@ -201,7 +240,6 @@ export default function ShopTask({ form }: ShopTaskProps) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -235,13 +273,12 @@ export default function ShopTask({ form }: ShopTaskProps) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -265,7 +301,6 @@ export default function ShopTask({ form }: ShopTaskProps) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -276,7 +311,6 @@ export default function ShopTask({ form }: ShopTaskProps) { min={0} max={MAX_NUM} style={{ width: "100%" }} - disabled={isReadOnly} precision={0} /> @@ -405,6 +439,15 @@ export default function ShopTask({ form }: ShopTaskProps) { 保存草稿 )} + {!isReadOnly && ( + + )} ( - API.getShopSaleTask, - {} as API.ShopTaskItem, - { - shopId, - taskDate, - } - ); + const { data, setParams } = useInitial< + API.ShopTaskItem, + API.GetShopSaleTaskReq + >(API.getShopSaleTask, {} as API.ShopTaskItem, { + shopId, + taskDate, + }); useEffect(() => { setShopTaskItem(data); @@ -46,21 +46,27 @@ function TaskEdit() { title={当前选择门店:{shopTaskItem?.shopName}} > setCurrStep(activeKey)} + defaultActiveKey={currTab} + onChange={(activeKey) => setCurrTab(activeKey)} items={[ { label: `门店任务分配${readOnly ? "详情" : ""}`, key: "1", children: !isEmpty(shopTaskItem) && ( - + setParams({}, true)} + /> ), }, { label: `销售顾问任务分配${readOnly ? "详情" : ""}`, key: "2", children: !isEmpty(shopTaskItem) && ( - + setParams({}, true)} + /> ), }, ]}