ww664853070 2 years ago
parent
commit
62eecc3fe3
  1. 6
      api/app/lib/controllers/report/achievement.js
  2. 2
      api/app/lib/controllers/report/index.js
  3. 35
      api/app/lib/models/contract_detail.js
  4. 13
      api/app/lib/models/performance_detail.js
  5. 4
      web/client/src/layout/components/header/index.jsx
  6. 2
      web/client/src/sections/business/constants/index.js
  7. 3
      web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx
  8. 2
      web/client/src/sections/business/containers/performanceReport/contractDetails.jsx
  9. 30
      web/client/src/sections/business/containers/performanceReport/importAchieveModal.jsx
  10. 30
      web/client/src/sections/business/containers/performanceReport/importBackModal.jsx
  11. 17
      web/client/src/sections/business/containers/performanceReport/importContractDetailsModal.js
  12. 73
      web/client/src/sections/business/containers/performanceReport/importInvoicingDetailsModal.js
  13. 5
      web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx
  14. 5
      web/client/src/sections/business/containers/salesReport/salesDistributionDetails.jsx
  15. 8
      web/config.js

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

@ -237,8 +237,8 @@ async function exportAchievementDetail(ctx, dataList) {
const { utils: { simpleExcelDown } } = ctx.app.fs;
let exportData = []
for (let { dataValues: item } of dataList) {
item.isApproval = JSON.stringify(item.isApproval) === 'null' ? '-' : item.isApproval ? '是' : '否';
item.reproducible = JSON.stringify(item.reproducible) === 'null' ? '-' : item.reproducible ? '是' : '否';
//item.isApproval = JSON.stringify(item.isApproval) === 'null' ? '-' : item.isApproval ? '是' : '否';
//item.reproducible = JSON.stringify(item.reproducible) === 'null' ? '-' : item.reproducible ? '是' : '否';
exportData.push(item)
}
const fileName = `业绩明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx'
@ -346,6 +346,7 @@ async function getContractDetail(ctx) {
where[keywordTarget] = { $iLike: `%${keyword}%` };
}
let contractDetail = await models.ContractDetail.findAndCountAll({
attributes: { exclude: ['iscopy', 'hiredate', 'regularDate'] },
where: where,
offset: Number(page) * Number(limit),
limit: Number(limit),
@ -394,6 +395,7 @@ async function exportContractDetail(ctx) {
try {
const { models } = ctx.fs.dc;
let exportData = await models.ContractDetail.findAll({
attributes: { exclude: ['iscopy', 'hiredate', 'regularDate'] },
order: [['id', 'ASC']]
});
const { utils: { simpleExcelDown, contractDetailsColumnKeys } } = ctx.app.fs;

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

@ -58,7 +58,7 @@ async function getSalersReport(ctx) {
const salersRes = await clickHouse.hr.query(`
SELECT * from sales_distribution as sales
${innerSelectQuery}
order by id asc
order by id desc
${!toExport && limit ? `LIMIT ${limit}` : ''}
${!toExport && limit && page ? 'OFFSET ' + parseInt(limit) * parseInt(page) : ''}
`).toPromise()

35
api/app/lib/models/contract_detail.js

@ -16,12 +16,12 @@ module.exports = dc => {
autoIncrement: true
},
year: {
type: DataTypes.STRING,
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: "年度",
primaryKey: false,
field: "year ",
field: "year",
autoIncrement: false
},
serialNo: {
@ -402,13 +402,13 @@ module.exports = dc => {
field: "cus_province",
autoIncrement: false
},
cusArea: {
itemArea: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "项目所在地",
primaryKey: false,
field: "cus_area",
field: "item_area",
autoIncrement: false
},
text: {
@ -419,6 +419,33 @@ module.exports = dc => {
primaryKey: false,
field: "text",
autoIncrement: false
},
iscopy: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "是否可复制的业务路径",
primaryKey: false,
field: "iscopy",
autoIncrement: false
},
hiredate: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "入职时间",
primaryKey: false,
field: "hiredate",
autoIncrement: false
},
regularDate: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "转正时间",
primaryKey: false,
field: "regular_date",
autoIncrement: false
}
}, {
tableName: "contract_detail",

13
api/app/lib/models/performance_detail.js

@ -97,7 +97,7 @@ module.exports = dc => {
autoIncrement: false
},
isApproval: {
type: DataTypes.BOOLEAN,
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "价格是否特批:根据折算比例-《合同明细表》推\n算(100%——“是”;其他为否)",
@ -196,7 +196,7 @@ module.exports = dc => {
autoIncrement: false
},
reproducible: {
type: DataTypes.BOOLEAN,
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: "是否可复制的业务路径:《合同评审》表单获取",
@ -230,6 +230,15 @@ module.exports = dc => {
primaryKey: false,
field: "isreproduce",
autoIncrement: false
},
contractNo: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: "",
comment: "合同编号",
primaryKey: false,
field: "contract_no",
autoIncrement: false
}
}, {
tableName: "performance_detail",

4
web/client/src/layout/components/header/index.jsx

@ -84,7 +84,7 @@ const Header = (props) => {
)
}
})}
<div style={{ display: 'flex', alignItems: 'center' }}>
{/* <div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ marginLeft: 16, width: 24, height: 24 }}>
<img src="/assets/images/background/username.png" alt="" style={{ width: '100%', height: '100%' }} />
</div>
@ -97,7 +97,7 @@ const Header = (props) => {
}}>
退出
</div>
</div>
</div> */}
{/* <Nav.Sub
itemKey={"user"}

2
web/client/src/sections/business/constants/index.js

@ -47,7 +47,7 @@ export const contractDetailsColumnKeys = {
industry: '行业',
source: '信息来源',
cusProvince: '客户省份',
cusArea: '项目所在地',
itemArea: '项目所在地',
text: '备注',
}

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

@ -47,8 +47,7 @@ const AchievementDetails = (props) => {
default:
columns.push({
title: columnKeys[key], dataIndex: key, key: key, width: 120,
render: (text, record) => ['isApproval', 'reproducible'].indexOf(key) != -1 ? JSON.stringify(text) === 'null' ? '-' : text ? '是' : '否'
: text === 0 ? text : text ? text : '-'
render: (text, record) => text === 0 ? text : text ? text : '-'
});
break;
}

2
web/client/src/sections/business/containers/performanceReport/contractDetails.jsx

@ -35,7 +35,7 @@ const ContractDetails = (props) => {
columns.push({
title: columnKeys[key], dataIndex: key, key: key,
render: (text, record) => text === 0 ? text : text ? text : '-',
width: 32 + columnKeys[key].length * 32
width: 60 + columnKeys[key].length * 16
});
break;
}

30
web/client/src/sections/business/containers/performanceReport/importAchieveModal.jsx

@ -96,17 +96,17 @@ const ImportAchieveModal = props => {
}
const judgeNull = (value) => {
return value ? String(value).trim().replace(/\s*/g, "") : null;
}
// const judgeNull = (value) => {
// return value ? String(value).trim().replace(/\s*/g, "") : null;
// }
const judgeNullTime = (v) => {
//xlsxexcel
//2020/8/1xlsx 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 judgeNullTime = (v) => {
// //xlsxexcel
// //2020/8/1xlsx 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;
@ -168,11 +168,11 @@ const ImportAchieveModal = props => {
let d = res[i];
let obj = {};
Object.keys(achievementColumnKeys).map(key => {
if (key === 'recConDate') {//
obj[key] = judgeNullTime(d[achievementColumnKeys[key]]);
} else {
obj[key] = d[achievementColumnKeys[key]] || null;
}
// if (key === 'recConDate') {//
// obj[key] = judgeNullTime(d[achievementColumnKeys[key]]);
// } else {
obj[key] = d[achievementColumnKeys[key]] || null;
//}
})
let tValid = judgeTimeValid(obj.recConDate);
if (!tValid) {

30
web/client/src/sections/business/containers/performanceReport/importBackModal.jsx

@ -104,17 +104,17 @@ const ImportBackModal = props => {
}
const judgeNull = (value) => {
return value ? String(value).trim().replace(/\s*/g, "") : null;
}
// const judgeNull = (value) => {
// return value ? String(value).trim().replace(/\s*/g, "") : null;
// }
const judgeNullTime = (v) => {
//xlsxexcel
//2020/8/1xlsx 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 judgeNullTime = (v) => {
// //xlsxexcel
// //2020/8/1xlsx 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;
@ -178,11 +178,11 @@ const ImportBackModal = props => {
let d = res[i];
let obj = {};
Object.keys(backMoneyColumnKeys).map(key => {
if (['receivableDate', 'incomeConfirmdate'].indexOf(key) != -1) {//
obj[key] = judgeNullTime(d[backMoneyColumnKeys[key]]);
} else {
obj[key] = d[backMoneyColumnKeys[key]] || null;
}
// if (['receivableDate', 'incomeConfirmdate'].indexOf(key) != -1) {//
// obj[key] = judgeNullTime(d[backMoneyColumnKeys[key]]);
// } else {
obj[key] = d[backMoneyColumnKeys[key]] || null;
//}
})
//
if (!obj.year || !obj.serialNo || !obj.number) {

17
web/client/src/sections/business/containers/performanceReport/importContractDetailsModal.js

@ -107,14 +107,6 @@ const ImportContractDetailsModal = props => {
})
}
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) {
@ -176,12 +168,7 @@ const ImportContractDetailsModal = props => {
let d = res[i];
let obj = {};
Object.keys(contractDetailsColumnKeys).map(key => {
//四个日期验证“申请日期”、“收到合同日期”、“验收日期”、“收入确认时间”
if (ColumnDateKey.indexOf(key) != -1) {//两个时间
obj[key] = judgeNullTime(d[contractDetailsColumnKeys[key]]);
} else {
obj[key] = d[contractDetailsColumnKeys[key]] || null;
}
obj[key] = d[contractDetailsColumnKeys[key]] || null;
})
//年度 序号 编号必填
if (!obj.year || !obj.serialNo || !obj.number) {
@ -207,7 +194,7 @@ const ImportContractDetailsModal = props => {
error(`${i + 2}行【编号】重复,请更改后重新上传`)
return
}
//日期验证“申请日期”、“收到合同日期”、“验收日期”、“收入确认时间”
for (let k = 0; k < ColumnDateKey.length; k++) {
let cValid = judgeTimeValid(obj[ColumnDateKey[k]]);
if (!cValid) {

73
web/client/src/sections/business/containers/performanceReport/importInvoicingDetailsModal.js

@ -4,22 +4,35 @@ import { connect } from 'react-redux';
import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui';
import { IconUpload } from '@douyinfe/semi-icons';
import XLSX from 'xlsx';
import moment from 'moment';
import { invoicingDetailsColumnKeys } from '../../constants/index';
const ColumnDateKey = ['invoiceDate'];
const ColumnDateName = ['开票日期'];
//下载模板和上传文件读取
const ImportInvoicingDetailsModal = props => {
const { dispatch, actions, onCancel } = props;
const { humanAffairs } = actions;
const { businessManagement } = actions;
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState('');
const [postData, setPostData] = useState([]);
const [uploadAble, setUploadAble] = useState(false);
const [allNumbers, setAllNumbers] = useState([]);
//初始化
useEffect(() => {
//查询明细表已存在编号数据接口通用
dispatch(businessManagement.getReceivedNumbers({ tableModel: 'InvoiceDetail' })).then(r => {
if (r.success) {
setUploadAble(true);
setAllNumbers(r.payload.data);
}
})
}, []);
const confirm = () => {
if (postData.length) {
setLoading(true)
dispatch(humanAffairs.addSalesMemberBulk(postData)).then(res => {
//导入明细接口通用
dispatch(businessManagement.importBackDetails(postData, { tableModel: 'InvoiceDetail' })).then(res => {
if (res.success) {
onCancel()
}
@ -94,9 +107,21 @@ const ImportInvoicingDetailsModal = props => {
})
}
const judgeNull = (value) => {
return value ? String(value).trim().replace(/\s*/g, "") : 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 (
@ -137,12 +162,46 @@ const ImportInvoicingDetailsModal = props => {
return
}
let postData = [];
let yearPattern = /^(19|20)\d{2}$/;//1900-2099年
let zzsPattern = /^[+]{0,1}(\d+)$/;//正整数
for (let i = 0; i < res.length; i++) {
let d = res[i];
let obj = {};
Object.keys(invoicingDetailsColumnKeys).map(key => {
obj[key] = d[invoicingDetailsColumnKeys[key]] || '';
obj[key] = d[invoicingDetailsColumnKeys[key]] || null;
})
//年度 序号 编号必填
if (!obj.year || !obj.serialNo || !obj.number) {
error(`${i + 2}行【年度】、【序号】、【编号】存在空值,请填写`)
return
}
//年度
if (obj.year && !yearPattern.test(obj.year)) {
error(`${i + 2}行【年度】填写错误`)
return
}
//序号 正整数
if (obj.serialNo && !zzsPattern.test(obj.serialNo)) {
error(`${i + 2}行【序号】填写错误,需要为非负整数`)
return
}
let exist = allNumbers.find(m => m.number == obj.number);//数据库中 已有该编号
if (exist) {
error(`${i + 2}行的【编号】在系统中已存在`)
return
}
if (postData.some(p => p.number == obj.number)) {//编号 唯一
error(`${i + 2}行【编号】重复,请更改后重新上传`)
return
}
for (let k = 0; k < ColumnDateKey.length; k++) {
let cValid = judgeTimeValid(obj[ColumnDateKey[k]]);
if (!cValid) {
error(`${i + 2}行【${ColumnDateName[k]}】错误,请填写yyyy/mm/dd格式`)
return
}
}
postData.push(obj);
}
setPostData(postData)
@ -151,7 +210,7 @@ const ImportInvoicingDetailsModal = props => {
onSuccess({ message: msg })
})
}}>
<Button icon={<IconUpload />} theme="light">
<Button icon={<IconUpload />} theme="light" disabled={!uploadAble}>
请选择文件
</Button>
</Form.Upload>

5
web/client/src/sections/business/containers/performanceReport/invoicingDetails.jsx

@ -34,8 +34,8 @@ const InvoicingDetails = (props) => {
default:
columns.push({
title: columnKeys[key], dataIndex: key, key: key,
render: (text, record) => text === 0 ? text : text ? text : '',
width: 32 + columnKeys[key].length * 16
render: (text, record) => text === 0 ? text : text ? text : '-',
width: 60 + columnKeys[key].length * 16
});
break;
}
@ -216,6 +216,7 @@ const InvoicingDetails = (props) => {
importModalV ? <ImportInvoicingDetailsModal
onCancel={() => {
setImportModalV(false);
getInvoicingDetailData();
}} /> : ''
}
{

5
web/client/src/sections/business/containers/salesReport/salesDistributionDetails.jsx

@ -14,6 +14,7 @@ const SalesDistributionDetails = (props) => {
const [limits, setLimits] = useState()//
const [query, setQuery] = useState({ limit: 10, page: 0 }); //
const [tableData, setTableData] = useState([]);
const [loading, setLoading] = useState(false)
const [exportUrl, setExportUrl] = useState('')
const page = useRef(query.page);
function seachValueChange(value) {
@ -29,11 +30,13 @@ const SalesDistributionDetails = (props) => {
}, [query])
function getMemberSearchList() {
setLoading(true);
let kt = keywordTarget == 'place' ? '' : keywordTarget;
let k = keywordTarget == 'place' ? '' : keyword;
let placeSearch = keywordTarget == 'place' ? keyword : '';
dispatch(businessManagement.getSalesList({ keywordTarget: kt, keyword: k, placeSearch, ...query })).then(r => {
if (r.success) {
setLoading(false);
setTableData(r.payload?.data?.rows);
setLimits(r.payload?.data?.count)
}
@ -233,7 +236,7 @@ const SalesDistributionDetails = (props) => {
<div style={{ marginTop: 20 }}>
<Skeleton
loading={false}
loading={loading}
active={true}
placeholder={SkeletonScreen()}
>

8
web/config.js

@ -12,7 +12,7 @@ dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,
args.option(['p', 'port'], '启动端口');
args.option(['u', 'api-url'], 'webapi的URL');
args.option('apiHrUrl', 'webapi的URL 外网可访问');
args.option(['d', 'domain'], 'web domain');
args.option(['d', 'domain'], '项企的顶级域名 domain');
args.option('webPepUrl', '企业管理 web');
@ -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_REPORT_DOMAIN = process.FS_REPORT_DOMAIN || flags.domain;
const FS_PEP_DOMAIN = process.FS_REPORT_DOMAIN || flags.domain;
const WEB_PEP_URL = process.env.WEB_PEP_URL || flags.webPepUrl;
// 七牛
@ -43,6 +43,8 @@ const ANXINCLOUD_PM_SERVICES = process.env.ANXINCLOUD_PM_SERVICES || flags.pmrs;
if (
!API_URL
|| !API_DC_URL
|| !WEB_PEP_URL
|| !FS_PEP_DOMAIN
|| !ANXINCLOUD_QINIU_AK || !ANXINCLOUD_QINIU_SK || !ANXINCLOUD_QINIU_BUCKET_RESOURCE || !ANXINCLOUD_QINIU_DOMAIN_QNDMN_RESOURCE
) {
console.log('缺少启动参数,异常退出');
@ -83,7 +85,7 @@ const product = {
service: {
url: ANXINCLOUD_PM_SERVICES
},
domain: FS_REPORT_DOMAIN,
domain: FS_PEP_DOMAIN,
webPepUrl: WEB_PEP_URL
}
}, {

Loading…
Cancel
Save