Browse Source

(*)回款和业绩明细表 导出全部 代码暂交

master
wuqun 2 years ago
parent
commit
cbd1a4fab5
  1. 236
      api/app/lib/controllers/report/achievement.js
  2. 10
      api/app/lib/controllers/report/index.js
  3. 61
      web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx
  4. 55
      web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx

236
api/app/lib/controllers/report/achievement.js

@ -5,22 +5,33 @@ const moment = require('moment');
async function getReceivedDetail(ctx) {
try {
const { models } = ctx.fs.dc;
const { keywordTarget, keyword, limit, page } = ctx.query
const { keywordTarget, keyword, limit, page, toExport } = ctx.query
let where = {}
if (keywordTarget == 'contract' && keyword) {
where.contractNo = { $like: `%${keyword}%` }
}
let res = await models.ReceivableDetail.findAndCountAll({
let findOption = {
where: where,
offset: Number(page) * Number(limit),
limit: Number(limit),
order: [['id', 'DESC']]
})
ctx.status = 200
ctx.body = {
count: res.count,
rows: res.rows
};
}
if (!toExport) {//非导出时考虑分页
if (limit) {
findOption.limit = Number(limit)
}
if (page && limit) {
findOption.offset = Number(page) * Number(limit)
}
}
let res = await models.ReceivableDetail.findAndCountAll(findOption);
if (toExport) {//数据导出相关
await exportReceivedDetail(ctx, res.rows);
} else {
ctx.status = 200
ctx.body = {
count: res.count,
rows: res.rows
};
}
} catch (error) {
ctx.fs.logger.error(`path:${ctx.path},error:${error}`)
ctx.status = 400;
@ -28,10 +39,87 @@ async function getReceivedDetail(ctx) {
}
}
async function exportReceivedDetail(ctx, dataList) {
try {
let header = [{
title: '年度',
key: 'year',
}, {
title: '序号',
key: 'serialNo',
}, {
title: '编号',
key: 'number',
}, {
title: '部门',
key: 'department',
}, {
title: '销售人员',
key: 'sale',
}, {
title: '合同编号',
key: 'contractNo',
}, {
title: '客户名称',
key: 'customer',
}, {
title: '项目名称',
key: 'item',
}, {
title: '合同金额',
key: 'amount',
}, {
title: '变更后合同金额',
key: 'changeAmount',
}, {
title: '回款年份',
key: 'receivableYear',
}, {
title: '回款日期',
key: 'receivableDate',
}, {
title: '回款金额',
key: 'receivableAmount',
}, {
title: '开票-回款',
key: 'invoicedBack',
}, {
title: '剩余合同金额',
key: 'remainConAmount',
}, {
title: '收入确认时间',
key: 'incomeConfirmdate',
}, {
title: '第三方付款单位',
key: 'thirdPayment',
}, {
title: '备注',
key: 'remark',
}]
const { utils: { simpleExcelDown } } = ctx.app.fs;
let exportData = []
for (let { dataValues: item } of dataList) {
exportData.push(item)
}
const fileName = `回款明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'
const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName })
const fileData = fs.readFileSync(filePath);
ctx.status = 200;
ctx.set('Content-Type', 'application/x-xls');
ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
ctx.body = fileData;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
async function getAchievementDetail(ctx) {
try {
const { models } = ctx.fs.dc;
const { keywordTarget, keyword, limit, page } = ctx.query
const { keywordTarget, keyword, limit, page, toExport } = ctx.query
let where = {}
if (keywordTarget == 'saler' && keyword) {
where.sale = { $like: `%${keyword}%` }
@ -42,17 +130,28 @@ async function getAchievementDetail(ctx) {
if (keywordTarget == 'dep' && keyword) {
where.department = { $like: `%${keyword}%` }
}
let res = await models.PerformanceDetail.findAndCountAll({
let findOption = {
where: where,
offset: Number(page) * Number(limit),
limit: Number(limit),
order: [['id', 'DESC']]
})
ctx.status = 200
ctx.body = {
count: res.count,
rows: res.rows
};
}
if (!toExport) {//非导出时考虑分页
if (limit) {
findOption.limit = Number(limit)
}
if (page && limit) {
findOption.offset = Number(page) * Number(limit)
}
}
let res = await models.PerformanceDetail.findAndCountAll(findOption);
if (toExport) {//数据导出相关
await exportAchievementDetail(ctx, res.rows);
} else {
ctx.status = 200
ctx.body = {
count: res.count,
rows: res.rows
};
}
} catch (error) {
ctx.fs.logger.error(`path:${ctx.path},error:${error}`)
ctx.status = 400;
@ -60,6 +159,103 @@ async function getAchievementDetail(ctx) {
}
}
async function exportAchievementDetail(ctx, dataList) {
try {
let header = [{
title: '收到合同日期',
key: 'recConDate',
}, {
title: '月份',
key: 'month',
}, {
title: '部门',
key: 'department',
}, {
title: '销售人员',
key: 'sale',
}, {
title: '客户名称',
key: 'customer',
}, {
title: '项目名称',
key: 'item',
}, {
title: '合同金额',
key: 'amount',
}, {
title: '实际业绩',
key: 'realPerformance',
}, {
title: '考核业绩',
key: 'assessmentPerformance',
}, {
title: '价格是否特批',
key: 'isApproval',
}, {
title: '特批折算比例',
key: 'approvalProp',
}, {
title: '预支提成及委外费用',
key: 'cost',
}, {
title: '业务线',
key: 'serviceLine',
}, {
title: '客户类型',
key: 'cusType',
}, {
title: '行业',
key: 'industry',
}, {
title: '信息来源',
key: 'source',
}, {
title: '项目类型',
key: 'itemType',
}, {
title: '客户省份',
key: 'cusProvince',
}, {
title: '客户属性',
key: 'cusAttribute',
}, {
title: '复购次数',
key: 'repurchaseCount',
}, {
title: '是否可复制的业务路径',
key: 'reproducible',
}, {
title: '省外业务1.1',
key: 'outProvince',
}, {
title: '复购业务1.05',
key: 'repurchase',
}, {
title: '可复制的业务路径1.1',
key: 'isreproduce',
}]
const { utils: { simpleExcelDown } } = ctx.app.fs;
let exportData = []
for (let { dataValues: item } of dataList) {
item.isApproval = item.isApproval ? '是' : '否';
item.reproducible = item.reproducible ? '是' : '否';
exportData.push(item)
}
const fileName = `业绩明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'
const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName })
const fileData = fs.readFileSync(filePath);
ctx.status = 200;
ctx.set('Content-Type', 'application/x-xls');
ctx.set('Content-disposition', 'attachment; filename=' + encodeURI(fileName));
ctx.body = fileData;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
message: typeof error == 'string' ? error : undefined
}
}
}
module.exports = {
getReceivedDetail,//回款
getAchievementDetail,//业绩

10
api/app/lib/controllers/report/index.js

@ -133,12 +133,12 @@ async function exportSalesDetail(ctx, dataList) {
let exportData = []
for (let item of dataList) {
item.department = item.department.map(t => t.name).join('、') || '-';
item.cities = item.cities || '-';
item.businessLines = item.businessLines || '-';
item.post = item.post || '-';
item.regularDate = item.regularDate || '-';
// item.cities = item.cities || '-';
// item.businessLines = item.businessLines || '-';
// item.post = item.post || '-';
// item.regularDate = item.regularDate || '-';
item.workYears = item.hireDate ? String(moment(new Date()).diff(item.hireDate, 'years', true)).substring(0, 3) + '年' : '-'
item.hireDate = item.hireDate || '-';
//item.hireDate = item.hireDate || '-';
exportData.push(item)
}
const fileName = `销售人员分布明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'

61
web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx

@ -1,18 +1,20 @@
import React, { useRef, useEffect, useState, useMemo } from 'react';
import { connect } from 'react-redux';
import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import moment from 'moment'
import { Select, Input, Button, Toast, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen } from "$components";
import '../../style.less';
const AchievementDetails = (props) => {
const { dispatch, actions } = props
const { dispatch, actions, user } = props
const { businessManagement } = actions
const [keywordTarget, setKeywordTarget] = useState('saler');
const [keyword, setKeyword] = useState('');//
const [limits, setLimits] = useState()//
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [tableData, setTableData] = useState([]);
const [exportUrl, setExportUrl] = useState('')
const page = useRef(query.page);
function seachValueChange(value) {
setKeyword(value)
@ -22,6 +24,10 @@ const AchievementDetails = (props) => {
getAchievementDetails()
}, []);
useEffect(() => {
getAchievementDetails()
}, [query])
function getAchievementDetails() {
dispatch(businessManagement.getAchievementDetail({ keywordTarget, keyword, ...query })).then(r => {
if (r.success) {
@ -36,121 +42,145 @@ const AchievementDetails = (props) => {
dataIndex: 'recConDate',
key: 'recConDate',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '月份',
dataIndex: 'month',
key: 'month',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '部门',
dataIndex: 'department',
key: 'department',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '销售人员',
dataIndex: 'sale',
key: 'sale',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '客户名称',
dataIndex: 'customer',
key: 'customer',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '项目名称',
dataIndex: 'item',
key: 'item',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '合同金额',
dataIndex: 'amount',
key: 'amount',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '实际业绩',
dataIndex: 'realPerformance',
key: 'realPerformance',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '考核业绩',
dataIndex: 'assessmentPerformance',
key: 'assessmentPerformance',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '价格是否特批',
dataIndex: 'isApproval',
key: 'isApproval',
width: 120,
render: (text, record) => <span>{text ? '是' : '否'}</span>
}, {
title: '特批折算比例',
dataIndex: 'approvalProp',
key: 'approvalProp',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '预支提成及委外费用',
dataIndex: 'cost',
key: 'cost',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '业务线',
dataIndex: 'serviceLine',
key: 'serviceLine',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '客户类型',
dataIndex: 'cusType',
key: 'cusType',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '行业',
dataIndex: 'industry',
key: 'industry',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '信息来源',
dataIndex: 'source',
key: 'source',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '项目类型',
dataIndex: 'itemType',
key: 'itemType',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '客户省份',
dataIndex: 'cusProvince',
key: 'cusProvince',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '客户属性',
dataIndex: 'cusAttribute',
key: 'cusAttribute',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '复购次数',
dataIndex: 'repurchaseCount',
key: 'repurchaseCount',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '是否可复制的业务路径',
dataIndex: 'reproducible',
key: 'reproducible',
width: 120,
render: (text, record) => <span>{text ? '是' : '否'}</span>
}, {
title: '省外业务1.1',
dataIndex: 'outProvince',
key: 'outProvince',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '复购业务1.05',
dataIndex: 'repurchase',
key: 'repurchase',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '可复制的业务路径1.1',
dataIndex: 'isreproduce',
key: 'isreproduce',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}]
function handleRow(record, index) {//
if (index % 2 === 0) {
@ -163,6 +193,23 @@ const AchievementDetails = (props) => {
return {};
}
}
const exportAllData = () => {
dispatch(businessManagement.getAchievementDetail({ limit: 1 })).then((res) => {//
if (res.success) {
if (res.payload.data.count) {
let url = `detail/achievement?token=${user.token}&toExport=1&timestamp=${moment().valueOf()}`
setExportUrl(url);
} else {
Toast.info({
content: '暂无可导出的数据',
duration: 3,
})
}
}
})
}
const scroll = useMemo(() => ({}), []);
return (
<>
@ -206,12 +253,15 @@ const AchievementDetails = (props) => {
setQuery({ limit: 10, page: 0 })
}}>查询</Button>
</div>
<div style={{ display: 'flex', marginRight: 20 }}>
<div style={{ display: 'flex' }}>
<div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer" }}
onClick={() => { setImportModalV(true); }}>
导入
</div>
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, marginLeft: 18 }}>
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, cursor: "pointer", marginLeft: 18 }}
onClick={() => {
exportAllData()
}}>
导出全部
</div>
</div>
@ -257,6 +307,9 @@ const AchievementDetails = (props) => {
</div>
</div>
</div>
{
exportUrl ? <iframe src={`/_api/${exportUrl}`} style={{ display: 'none' }} /> : ''
}
</div>
</>
)

55
web/client/src/sections/business/containers/performanceReport/backMoneyDetails.jsx

@ -1,18 +1,20 @@
import React, { useRef, useEffect, useState, useMemo } from 'react';
import { connect } from 'react-redux';
import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import moment from 'moment'
import { Select, Input, Button, Toast, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
import { SkeletonScreen } from "$components";
import '../../style.less';
const BackMoneyDetails = (props) => {
const { dispatch, actions } = props
const { dispatch, actions, user } = props
const { businessManagement } = actions
const [keywordTarget, setKeywordTarget] = useState('contract');
const [keyword, setKeyword] = useState('');//
const [limits, setLimits] = useState()//
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [tableData, setTableData] = useState([]);
const [exportUrl, setExportUrl] = useState('')
const page = useRef(query.page);
function seachValueChange(value) {
setKeyword(value)
@ -22,6 +24,10 @@ const BackMoneyDetails = (props) => {
getBackMoneyDetails()
}, []);
useEffect(() => {
getBackMoneyDetails()
}, [query])
function getBackMoneyDetails() {
dispatch(businessManagement.getReceivedDetail({ keywordTarget, keyword, ...query })).then(r => {
if (r.success) {
@ -36,91 +42,109 @@ const BackMoneyDetails = (props) => {
dataIndex: 'year',
key: 'year',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '序号',
dataIndex: 'serialNo',
key: 'serialNo',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '编号',
dataIndex: 'number',
key: 'number',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '部门',
dataIndex: 'department',
key: 'department',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '销售人员',
dataIndex: 'sale',
key: 'sale',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '合同编号',
dataIndex: 'contractNo',
key: 'contractNo',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '客户名称',
dataIndex: 'customer',
key: 'customer',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '项目名称',
dataIndex: 'item',
key: 'item',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '合同金额',
dataIndex: 'amount',
key: 'amount',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '变更后合同金额',
dataIndex: 'changeAmount',
key: 'changeAmount',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '回款年份',
dataIndex: 'receivableYear',
key: 'receivableYear',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '回款日期',
dataIndex: 'receivableDate',
key: 'receivableDate',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '回款金额',
dataIndex: 'receivableAmount',
key: 'receivableAmount',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '开票-回款',
dataIndex: 'invoicedBack',
key: 'invoicedBack',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '剩余合同金额',
dataIndex: 'remainConAmount',
key: 'remainConAmount',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '收入确认时间',
dataIndex: 'incomeConfirmdate',
key: 'incomeConfirmdate',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '第三方付款单位',
dataIndex: 'thirdPayment',
key: 'thirdPayment',
width: 140,
render: (text, record) => <span>{text || '-'}</span>
}, {
title: '备注',
dataIndex: 'remark',
key: 'remark',
width: 120,
render: (text, record) => <span>{text || '-'}</span>
}]
function handleRow(record, index) {//
if (index % 2 === 0) {
@ -133,6 +157,23 @@ const BackMoneyDetails = (props) => {
return {};
}
}
const exportAllData = () => {
dispatch(businessManagement.getReceivedDetail({ limit: 1 })).then((res) => {//
if (res.success) {
if (res.payload.data.count) {
let url = `detail/received/back?token=${user.token}&toExport=1&timestamp=${moment().valueOf()}`
setExportUrl(url);
} else {
Toast.info({
content: '暂无可导出的数据',
duration: 3,
})
}
}
})
}
const scroll = useMemo(() => ({}), []);
return (
<>
@ -176,12 +217,15 @@ const BackMoneyDetails = (props) => {
setQuery({ limit: 10, page: 0 })
}}>查询</Button>
</div>
<div style={{ display: 'flex', marginRight: 20 }}>
<div style={{ display: 'flex' }}>
<div style={{ padding: '6px 20px', background: '#0F7EFB', color: '#FFFFFF', fontSize: 14, cursor: "pointer" }}
onClick={() => { setImportModalV(true); }}>
导入
</div>
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, marginLeft: 18 }}>
<div style={{ padding: '6px 20px', background: '#00BA85', color: '#FFFFFF', fontSize: 14, cursor: "pointer", marginLeft: 18 }}
onClick={() => {
exportAllData()
}}>
导出全部
</div>
</div>
@ -227,6 +271,9 @@ const BackMoneyDetails = (props) => {
</div>
</div>
</div>
{
exportUrl ? <iframe src={`/_api/${exportUrl}`} style={{ display: 'none' }} /> : ''
}
</div>
</>
)

Loading…
Cancel
Save