diff --git a/api/app/lib/controllers/salePerformance/index.js b/api/app/lib/controllers/salePerformance/index.js new file mode 100644 index 0000000..0659c4d --- /dev/null +++ b/api/app/lib/controllers/salePerformance/index.js @@ -0,0 +1,27 @@ +'use strict'; + +// 查询业绩汇总表-->关联sale表 +async function getSalePerformance(ctx, next) { + const { type } = ctx.params; + const models = ctx.fs.dc.models; + let rslt = null; + try { + rslt = await models.sale.findAll({ + order: [['id', 'DESC']], + include:[{ + model:models.salePerformance + }] + // where: { type: type } + }) + ctx.status = 200 + ctx.body = rslt + } catch (error) { + ctx.fs.logger.error(`path:${ctx.path},error:${error}`) + ctx.status = 400; + ctx.body = { name: 'FindAllError', message: '获取失败' } + } +} + +module.exports = { + getSalePerformance, +} \ No newline at end of file diff --git a/api/app/lib/models/customerContactsFollup.js b/api/app/lib/models/customerContactsFollup.js index 0c5a119..1184230 100644 --- a/api/app/lib/models/customerContactsFollup.js +++ b/api/app/lib/models/customerContactsFollup.js @@ -7,7 +7,7 @@ module.exports = dc => { const CustomerContactsFollowup = sequelize.define("customerContactsFollowup", { id: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, primaryKey: true, field: "id", autoIncrement: true, @@ -19,7 +19,7 @@ module.exports = dc => { }, items: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "items", }, department: { @@ -29,7 +29,7 @@ module.exports = dc => { }, sale: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "sale", }, updatetime: { @@ -39,22 +39,22 @@ module.exports = dc => { }, customerContacts: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "customer_contacts", }, phone: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "phone", }, visitStyle: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "visit_style", }, itemText: { type: DataTypes.STRING, - allowNull: true, + allowNull: false, field: "item_text", } }, { diff --git a/api/app/lib/models/sale.js b/api/app/lib/models/sale.js new file mode 100644 index 0000000..8afd2d5 --- /dev/null +++ b/api/app/lib/models/sale.js @@ -0,0 +1,50 @@ +/* eslint-disable*/ +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const sale = sequelize.define("sale", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: "id", + autoIncrement: true, + }, + department: { + type: DataTypes.STRING, + allowNull: true, + field: "department", + }, + business: { + type: DataTypes.STRING, + allowNull: true, + field: "business", + }, + sale: { + type: DataTypes.STRING, + allowNull: true, + field: "sale", + }, + hiredate: { + type: DataTypes.DATE, + allowNull: false, + field: "hiredate", + }, + regularDate: { + type: DataTypes.DATE, + allowNull: false, + field: "regular_date", + }, + pepId: { + type: DataTypes.INTEGER, + allowNull: true, + field: "pep_id", + } + }, { + tableName: "sale", + }); + dc.models.sale = sale; + return sale; +}; \ No newline at end of file diff --git a/api/app/lib/models/salePerformance.js b/api/app/lib/models/salePerformance.js new file mode 100644 index 0000000..c987425 --- /dev/null +++ b/api/app/lib/models/salePerformance.js @@ -0,0 +1,63 @@ +/* eslint-disable*/ +'use strict'; + +module.exports = dc => { + const DataTypes = dc.ORM; + const sequelize = dc.orm; + const salePerformance = sequelize.define("salePerformance", { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: "id", + autoIncrement: true, + }, + amount: { + type: DataTypes.STRING, + allowNull: false, + field: "amount", + }, + actualPerformance: { + type: DataTypes.STRING, + allowNull: false, + field: "actual_performance", + }, + assessmentPerformance: { + type: DataTypes.STRING, + allowNull: false, + field: "assessment_performance", + }, + saleName : { + type: DataTypes.STRING, + allowNull: false, + field: "sale_name", + }, + month: { + type: DataTypes.INTEGER, + allowNull: false, + field: "month", + }, + year: { + type: DataTypes.INTEGER, + allowNull: false, + field: "year", + }, + saleId: { + type: DataTypes.INTEGER, + allowNull: false, + field: "sale_id", + }, + task: { + type: DataTypes.INTEGER, + allowNull: false, + field: "task", + } + }, { + tableName: "sale_performance", + }); + const { sale } = dc.models; + sale.hasMany(salePerformance, { foreighKey: 'saleName', targetKey: "sale" }) + + dc.models.salePerformance = salePerformance; + return salePerformance; +}; \ No newline at end of file diff --git a/api/app/lib/routes/salePerformance/index.js b/api/app/lib/routes/salePerformance/index.js new file mode 100644 index 0000000..a8889b0 --- /dev/null +++ b/api/app/lib/routes/salePerformance/index.js @@ -0,0 +1,8 @@ +'use strict'; + +const report = require('../../controllers/salePerformance'); + +module.exports = function (app, router, opts) { + app.fs.api.logAttr['GET/salePerformance'] = { content: '业绩汇总表', visible: false }; + router.get('/salePerformance', report.getSalePerformance); +}; \ No newline at end of file diff --git a/api/config.js b/api/config.js index d4891d1..de1647c 100644 --- a/api/config.js +++ b/api/config.js @@ -55,6 +55,9 @@ const CLICKHOUST_PASSWORD = process.env.CLICKHOUST_PASSWORD || flags.clickHouseP const CLICKHOUST_PEP_EMIS = process.env.CLICKHOUST_PEP_EMIS || flags.clickHousePepEmis const CLICKHOUST_HR = process.env.CLICKHOUST_HR || flags.clickHouseHr + + + if ( !RC_DB || !IOTA_REDIS_SERVER_HOST || !IOTA_REDIS_SERVER_PORT diff --git a/web/client/src/layout/components/header/index.jsx b/web/client/src/layout/components/header/index.jsx index 919a679..1941b7d 100644 --- a/web/client/src/layout/components/header/index.jsx +++ b/web/client/src/layout/components/header/index.jsx @@ -7,99 +7,91 @@ import { headerItems } from './contant'; import "./index.less"; const Header = (props) => { - const { dispatch, history, user, actions, socket, tochange } = props; + const { dispatch, history, user, actions, socket, tochange } = props; - return ( - <> -
-
- - ); + + } + /> + + + ); }; -function mapStateToProps(state) { - const { global, auth, webSocket } = state; - return { - actions: global.actions, - user: auth.user, - socket: webSocket.socket, - }; +function mapStateToProps (state) { + const { global, auth, webSocket } = state; + return { + actions: global.actions, + user: auth.user, + socket: webSocket.socket, + }; } export default connect(mapStateToProps)(Header); diff --git a/web/client/src/sections/auth/containers/login.jsx b/web/client/src/sections/auth/containers/login.jsx index 4daa853..6d962a6 100644 --- a/web/client/src/sections/auth/containers/login.jsx +++ b/web/client/src/sections/auth/containers/login.jsx @@ -8,7 +8,7 @@ import { IconLock, IconUser } from '@douyinfe/semi-icons'; import '../style.less' const Login = props => { - const { dispatch, user, error, actions, apiRoot, isRequesting } = props + const { dispatch, user, error, actions, apiRoot, isRequesting,webPepUrl } = props const form = useRef(); useEffect(() => { @@ -23,6 +23,8 @@ const Login = props => { dispatch(push('/businessManagement/pmReport/reserveItemsReporting')); localStorage.setItem('poms_open_sider', JSON.stringify(["pmReport"])) localStorage.setItem('poms_selected_sider', JSON.stringify(["businessManagement"])) + }else{ + window.location.href = `${webPepUrl}/signin` } }, [user]) @@ -110,7 +112,8 @@ function mapStateToProps(state) { error: auth.error, actions: global.actions, apiRoot: global.apiRoot, - isRequesting: auth.isRequesting + isRequesting: auth.isRequesting, + webPepUrl: global.webPepUrl, } } diff --git a/web/client/src/sections/business/actions/index.js b/web/client/src/sections/business/actions/index.js index 37f28f9..652c1b2 100644 --- a/web/client/src/sections/business/actions/index.js +++ b/web/client/src/sections/business/actions/index.js @@ -3,9 +3,11 @@ import * as reserveItem from './reserve-item'; import * as salersReport from './salers-report'; import * as achievementReport from './achievement-report'; import * as getCustomerContactsFollowup from './customerContactFollowup' +import * as getPerformanceSummary from './performanceSummary' export default { ...reserveItem, ...salersReport, ...achievementReport, - ...getCustomerContactsFollowup + ...getCustomerContactsFollowup, + ...getPerformanceSummary } \ No newline at end of file diff --git a/web/client/src/sections/business/actions/performanceSummary.js b/web/client/src/sections/business/actions/performanceSummary.js new file mode 100644 index 0000000..7d2e6fb --- /dev/null +++ b/web/client/src/sections/business/actions/performanceSummary.js @@ -0,0 +1,16 @@ +'use strict'; + +import { ApiTable, basicAction } from '$utils' + +export function getPerformanceSummary() {//查询 + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_PERFORMANCE_SUMMARY", + url: `${ApiTable.getPerformanceSummary}`, + msg: { option: "查询业绩汇总表" }, + reducer: { name: "performanceSummaryList", params: { noClear: true } }, + }); +} + + diff --git a/web/client/src/sections/business/constants/index.js b/web/client/src/sections/business/constants/index.js index 43e8e2c..d6de706 100644 --- a/web/client/src/sections/business/constants/index.js +++ b/web/client/src/sections/business/constants/index.js @@ -119,4 +119,56 @@ export const achievementColumnKeys = { outProvince: '省外业务', repurchase: '复购业务', isreproduce: '可复制的业务路径', +} + +export const performanceSummaryKeys = { + oneAmount:'一月合同金额', + oneActualPerformance:'一月实际业绩', + oneAssessmentPerformance:'一月考核业绩', + oneTask:'一月销售任务', + twoAmount:'二月合同金额', + twoActualPerformance:'二月实际业绩', + twoAssessmentPerformance:'二月考核业绩', + twoTask:'二月销售任务', + threeAmount:'三月合同金额', + threeActualPerformance:'三月实际业绩', + threeAssessmentPerformance:'三月考核业绩', + threeTask:'三月销售任务', + fourAmount:'四月合同金额', + fourActualPerformance:'四月实际业绩', + fourAssessmentPerformance:'四月考核业绩', + fourTask:'四月销售任务', + fiveActualPerformance:'五月实际业绩', + fiveAssessmentPerformance:'五月考核业绩', + fiveTask:'五月销售任务', + fiveAmount:'五月合同金额', + sixAmount:'六月合同金额', + sixActualPerformance:'六月实际业绩', + sixAssessmentPerformance:'六月考核业绩', + sixTask:'六月销售任务', + sevenAmount:'七月合同金额', + sevenActualPerformance:'七月实际业绩', + sevenAssessmentPerformance:'七月考核业绩', + sevenTask:'七月销售任务', + eightAmount:'八月合同金额', + eightActualPerformance:'八月实际业绩', + eightAssessmentPerformance:'八月考核业绩', + eightTask:'八月销售任务', + nineAmount:'九月合同金额', + nineActualPerformance:'九月实际业绩', + nineAssessmentPerformance:'九月考核业绩', + nineTask:'九月销售任务', + tenAmount:'十月合同金额', + tenActualPerformance:'十月实际业绩', + tenAssessmentPerformance:'十月考核业绩', + tenTask:'十月销售任务', + elevenActualPerformance:'十一月实际业绩', + elevenAssessmentPerformance:'十一月考核业绩', + elevenTask:'十一月销售任务', + elevenAmount:'十一月合同金额', + twelveAmount:'十二月合同金额', + twelveActualPerformance:'十二月实际业绩', + twelveAssessmentPerformance:'十二月考核业绩', + twelveTask:'十二月销售任务', + name:'销售人员' } \ No newline at end of file diff --git a/web/client/src/sections/business/containers/customer/customerContactFollowup.jsx b/web/client/src/sections/business/containers/customer/customerContactFollowup.jsx index 46cf5b7..f17b597 100644 --- a/web/client/src/sections/business/containers/customer/customerContactFollowup.jsx +++ b/web/client/src/sections/business/containers/customer/customerContactFollowup.jsx @@ -48,7 +48,7 @@ const CustomerContactFollowup = (props) => { render: (text, record) => text == null ? '---' : moment(text).format('YYYY-MM-DD') }, { - title: '客户联系人', + title: '联系人', dataIndex: 'customerContacts', render: (text, record) => text == null ? '---' : text }, @@ -63,7 +63,7 @@ const CustomerContactFollowup = (props) => { render: (text, record) => text == null ? '---' : text }, { - title: '项目进展', + title: '跟进内容', dataIndex: 'itemText', render: (text, record) => text == null ? '---' : text } diff --git a/web/client/src/sections/business/containers/index.js b/web/client/src/sections/business/containers/index.js index b381139..6f25709 100644 --- a/web/client/src/sections/business/containers/index.js +++ b/web/client/src/sections/business/containers/index.js @@ -9,7 +9,8 @@ import InvoicingDetails from './performanceReport/invoicingDetails'; import BackMoneyDetails from './performanceReport/backMoneyDetails'; import AchievementDetails from './performanceReport/achievementDetails'; import SalesDistributionDetails from './salesReport/salesDistributionDetails'; -import CustomerContactFollowup from './customer/customerContactFollowup' +import CustomerContactFollowup from './customer/customerContactFollowup'; +import PerformanceSummary from './performanceReport/performanceSummary' export { ReserveItemsReporting, @@ -21,5 +22,6 @@ export { BackMoneyDetails, AchievementDetails, SalesDistributionDetails, - CustomerContactFollowup + CustomerContactFollowup, + PerformanceSummary }; \ No newline at end of file diff --git a/web/client/src/sections/business/containers/performanceReport/importPerformanceSummaryModal.jsx b/web/client/src/sections/business/containers/performanceReport/importPerformanceSummaryModal.jsx new file mode 100644 index 0000000..3c1242b --- /dev/null +++ b/web/client/src/sections/business/containers/performanceReport/importPerformanceSummaryModal.jsx @@ -0,0 +1,225 @@ +'use strict'; +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment'; +import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; +import { IconUpload } from '@douyinfe/semi-icons'; +import XLSX from 'xlsx'; +import { performanceSummaryKeys } from '../../constants/index'; +//下载模板和上传文件读取 +const ImportPerformanceSummaryModal = props => { + const { dispatch, actions, onCancel } = props; + const { businessManagement } = actions + const [msg, setMsg] = useState(''); + const [loading, setLoading] = useState(''); + const [postData, setPostData] = useState([]); + //初始化 + useEffect(() => { + }, []); + + const confirm = () => { + if (postData.length) { + setLoading(true) + dispatch(businessManagement.importAchieveDetails(postData)).then(res => { + if (res.success) { + onCancel() + } + setLoading(false) + }) + } else { + Notification.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) + } + } + + const dldCsvMb = () => { + //表头 + let head = []; + Object.keys(performanceSummaryKeys).map(key => { + head.push(performanceSummaryKeys[key]); + }) + head = head.join(',') + "\n"; + //数据 + //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5 + let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head; + //创建一个a标签 + let link = document.createElement("a"); + //为a标签设置属性 + link.setAttribute("href", templateCsv); + link.setAttribute("download", "业绩汇总表导入模板.csv"); + //点击a标签 + link.click(); + } + const download = () => { + dldCsvMb(); + } + + const fileLimit = '.csv'; + + const getFileBlob = (url) => { + return new Promise((resolve, reject) => { + let request = new XMLHttpRequest() + request.open("GET", url, true) + request.responseType = "blob" + request.onreadystatechange = e => { + if (request.readyState == 4) { + if (request.status == 200) { + if (window.FileReader) { + let reader = new FileReader(); + reader.readAsBinaryString(request.response); + reader.onload = event => { + try { + const { result } = event.target; + // 以二进制流方式读取得到整份excel表格对象 + const workbook = XLSX.read(result, { + type: "binary", + cellDates: true,//设为true,将天数的时间戳转为时间格式 + codepage: 936//解决了乱码问题 + }); + let data = []; // 存储获取到的数据 + // 遍历每张工作表进行读取(这里默认只读取第一张表) + for (const sheet in workbook.Sheets) { + if (workbook.Sheets.hasOwnProperty(sheet)) { + data = data.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet])); + } + } + resolve(data);//导出数据 + } catch (e) { + reject("失败"); + } + } + } + } + } + } + request.send(); + }) + } + + + // const judgeNull = (value) => { + // return value ? String(value).trim().replace(/\s*/g, "") : null; + // } + + // const judgeNullTime = (v) => { + // //注意的点:xlsx将excel中的时间内容解析后,会小一天 + // //如2020/8/1,xlsx会解析成 Fri Jul 31 2020 23:59:17 GMT+0800 小了43秒 + // let a = new Date(v); + // a.setTime(a.getTime() + 43 * 1000); + // return v ? a : null; + // } + + const judgeTimeValid = (time) => { + let valid = true; + if (!time) { + return valid;//可以不填 + } + const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日 + if (time instanceof Date) { + let timeStr = moment(time).format('YYYY/MM/DD'); + if (!ymd.test(timeStr)) { + valid = false; + } + } else { + valid = false; + } + return valid; + } + + return ( + { + setMsg('') + setLoading(false) + setPostData([]) + onCancel() + }} + > +
+
+ { + setMsg(''); + setPostData([]); + }} + customRequest={(data) => { + const { file, onSuccess, onError } = data + getFileBlob(file.url).then(res => { + + const error = (msg) => { + setMsg(msg) + onError({ message: msg }) + } + if (res.length > 1000) { + error('一次性上传数据行数应小于1000行,请分批上传') + return + } + if (!res.length) { + error('请填写至少一行数据') + return + } + let postData = []; + let zzsPattern = /^[+]{0,1}(\d+)$/;//正整数 + for (let i = 0; i < res.length; i++) { + let d = res[i]; + let obj = {}; + Object.keys(performanceSummaryKeys).map(key => { + obj[key] = d[performanceSummaryKeys[key]] || null; + //} + }) + let tValid = judgeTimeValid(obj.recConDate); + // if (!tValid) { + // error(`第${i + 2}行【收到合同日期】错误,请填写yyyy/mm/dd格式`) + // return + // } + + // if (obj.isApproval && ['是', '否'].indexOf(obj.isApproval) == -1) { + // error(`第${i + 2}行【价格是否特批】错误,请填写是或否`) + // return + // } + // //复购次数 正整数 + // if (obj.repurchaseCount && !zzsPattern.test(obj.repurchaseCount)) { + // error(`第${i + 2}行【复购次数】填写错误,需要为非负整数`) + // return + // } + + // if (obj.reproducible && ['是', '否'].indexOf(obj.reproducible) == -1) { + // error(`第${i + 2}行【是否可复制的业务路径】错误,请填写是或否`) + // return + // } + postData.push(obj); + } + setPostData(postData) + console.log(postData,'======================='); + let msg = '文件解析完成,点击确定按钮上传保存!' + setMsg(msg) + onSuccess({ message: msg }) + }) + }}> + + + {msg} +
最大不超过200M,导入文件需与 + download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板 + 一致
+
+
+ ) +} + +function mapStateToProps(state) { + const { auth, global } = state; + return { + user: auth.user, + actions: global.actions, + } +} + +export default connect(mapStateToProps)(ImportPerformanceSummaryModal); \ No newline at end of file diff --git a/web/client/src/sections/business/containers/performanceReport/performanceSummary.jsx b/web/client/src/sections/business/containers/performanceReport/performanceSummary.jsx new file mode 100644 index 0000000..e391c87 --- /dev/null +++ b/web/client/src/sections/business/containers/performanceReport/performanceSummary.jsx @@ -0,0 +1,487 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment' +import { Table, } from '@douyinfe/semi-ui'; +import '../../style.less'; +import ImportPerformanceSummaryModal from './importPerformanceSummaryModal' +import FileSaver from 'file-saver' + +const AchievementDetails = (props) => { + const { dispatch, actions, performanceSummaryList } = props + const [importModalV, setImportModalV] = useState(false); + useEffect(() => { + dispatch(actions.businessManagement.getPerformanceSummary()); + }, []); + let colums = [{ num: '1月', name: 'one' }, { num: '2月', name: 'two' }, { num: '3月', name: 'three' }, { num: '4月', name: 'four' }, { num: '5月', name: 'five' }, { num: '6月', name: 'six' }, { num: '7月', name: 'seven' }, { num: '8月', name: 'eight' }, { num: '9月', name: 'nine' }, { num: '10月', name: 'ten' }, { num: '11月', name: 'eleven' }, { num: '12月', name: 'twelve' }] + const columnsList = () => { + colums.forEach(e => { + columns.push({ + title: e.num, + children: [ + { + title: '合同金额', + dataIndex: e.name + '[amount]', + width: 130, + render: (text, record, index) => text + }, + { + title: '实际业绩', + dataIndex: e.name + '[actualPerformance]', + width: 130, + render: (text, record, index) => text + }, + { + title: '考核业绩', + dataIndex: e.name + '[assessmentPerformance]', + width: 130, + render: (text, record, index) => text + }, + { + title: '销售任务', + dataIndex: e.name + '[task]', + width: 130, + render: (text, record, index) => text + }, + { + title: '完成率%', + dataIndex: e.name + '[completion]', + width: 130, + render: (text, record, index) => { + let assessmentPerformance = record[e.name] ? record[e.name].assessmentPerformance : 0 + let task = record[e.name] ? record[e.name].task : 0 + return assessmentPerformance == 0 || task == 0 ? 0 : assessmentPerformance / task + } + } + ] + } + ) + }) + } + let columns = [ + { + title: '部门', + dataIndex: 'department', + width: 130, + // render: (text, record, index) => index + 1 + }, + { + title: '销售人员', + dataIndex: 'sale', + width: 130, + // render: (text, record, index) => index + 1 + }, + { + title: '业务线', + dataIndex: 'business', + width: 130, + // render: (text, record, index) => index + 1 + }, + { + title: '入职日期', + dataIndex: 'hiredate', + width: 130, + render: (text, record, index) => moment(text).format('YYYY-MM-DD') + }, + { + title: '转正日期', + dataIndex: 'regularDate', + width: 130, + render: (text, record, index) => moment(text).format('YYYY-MM-DD') + }, + { + title: '工龄', + dataIndex: 'workingYears', + width: 130, + render: (text, record, index) => { + let days = moment().diff(moment(record.hiredate).format('YYYY-MM-DD'), 'days') + let day = days / 365 + return day.toFixed(1) + } + }, + ] + columnsList() + let arr = performanceSummaryList.map(e => { + e.salePerformances.forEach(i => { + if (i.month == 1) { + e.one = i + } + if (i.month == 2) { + e.two = i + } + if (i.month == 3) { + e.three = i + } + if (i.month == 4) { + e.four = i + } + if (i.month == 5) { + e.five = i + } + if (i.month == 6) { + e.six = i + } + if (i.month == 7) { + e.seven = i + } + if (i.month == 8) { + e.eight = i + } + if (i.month == 9) { + e.nine = i + } + if (i.month == 10) { + e.ten = i + } + if (i.month == 11) { + e.eleven = i + } + if (i.month == 12) { + e.twelve = i + } + }) + return e + }) + const exportDetail = () => { + let tableStyle = "text-align: center;font-size:21px" + let exportTable = ` + +
部门
+
销售人员
+
业务线
+
入职日期
+
转正日期
+
工龄
+
1月
+
2月
+
3月
+
一季度
+
4月
+
5月
+
6月
+
二季度
+
7月
+
8月
+
9月
+
三季度
+
10月
+
11月
+
12月
+
四季度
+
四季度
+
扣除转法务业绩金额
+
2022年最终业绩合计
+ + +
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
合同金额
+
实际业绩
+
考核业绩
+
销售任务
+
完成率
+
销售任务
+
完成率
+
合同金额合计
+
实际业绩合计
+
考核业绩合计
+ + `; + //无数据 + if (JSON.stringify(arr) == '[]') { + message.warning('暂无导出的数据') + } else { + let allList = arr.map(item => { + const oneAssessmentPerformance = item.one && item.one.assessmentPerformance; + const oneTask = item.one && item.one.task; + const oneCompletion = oneAssessmentPerformance && oneTask && oneAssessmentPerformance / oneTask; + const twoAssessmentPerformance = item.two && item.two.assessmentPerformance; + const twoTask = item.two && item.two.task; + const twoCompletion = twoAssessmentPerformance && twoTask && twoAssessmentPerformance / twoTask; + const threeAssessmentPerformance = item.three && item.three.assessmentPerformance; + const threeTask = item.three && item.three.task; + const threeCompletion = threeAssessmentPerformance && threeTask && threeAssessmentPerformance / threeTask; + const fourAssessmentPerformance = item.four && item.four.assessmentPerformance; + const fourTask = item.four && item.four.task; + const fourCompletion = fourAssessmentPerformance && fourTask && fourAssessmentPerformance / fourTask; + const fiveAssessmentPerformance = item.five && item.five.assessmentPerformance; + const fiveTask = item.five && item.five.task; + const fiveCompletion = fiveAssessmentPerformance && fiveTask && fiveAssessmentPerformance / fiveTask; + const sixAssessmentPerformance = item.six && item.six.assessmentPerformance; + const sixTask = item.six && item.six.task; + const sixCompletion = sixAssessmentPerformance && sixTask && sixAssessmentPerformance / sixTask; + const sevenAssessmentPerformance = item.seven && item.seven.assessmentPerformance; + const sevenTask = item.seven && item.seven.task; + const sevenCompletion = sevenAssessmentPerformance && sevenTask && sevenAssessmentPerformance / sevenTask; + const eightAssessmentPerformance = item.eight && item.eight.assessmentPerformance; + const eightTask = item.eight && item.eight.task; + const eightCompletion = eightAssessmentPerformance && eightTask && eightAssessmentPerformance / eightTask; + const nineAssessmentPerformance = item.nine && item.nine.assessmentPerformance; + const nineTask = item.nine && item.nine.task; + const nineCompletion = nineAssessmentPerformance && nineTask && nineAssessmentPerformance / nineTask; + const tenAssessmentPerformance = item.ten && item.ten.assessmentPerformance; + const tenTask = item.ten && item.ten.task; + const tenCompletion = tenAssessmentPerformance && tenTask && tenAssessmentPerformance / tenTask; + const elevenAssessmentPerformance = item.eleven && item.eleven.assessmentPerformance; + const elevenTask = item.eleven && item.eleven.task; + const elevenCompletion = elevenAssessmentPerformance && elevenTask && elevenAssessmentPerformance / elevenTask; + const twelveAssessmentPerformance = item.twelve && item.twelve.assessmentPerformance; + const twelveTask = item.twelve && item.twelve.task; + const twelveCompletion = twelveAssessmentPerformance && twelveTask && twelveAssessmentPerformance / twelveTask; + + let days = moment().diff(moment(item.hiredate).format('YYYY-MM-DD'), 'days') + let day = days / 365 + const workingYears = day.toFixed(1) + return Object.assign({}, item, { + oneCompletion: oneCompletion, + twoCompletion: twoCompletion, + threeCompletion: threeCompletion, + oneQuarterTaskNnu: Number(oneTask ? oneTask : 0) + Number(twoTask ? twoTask : 0) + Number(threeTask ? threeTask : 0), + oneQuarterTask: (oneAssessmentPerformance || twoAssessmentPerformance || threeAssessmentPerformance) && (oneTask || twoTask || threeTask) && (oneAssessmentPerformance || 0 + twoAssessmentPerformance || 0 + threeAssessmentPerformance || 0) / (oneTask || 0 + twoTask || 0 + threeTask || 0), + fourCompletion: fourCompletion, + fiveCompletion: fiveCompletion, + sixCompletion: sixCompletion, + twoQuarterTaskNnu: Number(fourTask ? fourTask : 0) + Number(fiveTask ? fiveTask : 0) + Number(sixTask ? sixTask : 0), + twoQuarterTask: (fourAssessmentPerformance || fiveAssessmentPerformance || sixAssessmentPerformance) && (fourTask || fiveTask || sixTask) && (fourAssessmentPerformance || 0 + fiveAssessmentPerformance || 0 + sixAssessmentPerformance || 0) / (fourTask || 0 + fiveTask || 0 + sixTask || 0), + sevenCompletion: sevenCompletion, + eightCompletion: eightCompletion, + nineCompletion: nineCompletion, + threeQuarterTaskNnu: Number(sevenTask ? sevenTask : 0) + Number(eightTask ? eightTask : 0) + Number(nineTask ? nineTask : 0), + threeQuarterTask: (sevenAssessmentPerformance || eightAssessmentPerformance || nineAssessmentPerformance) && (sevenTask || eightTask || nineTask) && (sevenAssessmentPerformance || 0 + eightAssessmentPerformance || 0 + nineAssessmentPerformance || 0) / (sevenTask || 0 + eightTask || 0 + nineTask || 0), + tenCompletion: tenCompletion, + elevenCompletion: elevenCompletion, + twelveCompletion: twelveCompletion, + fourQuarterTaskNnu: Number(tenTask ? tenTask : 0) + Number(elevenTask ? elevenTask : 0) + Number(twelveTask ? twelveTask : 0), + fourQuarterTask: (tenAssessmentPerformance || elevenAssessmentPerformance || twelveAssessmentPerformance) && (tenTask || elevenTask || twelveTask) && (tenAssessmentPerformance || 0 + elevenAssessmentPerformance || 0 + twelveAssessmentPerformance || 0) / (tenTask || 0 + elevenTask || 0 + twelveTask || 0), + workingYears: workingYears, + allAmount: Number(item.one ? item.one.amount : 0) + Number(item.two ? item.two.amount : 0) + Number(item.three ? item.three.amount : 0) + Number(item.four ? item.four.amount : 0) + Number(item.five ? item.five.amount : 0) + Number(item.six ? item.six.amount : 0) + Number(item.seven ? item.seven.amount : 0) + Number(item.eight ? item.eight.amount : 0) + Number(item.nine ? item.nine.amount : 0) + Number(item.ten ? item.ten.amount : 0) + Number(item.eleven ? item.eleven.amount : 0) + Number(item.twelve ? item.twelve.amount : 0), + + allactualPerformance: Number(item.one ? item.one.actualPerformance : 0) + Number(item.two ? item.two.actualPerformance : 0) + Number(item.three ? item.three.actualPerformance : 0) + Number(item.four ? item.four.actualPerformance : 0) + Number(item.five ? item.five.actualPerformance : 0) + Number(item.six ? item.six.actualPerformance : 0) + Number(item.seven ? item.seven.actualPerformance : 0) + Number(item.eight ? item.eight.actualPerformance : 0) + Number(item.nine ? item.nine.actualPerformance : 0) + Number(item.ten ? item.ten.actualPerformance : 0) + Number(item.eleven ? item.eleven.actualPerformance : 0) + Number(item.twelve ? item.twelve.actualPerformance : 0), + + allassessmentPerformance: Number(item.one ? item.one.assessmentPerformance : 0) + Number(item.two ? item.two.assessmentPerformance : 0) + Number(item.three ? item.three.assessmentPerformance : 0) + Number(item.four ? item.four.assessmentPerformance : 0) + Number(item.five ? item.five.assessmentPerformance : 0) + Number(item.six ? item.six.assessmentPerformance : 0) + Number(item.seven ? item.seven.assessmentPerformance : 0) + Number(item.eight ? item.eight.assessmentPerformance : 0) + Number(item.nine ? item.nine.assessmentPerformance : 0) + Number(item.ten ? item.ten.assessmentPerformance : 0) + Number(item.eleven ? item.eleven.assessmentPerformance : 0) + Number(item.twelve ? item.twelve.assessmentPerformance : 0), + }) + }) + for (let d of allList) { + exportTable += ` + +
${d['department']}
+
${d['sale']}
+
${d['business']}
+
${d['hiredate']}
+
${d['regularDate']}
+
${d['workingYears']}
+ +
${d.one ? d.one.amount : ''}
+
${d.one ? d.one.actualPerformance : ''}
+
${d.one ? d.one.assessmentPerformance : ''}
+
${d.one ? d.one.task : ''}
+
${d.oneCompletion ? d.oneCompletion : ''}
+
${d.two ? d.two.amount : ''}
+
${d.two ? d.two.actualPerformance : ''}
+
${d.two ? d.two.assessmentPerformance : ''}
+
${d.two ? d.two.task : ''}
+
${d.twoCompletion ? d.twoCompletion : ''}
+
${d.three ? d.three.amount : ''}
+
${d.three ? d.three.actualPerformance : ''}
+
${d.three ? d.three.assessmentPerformance : ''}
+
${d.three ? d.three.task : ''}
+
${d.threeCompletion ? d.threeCompletion : ''}
+
${d.oneQuarterTaskNnu == 0 ? '' : d.oneQuarterTaskNnu}
+
${d.oneQuarterTask ? d.oneQuarterTask : ''}
+ +
${d.four ? d.four.amount : ''}
+
${d.four ? d.four.actualPerformance : ''}
+
${d.four ? d.four.assessmentPerformance : ''}
+
${d.four ? d.four.task : ''}
+
${d.fourCompletion ? d.fourCompletion : ''}
+
${d.five ? d.five.amount : ''}
+
${d.five ? d.five.actualPerformance : ''}
+
${d.five ? d.five.assessmentPerformance : ''}
+
${d.five ? d.five.task : ''}
+
${d.fiveCompletion ? d.fiveCompletion : ''}
+
${d.six ? d.six.amount : ''}
+
${d.six ? d.six.actualPerformance : ''}
+
${d.six ? d.six.assessmentPerformance : ''}
+
${d.six ? d.six.task : ''}
+
${d.sixCompletion ? d.sixCompletion : ''}
+
${d.twoQuarterTaskNnu == 0 ? '' : d.twoQuarterTaskNnu}
+
${d.twoQuarterTask ? d.twoQuarterTask : ''}
+ +
${d.seven ? d.seven.amount : ''}
+
${d.seven ? d.seven.actualPerformance : ''}
+
${d.seven ? d.seven.assessmentPerformance : ''}
+
${d.seven ? d.seven.task : ''}
+
${d.sevenCompletion ? d.sevenCompletion : ''}
+
${d.eight ? d.eight.amount : ''}
+
${d.eight ? d.eight.actualPerformance : ''}
+
${d.eight ? d.eight.assessmentPerformance : ''}
+
${d.eight ? d.eight.task : ''}
+
${d.eightCompletion ? d.eightCompletion : ''}
+
${d.nine ? d.nine.amount : ''}
+
${d.nine ? d.nine.actualPerformance : ''}
+
${d.nine ? d.nine.assessmentPerformance : ''}
+
${d.nine ? d.nine.task : ''}
+
${d.nineCompletion ? d.nineCompletion : ''}
+
${d.threeQuarterTaskNnu == 0 ? '' : d.threeQuarterTaskNnu}
+
${d.threeQuarterTask ? d.threeQuarterTask : ''}
+ +
${d.ten ? d.ten.amount : ''}
+
${d.ten ? d.ten.actualPerformance : ''}
+
${d.ten ? d.ten.assessmentPerformance : ''}
+
${d.ten ? d.ten.task : ''}
+
${d.tenCompletion ? d.tenCompletion : ''}
+
${d.eleven ? d.eleven.amount : ''}
+
${d.eleven ? d.eleven.actualPerformance : ''}
+
${d.eleven ? d.eleven.assessmentPerformance : ''}
+
${d.eleven ? d.eleven.task : ''}
+
${d.elevenCompletion ? d.elevenCompletion : ''}
+
${d.twelve ? d.twelve.amount : ''}
+
${d.twelve ? d.twelve.actualPerformance : ''}
+
${d.twelve ? d.twelve.assessmentPerformance : ''}
+
${d.twelve ? d.twelve.task : ''}
+
${d.twelveCompletion ? d.twelveCompletion : ''}
+
${d.fourQuarterTaskNnu == 0 ? '' : d.fourQuarterTaskNnu}
+
${d.fourQuarterTask ? d.fourQuarterTask : ''}
+ +
${d.allAmount ? d.allAmount : ''}
+
${d.allactualPerformance ? d.allactualPerformance : ''}
+
${d.allassessmentPerformance ? d.allassessmentPerformance : ''}
+ +
+
+ + + + ` + } + exportTable = `\uFEFF + + + + + ${exportTable} +
储备项目明细表
+ `; + let tempStrs = new Blob([exportTable], { type: 'text/xls' }) + FileSaver.saveAs(tempStrs, `储备中项目明细${moment().format('YYYY-MM-DD')}.xls`) + } + } + + return ( + <> +
+
+
业务管理
+
/
+
业绩报表
+
/
+
业绩汇总表
+
+
+
+
+
+
业绩汇总表
+
PERFORMANCE SUMMARY
+
+
+
+
+
+
{ setImportModalV(true); }}> + 导入 +
+
{ + exportDetail() + }}> + 导出全部 +
+
+
+
+ + + + + { + importModalV ? { + setImportModalV(false); + }} /> : '' + } + + + ) +} + + +function mapStateToProps(state) { + const { auth, global, performanceSummaryList } = state; + return { + user: auth.user, + actions: global.actions, + performanceSummaryList: performanceSummaryList.data || [] + }; +} + +export default connect(mapStateToProps)(AchievementDetails); \ No newline at end of file diff --git a/web/client/src/sections/business/nav-item.jsx b/web/client/src/sections/business/nav-item.jsx index 2b60834..19bdd43 100644 --- a/web/client/src/sections/business/nav-item.jsx +++ b/web/client/src/sections/business/nav-item.jsx @@ -43,6 +43,9 @@ export function getNavItem(user, dispatch) { }, { itemKey: 'achievementDetails', to: '/businessManagement/performanceReport/achievementDetails', text: '业绩明细表' + }, { + itemKey: 'summary', + to: '/businessManagement/performanceReport/summary', text: '业绩汇总表' }] }, { itemKey: 'salesReport', diff --git a/web/client/src/sections/business/routes.js b/web/client/src/sections/business/routes.js index 241cff9..f6725c9 100644 --- a/web/client/src/sections/business/routes.js +++ b/web/client/src/sections/business/routes.js @@ -1,6 +1,6 @@ import { ReserveItemsReporting, ReserveItemsPeriodicStatistics, ReserveItemsDepSummary, ReserveItemsLostStatistics, - ContractDetails, InvoicingDetails, BackMoneyDetails, AchievementDetails, SalesDistributionDetails, CustomerContactFollowup + ContractDetails, InvoicingDetails, BackMoneyDetails, AchievementDetails, SalesDistributionDetails, CustomerContactFollowup,PerformanceSummary } from './containers'; export default [{ @@ -60,6 +60,11 @@ export default [{ key: 'achievementDetails', breadcrumb: '业绩明细表', component: AchievementDetails + }, { + path: '/summary', + key: 'summary', + breadcrumb: '业绩汇总表', + component: PerformanceSummary }] }, { path: '/salesReport', diff --git a/web/client/src/sections/business/style.less b/web/client/src/sections/business/style.less index a1290ea..35efeb7 100644 --- a/web/client/src/sections/business/style.less +++ b/web/client/src/sections/business/style.less @@ -7,3 +7,15 @@ } } } +.summaryList{ + margin-top: 20px; + .semi-table .semi-table-row:first-child .semi-table-row-head{ + border-right: 1px solid #ccc; + } + .semi-table-thead > .semi-table-row > .semi-table-row-head{ + border-right: 1px solid #ccc; + } + .semi-table-tbody > .semi-table-row > .semi-table-row-cell{ + border-right: 1px solid #ccc; + } +} diff --git a/web/client/src/utils/webapi.js b/web/client/src/utils/webapi.js index 60b361d..3c87750 100644 --- a/web/client/src/utils/webapi.js +++ b/web/client/src/utils/webapi.js @@ -31,7 +31,8 @@ export const ApiTable = { getContractDetail: 'contract/detail', getInvoicingDetail: 'invoicing/detail', //客户联系人对接跟进 - getCustomerContactsFollowup:'customerContactsFollup' + getCustomerContactsFollowup:'customerContactsFollup', + getPerformanceSummary:'salePerformance', }; export const RouteTable = { apiRoot: "/api/root", diff --git a/web/config.js b/web/config.js index 76d3bdf..242d22e 100644 --- a/web/config.js +++ b/web/config.js @@ -28,7 +28,7 @@ const flags = args.parse(process.argv); const API_URL = process.env.API_URL || flags.apiUrl; const API_DC_URL = process.env.API_DC_URL || flags.apiHrUrl; -const FS_PEP_DOMAIN = process.FS_REPORT_DOMAIN || flags.domain; +const FS_PEP_DOMAIN = process.env.FS_PEP_DOMAIN || flags.domain; const WEB_PEP_URL = process.env.WEB_PEP_URL || flags.webPepUrl; // 七牛 diff --git a/web/package.json b/web/package.json index 51976e1..5ef2a30 100644 --- a/web/package.json +++ b/web/package.json @@ -32,6 +32,7 @@ "css-loader": "^3.5.0", "express": "^4.17.1", "file-loader": "^6.0.0", + "file-saver": "^2.0.5", "html-webpack-plugin": "^4.5.0", "immutable": "^4.0.0-rc.12", "less": "^3.12.2", @@ -58,7 +59,6 @@ "echarts": "^5.3.3", "echarts-for-react": "^3.0.2", "ezuikit-js": "^0.6.1", - "file-saver": "^2.0.5", "fs-attachment": "^1.0.0", "fs-web-server-scaffold": "^1.0.6", "js-cookie": "^3.0.1",