diff --git a/.vscode/launch.json b/.vscode/launch.json index dd08d4d..cd44ee8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,7 +45,8 @@ "-g postgres://FashionAdmin:123456@10.8.30.36:5432/data_center", "--redisHost localhost", "--redisPort 6379", - "--apiEmisUrl http://10.8.30.161:1111", + "--apiEmisUrl http://10.8.30.103:14000", //开发 + // "--apiEmisUrl http://10.8.30.161:1111", //测试 // 镇江研发结束 // 测试 "--qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5", diff --git a/api/app/lib/controllers/report/achievement.js b/api/app/lib/controllers/report/achievement.js index 9a027ef..a5c2e70 100644 --- a/api/app/lib/controllers/report/achievement.js +++ b/api/app/lib/controllers/report/achievement.js @@ -257,6 +257,73 @@ async function exportAchievementDetail(ctx, dataList) { } } +async function getReceivedNumbers(ctx) { + try { + const models = ctx.fs.dc.models; + let list = await models.ReceivableDetail.findAll({//查编号 + attributes: ['number'] + }); + ctx.status = 200 + ctx.body = list; + } catch (error) { + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = { + message: typeof error == 'string' ? error : undefined + } + } +} + +async function importBackDetails(ctx) { + let errorMsg = { message: '导入回款明细失败' }; + const transaction = await ctx.fs.dc.orm.transaction(); + try { + const models = ctx.fs.dc.models; + const data = ctx.request.body; + let addArr = []; + let dataList = await models.ReceivableDetail.findAll({//查编号 + attributes: ['number'] + }); + data.map(d => { + let exist = dataList.find(m => m.dataValues.number == d.number); + if (!exist) { + addArr.push(d); + } + }) + //只处理新增的 + if (addArr.length) { + await models.ReceivableDetail.bulkCreate(addArr); + } + await transaction.commit(); + ctx.status = 204; + } catch (error) { + await transaction.rollback(); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = errorMsg; + } +} + +async function importAchieveDetails(ctx) { + let errorMsg = { message: '导入业绩明细失败' }; + const transaction = await ctx.fs.dc.orm.transaction(); + try { + const models = ctx.fs.dc.models; + const data = ctx.request.body; + //业绩明细 没有唯一标识,前端校验后,导啥存啥 + if (data.length) { + await models.PerformanceDetail.bulkCreate(data); + } + await transaction.commit(); + ctx.status = 204; + } catch (error) { + await transaction.rollback(); + ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`); + ctx.status = 400; + ctx.body = errorMsg; + } +} + /** * 查询合同明细表数据 * @param {*} ctx ctx ctx.query:{keywordTarget-关键字项、keyword-关键字内容、limit-页宽, page-页码} @@ -283,8 +350,99 @@ async function getContractDetail(ctx) { ctx.body = { name: 'FindError', message: '查询合同明细表数据失败' } } } + +/** + * 查询开票明细表数据 + * @param {*} ctx ctx ctx.query:{keywordTarget-关键字项、keyword-关键字内容、limit-页宽, page-页码} + */ +async function getInvoicingDetail(ctx) { + try { + const { models } = ctx.fs.dc; + const { keywordTarget, keyword, limit, page } = ctx.query; + const where = {}; + if (keywordTarget && keyword) { + where[keywordTarget] = { $iLike: `%${keyword}%` }; + } + let invoiceDetail = await models.InvoiceDetail.findAndCountAll({ + where: where, + offset: Number(page) * Number(limit), + limit: Number(limit), + order: [['id', 'DESC']] + }); + ctx.status = 200 + ctx.body = invoiceDetail; + } catch (error) { + ctx.fs.logger.error(`path:${ctx.path},error:${error}`) + ctx.status = 400; + ctx.body = { name: 'FindError', message: '查询开票明细表数据失败' } + } +} + +/** + * 导出合同明细表数据 + */ +async function exportContractDetail(ctx) { + try { + const { models } = ctx.fs.dc; + let exportData = await models.ContractDetail.findAll({ + order: [['id', 'DESC']] + }); + const { utils: { simpleExcelDown, contractDetailsColumnKeys } } = ctx.app.fs; + let header = []; + Object.keys(contractDetailsColumnKeys).map(key => { + header.push({ title: contractDetailsColumnKeys[key], key: key }); + }) + const fileName = `合同明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx' + const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName, needIndexCell: false }) + 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 = { name: 'ExportAllError', message: '导出合同明细表数据失败' } + } +} + + +/** + * 导出开票明细表数据 + */ +async function exportInvoicingDetail(ctx) { + try { + const { models } = ctx.fs.dc; + let exportData = await models.InvoiceDetail.findAll({ + order: [['id', 'DESC']] + }); + const { utils: { simpleExcelDown, invoicingDetailsColumnKeys } } = ctx.app.fs; + let header = []; + Object.keys(invoicingDetailsColumnKeys).map(key => { + header.push({ title: invoicingDetailsColumnKeys[key], key: key }); + }) + const fileName = `开票明细表_${moment().format('YYYYMMDDHHmmss')}` + '.xlsx' + const filePath = await simpleExcelDown({ data: exportData, header, fileName: fileName, needIndexCell: false }) + 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 = { name: 'ExportAllError', message: '导出开票明细表数据失败' } + } +} module.exports = { getReceivedDetail,//回款 getAchievementDetail,//业绩 + + getReceivedNumbers,//查询回款明细表 已有的所有编号 + importBackDetails,//导入回款明细 + importAchieveDetails,//导入业绩明细 getContractDetail, + getInvoicingDetail, + exportContractDetail, + exportInvoicingDetail } \ No newline at end of file diff --git a/api/app/lib/models/performance_detail.js b/api/app/lib/models/performance_detail.js index ff431ee..59aa6e1 100644 --- a/api/app/lib/models/performance_detail.js +++ b/api/app/lib/models/performance_detail.js @@ -13,11 +13,11 @@ module.exports = dc => { comment: null, primaryKey: true, field: "id", - autoIncrement: false + autoIncrement: true }, recConDate: { type: DataTypes.DATEONLY, - allowNull: false, + allowNull: true, defaultValue: null, comment: "收到合同日期", primaryKey: false, @@ -26,7 +26,7 @@ module.exports = dc => { }, month: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "月份", primaryKey: false, @@ -35,7 +35,7 @@ module.exports = dc => { }, department: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "部门:申请部门", primaryKey: false, @@ -44,7 +44,7 @@ module.exports = dc => { }, sale: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "销售人员", primaryKey: false, @@ -53,7 +53,7 @@ module.exports = dc => { }, customer: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "客户名称:【甲方名称】", primaryKey: false, @@ -62,7 +62,7 @@ module.exports = dc => { }, item: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "项目名称", primaryKey: false, @@ -71,7 +71,7 @@ module.exports = dc => { }, amount: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "合同金额:【合同金额\n(元)】", primaryKey: false, @@ -80,7 +80,7 @@ module.exports = dc => { }, realPerformance: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "实际业绩;(合同金额-预\n支提成及委外费用)*特批折算比例\n(G-L)*K", primaryKey: false, @@ -89,7 +89,7 @@ module.exports = dc => { }, assessmentPerformance: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "合同金额*省外业务(1.1)*复购业务\n(1.05)*可复制的业务路径(1.1)", primaryKey: false, @@ -98,7 +98,7 @@ module.exports = dc => { }, isApproval: { type: DataTypes.BOOLEAN, - allowNull: false, + allowNull: true, defaultValue: null, comment: "价格是否特批:根据折算比例-《合同明细表》推\n算(100%——“是”;其他为否)", primaryKey: false, @@ -107,7 +107,7 @@ module.exports = dc => { }, approvalProp: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "特批折算比例:【业绩折\n算比例】", primaryKey: false, @@ -116,7 +116,7 @@ module.exports = dc => { }, cost: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "预支提成及委外费用", primaryKey: false, @@ -125,7 +125,7 @@ module.exports = dc => { }, serviceLine: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "业务线", primaryKey: false, @@ -134,7 +134,7 @@ module.exports = dc => { }, cusType: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "客户类型", primaryKey: false, @@ -143,7 +143,7 @@ module.exports = dc => { }, industry: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "行业", primaryKey: false, @@ -152,7 +152,7 @@ module.exports = dc => { }, source: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "信息来源", primaryKey: false, @@ -161,7 +161,7 @@ module.exports = dc => { }, itemType: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "项目类型", primaryKey: false, @@ -170,7 +170,7 @@ module.exports = dc => { }, cusProvince: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "客户省份", primaryKey: false, @@ -179,7 +179,7 @@ module.exports = dc => { }, cusAttribute: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "客户属性:G:政府、事业单位\nB:央企、国企、平台商\nC:资源方", primaryKey: false, @@ -188,7 +188,7 @@ module.exports = dc => { }, repurchaseCount: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, defaultValue: null, comment: "复购次数:同一客户储备成单-成单项目数量\n(PM)", primaryKey: false, @@ -197,7 +197,7 @@ module.exports = dc => { }, reproducible: { type: DataTypes.BOOLEAN, - allowNull: false, + allowNull: true, defaultValue: null, comment: "是否可复制的业务路径:《合同评审》表单获取", primaryKey: false, @@ -206,7 +206,7 @@ module.exports = dc => { }, outProvince: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "省外业务1.1:当【客户省份】=“江西”时,省外业务=1;当【客户省份】<>“江西”时,省外业务=1.1", primaryKey: false, @@ -215,7 +215,7 @@ module.exports = dc => { }, repurchase: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "复购业务1.05:当【复购次数】\n<2时,复购业务=1;当【复购次数】\n>=2时,复购业务=1.05;", primaryKey: false, @@ -224,7 +224,7 @@ module.exports = dc => { }, isreproduce: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "可复制的业务路径1.1:当【是否可复\n制的业务路径】=“是”,可复制的业务路径=1.1;当【是否可复制的业务路径】=“否”,可复制的业务路径=1;", primaryKey: false, diff --git a/api/app/lib/models/receivable_detail.js b/api/app/lib/models/receivable_detail.js index bd9faa0..3d9c30b 100644 --- a/api/app/lib/models/receivable_detail.js +++ b/api/app/lib/models/receivable_detail.js @@ -13,7 +13,7 @@ module.exports = dc => { comment: null, primaryKey: true, field: "id", - autoIncrement: false + autoIncrement: true }, year: { type: DataTypes.STRING, @@ -44,7 +44,7 @@ module.exports = dc => { }, department: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "部门:申请部门", primaryKey: false, @@ -53,7 +53,7 @@ module.exports = dc => { }, sale: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "销售人员:申请人", primaryKey: false, @@ -62,7 +62,7 @@ module.exports = dc => { }, contractNo: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "合同编号", primaryKey: false, @@ -71,7 +71,7 @@ module.exports = dc => { }, customer: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "客户名称:【甲方名称】", primaryKey: false, @@ -80,7 +80,7 @@ module.exports = dc => { }, item: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "项目名称", primaryKey: false, @@ -89,7 +89,7 @@ module.exports = dc => { }, amount: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "合同金额:【合同金额\n(元)】", primaryKey: false, @@ -98,7 +98,7 @@ module.exports = dc => { }, changeAmount: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "变更后合同金额", primaryKey: false, @@ -107,7 +107,7 @@ module.exports = dc => { }, receivableYear: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "回款年份", primaryKey: false, @@ -116,7 +116,7 @@ module.exports = dc => { }, receivableDate: { type: DataTypes.DATEONLY, - allowNull: false, + allowNull: true, defaultValue: null, comment: "回款日期", primaryKey: false, @@ -125,7 +125,7 @@ module.exports = dc => { }, receivableAmount: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "回款金额", primaryKey: false, @@ -134,7 +134,7 @@ module.exports = dc => { }, invoicedBack: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "开票-回款", primaryKey: false, @@ -143,7 +143,7 @@ module.exports = dc => { }, remainConAmount: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, defaultValue: null, comment: "剩余合同金额", primaryKey: false, @@ -152,7 +152,7 @@ module.exports = dc => { }, incomeConfirmdate: { type: DataTypes.DATEONLY, - allowNull: false, + allowNull: true, defaultValue: null, comment: "收入确认时间", primaryKey: false, diff --git a/api/app/lib/routes/report/index.js b/api/app/lib/routes/report/index.js index b986cd6..aedc624 100644 --- a/api/app/lib/routes/report/index.js +++ b/api/app/lib/routes/report/index.js @@ -16,6 +16,26 @@ module.exports = function (app, router, opts) { app.fs.api.logAttr['GET/detail/achievement'] = { content: '查询业绩明细表', visible: false }; router.get('/detail/achievement', achieve.getAchievementDetail); + + + + app.fs.api.logAttr['GET/detail/received/numbers'] = { content: '查询业绩明细已有的编号集合', visible: false }; + router.get('/detail/received/numbers', achieve.getReceivedNumbers); + + app.fs.api.logAttr['POST/add/received/back/bulk'] = { content: '导入回款明细', visible: true }; + router.post('/add/received/back/bulk', achieve.importBackDetails); + + app.fs.api.logAttr['POST/add/achievement/bulk'] = { content: '导入业绩明细', visible: true }; + router.post('/add/achievement/bulk', achieve.importAchieveDetails); app.fs.api.logAttr['GET/contract/detail'] = { content: '查询合同明细表', visible: false }; router.get('/contract/detail', achieve.getContractDetail); + + app.fs.api.logAttr['GET/invoicing/detail'] = { content: '查询开票明细表', visible: false }; + router.get('/invoicing/detail', achieve.getInvoicingDetail); + + app.fs.api.logAttr['GET/export/contract/detail'] = { content: '导出合同明细表', visible: false }; + router.get('/export/contract/detail', achieve.exportContractDetail); + + app.fs.api.logAttr['GET/export/invoicing/detail'] = { content: '导出开票明细表', visible: false }; + router.get('/export/invoicing/detail', achieve.exportInvoicingDetail); }; \ No newline at end of file diff --git a/api/app/lib/utils/xlsxDownload.js b/api/app/lib/utils/xlsxDownload.js index 1aa03e7..c52d445 100644 --- a/api/app/lib/utils/xlsxDownload.js +++ b/api/app/lib/utils/xlsxDownload.js @@ -19,7 +19,7 @@ module.exports = function (app, opts) { } } - async function simpleExcelDown({ data = [], header = [], fileName = moment().format('YYYY-MM-DD HH:mm:ss') } = {}) { + async function simpleExcelDown({ data = [], header = [], fileName = moment().format('YYYY-MM-DD HH:mm:ss'), needIndexCell = true } = {}) { const fileDirPath = path.join(__dirname, `../../downloadFiles`) makeDir(fileDirPath) const file = new xlsx.File(); @@ -35,9 +35,11 @@ module.exports = function (app, opts) { headerStyle.border.bottomColor = '#000000'; const headerRow = sheet_1.addRow(); - const indexCell = headerRow.addCell(); - indexCell.value = '序号' - indexCell.style = headerStyle + if (needIndexCell) { + const indexCell = headerRow.addCell(); + indexCell.value = '序号' + indexCell.style = headerStyle + } for (let h of header) { const cell = headerRow.addCell(); cell.value = h.title; @@ -54,9 +56,11 @@ module.exports = function (app, opts) { style.border.bottomColor = '#000000'; for (let i = 0; i < data.length; i++) { const row = sheet_1.addRow(); - const indexCell = row.addCell(); - indexCell.value = i + 1 - indexCell.style = headerStyle + if (needIndexCell) { + const indexCell = row.addCell(); + indexCell.value = i + 1 + indexCell.style = headerStyle + } for (let h of header) { const cell = row.addCell(); cell.value = data[i][h.key] || h.defaultValue || '-'; diff --git a/web/client/src/layout/actions/global.js b/web/client/src/layout/actions/global.js index 4d019aa..4bbf599 100644 --- a/web/client/src/layout/actions/global.js +++ b/web/client/src/layout/actions/global.js @@ -38,6 +38,7 @@ export function initApiRoot () { type: INIT_API_ROOT, payload: { apiRoot: res.root, + webPepUrl:res.webPepUrl } }) }); diff --git a/web/client/src/layout/containers/layout/index.jsx b/web/client/src/layout/containers/layout/index.jsx index 9c7eb26..31a1186 100644 --- a/web/client/src/layout/containers/layout/index.jsx +++ b/web/client/src/layout/containers/layout/index.jsx @@ -10,9 +10,13 @@ import { resize } from '../../actions/global'; import * as NProgress from 'nprogress'; import PerfectScrollbar from 'perfect-scrollbar'; import { useLocation } from "react-router"; +import { RouteTable } from '$utils'; +import { RouteRequest } from '@peace/utils'; +import Cookie from 'js-cookie'; +import { login, LOGIN_SUCCESS } from '../../../sections/auth/actions/auth'; NProgress.configure({ - template: ` + template: `
@@ -23,303 +27,337 @@ NProgress.configure({ }); let scrollbar +let requestUser = true +let requestlogout = false; + // const location111 = useLocation(); const LayoutContainer = props => { - const { - dispatch, msg, user, copyright, children, sections, clientWidth, clientHeight, - location, match, routes, history, socket, - } = props - const [collapsed, setCollapsed] = useState(false) + const { + dispatch, actions, msg, user, copyright, children, sections, clientWidth, clientHeight, + location, match, routes, history, socket, apiRoot, webPepUrl + } = props + const [collapsed, setCollapsed] = useState(false) - NProgress.start(); + NProgress.start(); - const resize_ = () => { - dispatch(resize( - document.getElementById('DrApp').clientHeight, - document.getElementById('DrApp').clientWidth - (collapsed ? 120 : 240) - )); - } - function deepCopy(data) {//深拷贝 - //string,number,bool,null,undefined,symbol - //object,array,date - if (data && typeof data === "object") { - //针对函数的拷贝 - if (typeof data === "function") { - let tempFunc = data.bind(null); - tempFunc.prototype = deepCopy(data.prototype); - return tempFunc; - } + const resize_ = () => { + dispatch(resize( + document.getElementById('DrApp').clientHeight, + document.getElementById('DrApp').clientWidth - (collapsed ? 120 : 240) + )); + } + function deepCopy (data) {//深拷贝 + //string,number,bool,null,undefined,symbol + //object,array,date + if (data && typeof data === "object") { + //针对函数的拷贝 + if (typeof data === "function") { + let tempFunc = data.bind(null); + tempFunc.prototype = deepCopy(data.prototype); + return tempFunc; + } - switch (Object.prototype.toString.call(data)) { - case "[object String]": - return data.toString(); - case "[object Number]": - return Number(data.toString()); - case "[object Boolean]": - return new Boolean(data.toString()); - case "[object Date]": - return new Date(data.getTime()); - case "[object Array]": - var arr = []; - for (let i = 0; i < data.length; i++) { - arr[i] = deepCopy(data[i]); - } - return arr; + switch (Object.prototype.toString.call(data)) { + case "[object String]": + return data.toString(); + case "[object Number]": + return Number(data.toString()); + case "[object Boolean]": + return new Boolean(data.toString()); + case "[object Date]": + return new Date(data.getTime()); + case "[object Array]": + var arr = []; + for (let i = 0; i < data.length; i++) { + arr[i] = deepCopy(data[i]); + } + return arr; - //js自带对象或用户自定义类实例 - case "[object Object]": - var obj = {}; - for (let key in data) { - //会遍历原型链上的属性方法,可以用hasOwnProperty来控制 (obj.hasOwnProperty(prop) - obj[key] = deepCopy(data[key]); - } - return obj; - } - } else { - //string,number,bool,null,undefined,symbol - return data; - } - } - const [allItems, setAllItems] = useState([]) - // const [headerItems, setHeaderItems] = useState([]) - const [leftItems, setLeftItems] = useState([]) - const [leftChange, setLeftChange] = useState(true) - const [leftShow, setLeftShow] = useState(false) - useEffect(() => { - let topItems = []//头部导航 - const lastSelectedKeys = localStorage.getItem('poms_selected_sider') - let nextItems = []//所有导航 - for (let c of sections) { - if (typeof c.getNavItem == 'function') { - let item = c.getNavItem(user, dispatch); - if (item) { - nextItems.push.apply(nextItems, item) - if (item.length > 0) { - for (let j = 0; j < item.length; j++) { - let itm = deepCopy(item[j]); - if (itm.hasOwnProperty('items')) { - for (let i = 0; i < itm.items.length; i++) { - itm.items[i].fatherKey = itm.itemKey - delete itm.items[i].items - } - // topItems.push(itm) - // } - // else { - // topItems.push.apply(topItems, item) - } + //js自带对象或用户自定义类实例 + case "[object Object]": + var obj = {}; + for (let key in data) { + //会遍历原型链上的属性方法,可以用hasOwnProperty来控制 (obj.hasOwnProperty(prop) + obj[key] = deepCopy(data[key]); + } + return obj; + } + } else { + //string,number,bool,null,undefined,symbol + return data; + } + } + const [allItems, setAllItems] = useState([]) + // const [headerItems, setHeaderItems] = useState([]) + const [leftItems, setLeftItems] = useState([]) + const [leftChange, setLeftChange] = useState(true) + const [leftShow, setLeftShow] = useState(false) + useEffect(() => { + let topItems = []//头部导航 + const lastSelectedKeys = localStorage.getItem('poms_selected_sider') + let nextItems = []//所有导航 + for (let c of sections) { + if (typeof c.getNavItem == 'function') { + let item = c.getNavItem(user, dispatch); + if (item) { + nextItems.push.apply(nextItems, item) + if (item.length > 0) { + for (let j = 0; j < item.length; j++) { + let itm = deepCopy(item[j]); + if (itm.hasOwnProperty('items')) { + for (let i = 0; i < itm.items.length; i++) { + itm.items[i].fatherKey = itm.itemKey + delete itm.items[i].items } - } - } + // topItems.push(itm) + // } + // else { + // topItems.push.apply(topItems, item) + } + } + } } - } - setAllItems(nextItems) - // setHeaderItems(topItems) - if (lastSelectedKeys) {//如果有缓存 - for (let i = 0; i < nextItems.length; i++) { - if (JSON.parse(lastSelectedKeys)[0] == nextItems[i].itemKey) { + } + } + setAllItems(nextItems) + // setHeaderItems(topItems) + if (lastSelectedKeys) {//如果有缓存 + for (let i = 0; i < nextItems.length; i++) { + if (JSON.parse(lastSelectedKeys)[0] == nextItems[i].itemKey) { - // let openArr = [] - // for (let j = 0; j < nextItems[i].items.length; j++) { - // openArr.push(nextItems[i].items[j].itemKey) - // } - // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) - setLeftItems(nextItems[i].items) - } + // let openArr = [] + // for (let j = 0; j < nextItems[i].items.length; j++) { + // openArr.push(nextItems[i].items[j].itemKey) + // } + // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) + setLeftItems(nextItems[i].items) } - setLeftShow(true) - } - else { - setLeftShow(false) - } + } + setLeftShow(true) + } + else { + setLeftShow(false) + } - window.addEventListener('resize', resize_); - return () => { - window.removeEventListener('resize', resize_); - } - }, []) + window.addEventListener('resize', resize_); + return () => { + window.removeEventListener('resize', resize_); + } + }, []) - useEffect(() => { - let pathnameArr = location.pathname.split('/') - let openArr = [] - for (let i = 0; i < allItems.length; i++) { - if (allItems[i].items) { - for (let j = 0; j < allItems[i].items.length; j++) { - if (allItems[i].items[j].items) { - for (let k = 0; k < allItems[i].items[j].items.length; k++) { - if (allItems[i].items[j].items[k].to == location.pathname) { - for (let o = 0; o < allItems[i].items.length; o++) { - openArr.push(allItems[i].items[o].itemKey) - } - localStorage.setItem('poms_selected_sider', JSON.stringify([pathnameArr[1]])) - // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) - setLeftItems(allItems[i].items) - setLeftShow(true) - } + useEffect(() => { + let pathnameArr = location.pathname.split('/') + let openArr = [] + for (let i = 0; i < allItems.length; i++) { + if (allItems[i].items) { + for (let j = 0; j < allItems[i].items.length; j++) { + if (allItems[i].items[j].items) { + for (let k = 0; k < allItems[i].items[j].items.length; k++) { + if (allItems[i].items[j].items[k].to == location.pathname) { + for (let o = 0; o < allItems[i].items.length; o++) { + openArr.push(allItems[i].items[o].itemKey) } - } - } + localStorage.setItem('poms_selected_sider', JSON.stringify([pathnameArr[1]])) + // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) + setLeftItems(allItems[i].items) + setLeftShow(true) + } + } + } } - } - }, [location]) + } + } + }, [location]) - useEffect(() => { - NProgress.done(); - if ((!user || !user.authorized)) { - history.push('/signin'); - } - if (msg) { - if (msg.done) { - Notification.success({ - // title: msg.done, - content: msg.done, - duration: 2, - }) - } - if (msg.error) { - Notification.error({ - // title: msg.error, - content: msg.error, - duration: 2, - }) - } - } - const dom = document.getElementById('page-content'); - if (dom) { - if (!scrollbar) { - scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true }); - scrollbar.update(); - } else { - scrollbar.update(); - dom.scrollTop = 0; - } - } - }) + useEffect(() => { + NProgress.done(); + if ((!user || !user.authorized)) { + // history.push('/signin'); + getUserInfoByToken() + } + if (msg) { + if (msg.done) { + Notification.success({ + // title: msg.done, + content: msg.done, + duration: 2, + }) + } + if (msg.error) { + Notification.error({ + // title: msg.error, + content: msg.error, + duration: 2, + }) + } + } + const dom = document.getElementById('page-content'); + if (dom) { + if (!scrollbar) { + scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true }); + scrollbar.update(); + } else { + scrollbar.update(); + dom.scrollTop = 0; + } + } + }) + + const getUserInfoByToken = () => { + if (requestUser) { + requestUser = false; + RouteRequest.get(RouteTable.apiRoot).then(res => { + let token = Cookie.get('pepToken', { domain: res.domain }); + dispatch(login({ token })).then(res => { + console.log(res); + if (res.type == 'LOGIN_SUCCESS') { + const data = res.payload?.user || {} + history.push('/businessManagement/pmReport/reserveItemsReporting') + localStorage.setItem('poms_open_sider', JSON.stringify(["pmReport"])) + localStorage.setItem('poms_selected_sider', JSON.stringify(["businessManagement"])) + localStorage.setItem('word', JSON.stringify('')) //系统登录代码时候存的,不清楚意义 + dispatch(actions.layout.initWebSocket({ ioUrl: apiRoot, token: data.token, hrUserId: data.hrUserInfo && hrUserInfo.id })) + } else { + window.location.href = `${webPepUrl}/signin` + } + }, error => { + window.location.href = `${webPepUrl}/signin` + }) + }, error => { + message.error('鉴权失败', 5); + window.location.href = `${webPepUrl}/signin` + }) + } + } - // websocket 使用测试 - useEffect(() => { - // console.log(socket) - if (socket) { - socket.on('CAMERA_ONLINE', function (msg) { - console.info(msg); - if (msg.online == 'ON') { - Notification.success({ - title: 'Hi', - content: (
{msg.name}
已上线。
), - duration: 2, - }) - } - if (msg.online == 'OFF') { - Notification.error({ - title: 'Hi', - content: (
{msg.name}
发生离线。
), - duration: 2, - }) - } - }); - return () => { - socket.off("CAMERA_ONLINE"); - } - } - }, [socket]) - return ( - - { - <> - -
{ - setCollapsed(!collapsed); - }} - collapsed={collapsed} - history={history} - tochange={(val) => { - // setLeftChange(!leftChange) //会导致顶部右上角转小圈 - if (val.fatherKey) { - localStorage.setItem('poms_selected_sider', JSON.stringify([val.itemKey])); - const historyOpenKeys = localStorage.getItem('poms_open_sider'); - const openKeys = historyOpenKeys && JSON.parse(historyOpenKeys).concat(val.openKey || val.itemKey) || [val.openKey || val.itemKey] - localStorage.setItem('poms_open_sider', JSON.stringify(openKeys)); - // for (let i = 0; i < allItems.length; i++) { - // if (val.fatherKey == allItems[i].itemKey) { - // let openArr = [] - // for (let j = 0; j < allItems[i].items.length; j++) { - // openArr.push(allItems[i].items[j].itemKey) - // } - // localStorage.setItem('poms_selected_sider', JSON.stringify([val.fatherKey])) - // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) - // setLeftItems(allItems[i].items) - // } - // } - // setLeftShow(true) - } - else { - localStorage.setItem('poms_open_sider', JSON.stringify([])) - localStorage.removeItem('poms_selected_sider') - // setLeftShow(false) - } - history.push(val.to); - }} - /> - - - {leftShow ? ( - - ) : ('')} - -
-
-
- {children} -
- -
- -
-
-
-
- + // websocket 使用测试 + useEffect(() => { + // console.log(socket) + if (socket) { + socket.on('CAMERA_ONLINE', function (msg) { + console.info(msg); + if (msg.online == 'ON') { + Notification.success({ + title: 'Hi', + content: (
{msg.name}
已上线。
), + duration: 2, + }) + } + if (msg.online == 'OFF') { + Notification.error({ + title: 'Hi', + content: (
{msg.name}
发生离线。
), + duration: 2, + }) } - - ) + }); + return () => { + socket.off("CAMERA_ONLINE"); + } + } + }, [socket]) + + return ( + + { + <> + +
{ + setCollapsed(!collapsed); + }} + collapsed={collapsed} + history={history} + tochange={(val) => { + // setLeftChange(!leftChange) //会导致顶部右上角转小圈 + if (val.fatherKey) { + localStorage.setItem('poms_selected_sider', JSON.stringify([val.itemKey])); + const historyOpenKeys = localStorage.getItem('poms_open_sider'); + const openKeys = historyOpenKeys && JSON.parse(historyOpenKeys).concat(val.openKey || val.itemKey) || [val.openKey || val.itemKey] + localStorage.setItem('poms_open_sider', JSON.stringify(openKeys)); + // for (let i = 0; i < allItems.length; i++) { + // if (val.fatherKey == allItems[i].itemKey) { + // let openArr = [] + // for (let j = 0; j < allItems[i].items.length; j++) { + // openArr.push(allItems[i].items[j].itemKey) + // } + // localStorage.setItem('poms_selected_sider', JSON.stringify([val.fatherKey])) + // localStorage.setItem('poms_open_sider', JSON.stringify(openArr)) + // setLeftItems(allItems[i].items) + // } + // } + // setLeftShow(true) + } + else { + localStorage.setItem('poms_open_sider', JSON.stringify([])) + localStorage.removeItem('poms_selected_sider') + // setLeftShow(false) + } + history.push(val.to); + }} + /> + + + {leftShow ? ( + + ) : ('')} + +
+
+
+ {children} +
+ +
+ +
+
+
+
+ + } + + ) } -function mapStateToProps(state) { - const { global, auth, ajaxResponse, webSocket } = state; - return { - title: global.title, - copyright: global.copyright, - sections: global.sections, - actions: global.actions, - clientWidth: global.clientWidth, - clientHeight: global.clientHeight, - msg: ajaxResponse.msg, - user: auth.user, - socket: webSocket.socket - }; +function mapStateToProps (state) { + const { global, auth, ajaxResponse, webSocket } = state; + return { + title: global.title, + copyright: global.copyright, + sections: global.sections, + actions: global.actions, + clientWidth: global.clientWidth, + clientHeight: global.clientHeight, + msg: ajaxResponse.msg, + user: auth.user, + socket: webSocket.socket, + apiRoot: global.apiRoot, + webPepUrl: global.webPepUrl, + }; } export default connect(mapStateToProps)(LayoutContainer); \ No newline at end of file diff --git a/web/client/src/layout/reducers/global.js b/web/client/src/layout/reducers/global.js index 8187eb7..4f52077 100644 --- a/web/client/src/layout/reducers/global.js +++ b/web/client/src/layout/reducers/global.js @@ -11,6 +11,7 @@ function global (state = { clientHeight: 768, clientWidth: 1024, apiRoot: '', + webPepUrl: '' }, action) { const payload = action.payload; switch (action.type) { @@ -30,6 +31,7 @@ function global (state = { case INIT_API_ROOT: return Immutable.fromJS(state).merge({ apiRoot: payload.apiRoot, + webPepUrl: payload.webPepUrl }).toJS(); default: return state; diff --git a/web/client/src/sections/auth/actions/auth.js b/web/client/src/sections/auth/actions/auth.js index afcb759..18a1e59 100644 --- a/web/client/src/sections/auth/actions/auth.js +++ b/web/client/src/sections/auth/actions/auth.js @@ -4,7 +4,7 @@ import { ApiTable, AxyRequest, EmisRequest } from '$utils' import { Request } from '@peace/utils'; export const INIT_AUTH = 'INIT_AUTH'; -export function initAuth(userData) { +export function initAuth (userData) { const sessionUser = JSON.parse(sessionStorage.getItem('dcUser')) const user = userData || sessionUser || {}; if (user.authorized && !sessionUser) { @@ -21,11 +21,11 @@ export function initAuth(userData) { export const REQUEST_LOGIN = 'REQUEST_LOGIN'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_ERROR = 'LOGIN_ERROR'; -export function login(username, password) { +export function login ({ username, password, token }) { return dispatch => { dispatch({ type: REQUEST_LOGIN }); - if (!username || !password) { + if ((!username || !password) && !token) { dispatch({ type: LOGIN_ERROR, payload: { error: '请输入账号名和密码' } @@ -43,7 +43,7 @@ export function login(username, password) { // }, // }); - return Request.post(ApiTable.login, { username, password, code: 'HR' }) + return Request.post(ApiTable.login, { username, password, token, code: 'HR' }) .then(user => { sessionStorage.setItem('dcUser', JSON.stringify(user)); return dispatch({ @@ -63,7 +63,7 @@ export function login(username, password) { } export const LOGOUT = 'LOGOUT'; -export function logout() { +export function logout () { const user = JSON.parse(sessionStorage.getItem('dcUser')) user && user.token ? Request.put(ApiTable.logout, { diff --git a/web/client/src/sections/business/actions/achievement-report.js b/web/client/src/sections/business/actions/achievement-report.js index b7d5e94..66c27f3 100644 --- a/web/client/src/sections/business/actions/achievement-report.js +++ b/web/client/src/sections/business/actions/achievement-report.js @@ -28,6 +28,40 @@ export function getAchievementDetail(query) { }); } +//回款明细 编号集合 +export function getReceivedNumbers() { + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_RECEIVED_DETAIL_NUMBERS", + url: `${ApiTable.getReceivedNumbers}`, + msg: { option: "查询回款明细编号集合" }, + reducer: { name: "ReceivedDetailNumbers", params: { noClear: true } }, + }); +} +//导入回款明细 +export function importBackDetails(values) { + return dispatch => basicAction({ + type: 'post', + dispatch: dispatch, + actionType: 'RECEIVED_DETAIL_BULK_ADD', + url: ApiTable.importBackDetails, + data: values, + msg: { option: '导入回款明细' }, + }); +} + +//导入业绩明细 +export function importAchieveDetails(values) { + return dispatch => basicAction({ + type: 'post', + dispatch: dispatch, + actionType: 'ACHIEVEMENT_DETAIL_BULK_ADD', + url: ApiTable.importAchieveDetails, + data: values, + msg: { option: '导入业绩明细' }, + }); +} //查询合同明细表 export function getContractDetail(query) { return (dispatch) => basicAction({ @@ -39,4 +73,17 @@ export function getContractDetail(query) { msg: { option: "查询合同明细表" }, reducer: { name: "ContractDetail", params: { noClear: true } }, }); +} + +//查询开票明细表 +export function getInvoicingDetail(query) { + return (dispatch) => basicAction({ + type: "get", + dispatch: dispatch, + actionType: "GET_INVOICING_DETAIL", + query: query, + url: `${ApiTable.getInvoicingDetail}`, + msg: { option: "查询开票明细表" }, + reducer: { name: "InvoicingDetail", params: { noClear: true } }, + }); } \ No newline at end of file diff --git a/web/client/src/sections/business/constants/index.js b/web/client/src/sections/business/constants/index.js index 55ebc45..4cf96c3 100644 --- a/web/client/src/sections/business/constants/index.js +++ b/web/client/src/sections/business/constants/index.js @@ -71,4 +71,52 @@ export const invoicingDetailsColumnKeys = { taxRate13: '税率13%', taxRate9: '税率9%', taxRate6: '税率6%' +} + +export const backMoneyColumnKeys = { + year: '年度', + serialNo: '序号', + number: '编号', + department: '部门', + sale: '销售人员', + contractNo: '合同编号', + customer: '客户名称', + item: '项目名称', + amount: '合同金额', + changeAmount: '变更后合同金额', + receivableYear: '回款年份', + receivableDate: '回款日期', + receivableAmount: '回款金额', + invoicedBack: '开票-回款', + remainConAmount: '剩余合同金额', + incomeConfirmdate: '收入确认时间', + thirdPayment: '第三方付款单位', + remark: '备注', +} + +export const achievementColumnKeys = { + recConDate: '收到合同日期', + month: '月份', + department: '部门', + sale: '销售人员', + customer: '客户名称', + item: '项目名称', + amount: '合同金额', + realPerformance: '实际业绩', + assessmentPerformance: '考核业绩', + isApproval: '价格是否特批', + approvalProp: '特批折算比例', + cost: '预支提成及委外费用', + serviceLine: '业务线', + cusType: '客户类型', + industry: '行业', + source: '信息来源', + itemType: '项目类型', + cusProvince: '客户省份', + cusAttribute: '客户属性', + repurchaseCount: '复购次数', + reproducible: '是否可复制的业务路径', + outProvince: '省外业务1.1', + repurchase: '复购业务1.05', + isreproduce: '可复制的业务路径1.1', } \ No newline at end of file diff --git a/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx b/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx index 51e8039..c27d7a9 100644 --- a/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx +++ b/web/client/src/sections/business/containers/performanceReport/achievementDetails.jsx @@ -4,6 +4,8 @@ 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 { achievementColumnKeys } from '../../constants/index'; +import ImportAchieveModal from './importAchieveModal' import '../../style.less'; const AchievementDetails = (props) => { @@ -14,7 +16,8 @@ const AchievementDetails = (props) => { const [limits, setLimits] = useState()//每页实际条数 const [query, setQuery] = useState({ limit: 10, page: 0 }); //页码信息 const [tableData, setTableData] = useState([]); - const [exportUrl, setExportUrl] = useState('') + const [importModalV, setImportModalV] = useState(false); + const [exportUrl, setExportUrl] = useState(''); const page = useRef(query.page); function seachValueChange(value) { setKeyword(value) @@ -37,151 +40,22 @@ const AchievementDetails = (props) => { }) } - const columns = [{ - title: '收到合同日期', - dataIndex: 'recConDate', - key: 'recConDate', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '月份', - dataIndex: 'month', - key: 'month', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '部门', - dataIndex: 'department', - key: 'department', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '销售人员', - dataIndex: 'sale', - key: 'sale', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '客户名称', - dataIndex: 'customer', - key: 'customer', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '项目名称', - dataIndex: 'item', - key: 'item', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '合同金额', - dataIndex: 'amount', - key: 'amount', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '实际业绩', - dataIndex: 'realPerformance', - key: 'realPerformance', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '考核业绩', - dataIndex: 'assessmentPerformance', - key: 'assessmentPerformance', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '价格是否特批', - dataIndex: 'isApproval', - key: 'isApproval', - width: 120, - render: (text, record) => {text ? '是' : '否'} - }, { - title: '特批折算比例', - dataIndex: 'approvalProp', - key: 'approvalProp', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '预支提成及委外费用', - dataIndex: 'cost', - key: 'cost', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '业务线', - dataIndex: 'serviceLine', - key: 'serviceLine', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '客户类型', - dataIndex: 'cusType', - key: 'cusType', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '行业', - dataIndex: 'industry', - key: 'industry', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '信息来源', - dataIndex: 'source', - key: 'source', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '项目类型', - dataIndex: 'itemType', - key: 'itemType', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '客户省份', - dataIndex: 'cusProvince', - key: 'cusProvince', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '客户属性', - dataIndex: 'cusAttribute', - key: 'cusAttribute', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '复购次数', - dataIndex: 'repurchaseCount', - key: 'repurchaseCount', - width: 120, - render: (text, record) => {text || '-'} - }, { - title: '是否可复制的业务路径', - dataIndex: 'reproducible', - key: 'reproducible', - width: 120, - render: (text, record) => {text ? '是' : '否'} - }, { - title: '省外业务1.1', - dataIndex: 'outProvince', - key: 'outProvince', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '复购业务1.05', - dataIndex: 'repurchase', - key: 'repurchase', - width: 140, - render: (text, record) => {text || '-'} - }, { - title: '可复制的业务路径1.1', - dataIndex: 'isreproduce', - key: 'isreproduce', - width: 120, - render: (text, record) => {text || '-'} - }] + const renderColumns = (columnKeys) => { + let columns = []; + Object.keys(columnKeys).map(key => { + switch (key) { + default: + columns.push({ + title: columnKeys[key], dataIndex: key, key: key, width: 130, + render: (text, record) => ['isApproval', 'reproducible'].indexOf(key) != -1 ? JSON.stringify(text) === 'null' ? '-' : text ? '是' : '否' + : text === 0 ? text : text ? text : '-' + }); + break; + } + }) + return columns; + } + function handleRow(record, index) {// 给偶数行设置斑马纹 if (index % 2 === 0) { return { @@ -273,7 +147,7 @@ const AchievementDetails = (props) => { placeholder={SkeletonScreen()} > { + { + importModalV ? { + setImportModalV(false); + getAchievementDetails(); + }} /> : '' + } { exportUrl ?