feat: 老客户/推荐-销售业绩

feature/hotel-cruise
Lei OT 1 year ago
parent 345a2b88d8
commit ab79cdc289

@ -46,6 +46,7 @@ import ServicePersonNum from './views/ServicePersonNum';
import DataPivot from './views/DataPivot';
import Welcome from './views/Welcome';
import Meeting2024GH from './views/Meeting2024-GH';
import SalesCustomerCareRegular from './views/SalesCustomerCareRegular';
import { stores_Context, APP_VERSION } from './config';
import { WaterMark } from '@ant-design/pro-components';
@ -107,6 +108,7 @@ const App = () => {
key: 32,
label: <NavLink to="/customer_care_regular">老客户</NavLink>,
},
{ key: 'customer_care_regular_sales', label: <NavLink to="/customer_care_regular_sales">销售-老客户</NavLink> },
{
key: 33,
label: <NavLink to="/customer_care_inchina">在华客户</NavLink>,
@ -220,6 +222,7 @@ const App = () => {
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'customer_care']} />}>
<Route path="/customer_care_inchina" element={<Customer_care_inchina />} />
<Route path="/customer_care_regular" element={<Customer_care_regular />} />
<Route path="/customer_care_regular_sales" element={<SalesCustomerCareRegular />} />
<Route path="/customer_care_potential" element={<Customer_care_potential />} />
<Route path="/whatsapp_session" element={<WhatsApp_session />} />
<Route path="/wechat_session" element={<Wechat_session />} />

@ -88,7 +88,7 @@ export const TableExportBtn = (props) => {
disabled={false}
onClick={onExport}
>
导出excel
{props.btnTxt || '导出excel'}
</Button>
);
};

@ -208,6 +208,9 @@ export const pivotBy = (_data, [rows, columns, date]) => {
ele.PPPriceRange = calcPPPriceRange(ele.PPPrice);
ele.IsOld_txt = ele.IsOld === '1' ? '老客户' : '否';
ele.isCusCommend_txt = ele.isCusCommend === '1' ? '老客户推荐' : '否';
const hasOld = (ele.IsOld === '1' || ele.isCusCommend === '1') ? 1 : 0;
ele.hasOld = hasOld;
ele.hasOld_txt = hasOld === 1 ? '老客户(推荐)' : '';
return ele;
});
// 数组的字段值, 拆分处理
@ -292,10 +295,11 @@ export const pivotBy = (_data, [rows, columns, date]) => {
// Calculations
calculatedData.tourdays = Math.ceil(calculatedData.tourdays / _len);
calculatedData.confirmTourdays = calculatedData.ConfirmOrder > 0 ? Math.ceil(calculatedData.confirmTourdays / calculatedData.ConfirmOrder) : '0';
calculatedData.applyDays = Math.ceil(calculatedData.applyDays / _len);
calculatedData.confirmDays = Math.ceil(calculatedData.confirmDays / _len);
const _rowCalc = {
ConfirmRates: calculatedData.ConfirmOrder ? fixTo4Decimals(calculatedData.ConfirmOrder / calculatedData.SumOrder) : 0,
ConfirmRates: calculatedData.ConfirmOrder ? fixTo4Decimals(calculatedData.ConfirmOrder / calculatedData.SumOrder*100) : 0,
OrderValue: calculatedData.SumOrder ? fixToInt(calculatedData.SumML / calculatedData.SumOrder) : 0,
SingleML: calculatedData.ConfirmOrder ? fixToInt(calculatedData.SumML / calculatedData.ConfirmOrder) : 0,
@ -355,6 +359,7 @@ export const pivotBy = (_data, [rows, columns, date]) => {
}),everyR);
summaryCalc.tourdays = Math.ceil(summaryCalc.tourdays / allColumns.length);
summaryCalc.confirmTourdays = summaryCalc.ConfirmOrder > 0 ? Math.ceil(summaryCalc.confirmTourdays / summaryCalc.ConfirmOrder) : '0';
summaryCalc.applyDays = Math.ceil(summaryCalc.applyDays / allColumns.length);
summaryCalc.confirmDays = Math.ceil(summaryCalc.confirmDays / allColumns.length);
summaryCalc.ConfirmRates = summaryCalc.ConfirmOrder ? fixTo2Decimals(summaryCalc.ConfirmOrder / summaryCalc.SumOrder*100) : 0;

@ -1,7 +1,15 @@
import {makeAutoObservable, runInAction} from "mobx";
import { fetchJSON } from '../utils/request';
import * as config from "../config";
import { groupsMappedByKey, dataFieldAlias, sitesMappedByCode } from './../libs/ht';
import { groupsMappedByKey, dataFieldAlias, sitesMappedByCode, pivotBy } from './../libs/ht';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
class CustomerStore {
@ -247,6 +255,39 @@ class CustomerStore {
};
// 在华客人 end
sales_regular_data = {
loading: false,
data: [],
rawData: [],
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: undefined, // ['ALL'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
},
};
get_sales_regular_data = async (param) => {
this.sales_regular_data.loading = true;
const rawData = await getDetailData({...param, IncludeTickets: 1});
const filterHasOld = rawData.filter(ele => (ele.IsOld === '1' || ele.isCusCommend === '1'));
const { data: hasOldData, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(filterHasOld, [['hasOld', 'operatorName'], [], []]);
const { data: IsOldData, } = pivotBy(filterHasOld.filter(ele => ele.IsOld === '1'), [['operatorName', 'IsOld_txt', ], [], []]);
const { data: isCusCommendData, } = pivotBy(filterHasOld.filter(ele => ele.isCusCommend === '1'), [['operatorName', 'isCusCommend_txt', ], [], []]);
// console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData);
// console.log('data====', data, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix, '\nhasOld',filterHasOld);
const mergeDataBySales = hasOldData.map((ele) => ({
...ele,
children: [].concat(
IsOldData.filter((ele1) => ele1.operatorName === ele.operatorName).map(o => ({...o, operatorName: o.IsOld_txt, key: o.rowLabel})),
isCusCommendData.filter((ele2) => ele2.operatorName === ele.operatorName).map(o => ({...o, operatorName: o.isCusCommend_txt, key: o.rowLabel}))
),
}));
// console.log('merge', mergeDataBySales);
this.sales_regular_data.loading = false;
this.sales_regular_data.data = mergeDataBySales;
this.sales_regular_data.rawData = filterHasOld;
};
setSearchValues(obj, values, target) {
this[target].groups = obj.DepartmentList;
this[target].webcode = obj.WebCode;

@ -25,6 +25,7 @@ const filterFields = [
{ key: 'WebCode', label: '来源站点' },
{ key: 'IsOld_txt', label: '是否老客户' },
{ key: 'isCusCommend_txt', label: '是否老客户推荐' },
{ key: 'hasOld_txt', label: '老客户(推荐)' },
{ key: 'destinationCountry_AsJOSN', label: '目的地国籍' },
{ key: 'PPPriceRange', label: '人均天/单区间(外币)' },
// { key: 'unitPPPriceRange', label: '()' },
@ -61,6 +62,12 @@ const pageSetting = {
childrenColumns: [
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
{ key: 'confirmTourdays', title: '✅团天数', dataIndex: 'confirmTourdays', width: '5em' },
{ key: 'SumPersonNum', title: '人数', dataIndex: 'SumPersonNum', width: '5em' },
{ key: 'ConfirmPersonNum', title: '✅人数', dataIndex: 'ConfirmPersonNum', width: '5em' },
],
searchInitial: { DateType: { key: 'applyDate', value: 'applyDate', label: '提交日期' } },
},

@ -0,0 +1,108 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Button } from 'antd';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { uniqWith } from './../utils/commons';
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const SalesCustomerCareRegular = (props) => {
const { orders_store, date_picker_store: searchFormStore, customer_store } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { sales_regular_data: pageData } = customer_store;
const [expandRowKeys, setExpandRowKeys] = useState([]);
const [dataForExport, setDataForExport] = useState([]);
useEffect(() => {
setExpandRowKeys(pageData.data.map((ele) => ele.key));
setDataForExport(pageData.data.reduce((r, c) => r.concat([{...c, children: undefined}], c.children.map(ele => ({...ele, operatorName: ele.rowLabel}))), []));
return () => {};
}, [pageData.data]);
const allOPI1 = uniqWith(
pageData.data.map((rr) => ({ text: rr.operatorName, value: rr.operatorName })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)
).sort((a, b) => a.text.localeCompare(b.text));
const rowColumns = [
{ title: '顾问', dataIndex: 'operatorName', key: 'operatorName' },
{ title: '订单号', dataIndex: 'o_id', key: 'o_id' },
{ title: '预定日期', dataIndex: 'applyDate', key: 'applyDate' },
{ title: '订单状态', key: 'orderState', render: (_, r) => r.orderState === '1' ? '成行' : '', },
{ title: '毛利', dataIndex: 'ML', key: 'ML' },
{ title: '人数', dataIndex: 'personNum', key: 'personNum' },
{ title: '天数', dataIndex: 'tourdays', key: 'tourdays' },
// { title: '', dataIndex: 'CGI_PersonDays', key: 'CGI_PersonDays' },
// { title: '', dataIndex: 'COLI_OrderStartDate', key: 'COLI_OrderStartDate' },
{ title: '小组', dataIndex: 'dept', key: 'dept' },
{ title: '老客户', key: 'IsOld', render: (_, r) => r.IsOld === '1' ? '是' : '' },
{ title: '老客户推荐', key: 'IsCusCommend', render: (_, r) => r.IsCusCommend === '1' ? '是' : '' },
{ title: '网站', dataIndex: 'WebCode', key: 'WebCode' },
{ title: '来源', dataIndex: 'SourceType', key: 'SourceType' },
{ title: '页面类型', dataIndex: 'COLI_LineClass', key: 'COLI_LineClass' },
];
const columns = [
{ key: 'operatorName', title: '顾问', dataIndex: 'operatorName', width: '6em', filters: allOPI1, onFilter: (value, record) => record.operatorName === value, filterSearch: true },
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
// { key: 'ConfirmPersonNum', title: '(SUM)', dataIndex: 'ConfirmPersonNum', width: '5em' },
// { key: 'confirmTourdays', title: '(AVG)', dataIndex: 'confirmTourdays', width: '5em' },
{ key: 'SumML', title: '预计毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'action', title: '', width: '5em', render: (_, r) => {
const rowChildren = pageData.rawData.filter(ele => ele.operatorName === r.operatorName);
return r.hasOld ? <TableExportBtn btnTxt='导出' label={`老客户-${r.operatorName}`} {...{ columns: rowColumns, dataSource: rowChildren }} /> : null;
} },
];
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...formValues,
...pageData.searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'sales_regular_data');
customer_store.get_sales_regular_data(obj);
// MeetingDataStore.setSearchValues(form);
// dataRefresh(obj);
}}
/>
</Col>
</Row>
<h2>销售-老客户, 含推荐</h2>
<Divider orientation="right" plain>
<TableExportBtn btnTxt='导出明细' label={`${formValuesToSub.Date1}-销售.老客户-明细`} {...{ columns: rowColumns, dataSource: pageData.rawData }} />
<Divider type={'vertical'} />
<TableExportBtn btnTxt='导出下表' label={`${formValuesToSub.Date1}-销售.老客户`} {...{ columns, dataSource: dataForExport }} />
</Divider>
<Table
sticky
dataSource={pageData.data}
loading={pageData.loading}
columns={columns}
pagination={false}
defaultExpandAllRows
expandable={{ defaultExpandAllRows: true, defaultExpandedRowKeys: pageData.data.map((ele) => ele.key) }}
/>
</>
);
};
export default observer(SalesCustomerCareRegular);
Loading…
Cancel
Save