diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json index 3a4a4a3..efc0fbb 100644 --- a/api/.vscode/launch.json +++ b/api/.vscode/launch.json @@ -19,9 +19,9 @@ "-g postgres://postgres:123@10.8.30.166:5432/hr-dev", "--redisHost 10.8.30.112", "--redisPort 6379", - // "--apiEmisUrl http://10.8.30.112:14000", + "--apiEmisUrl http://10.8.30.112:14000", // 测试 - "--apiEmisUrl http://10.8.30.161:1111", + // "--apiEmisUrl http://10.8.30.161:1111", "--qnak XuDgkao6cL0HidoMAPnA5OB10Mc_Ew08mpIfRJK5", "--qnsk yewcieZLzKZuDfig0wLZ9if9jKp2P_1jd3CMJPSa", "--qnbkt dev-hr", diff --git a/api/app/lib/controllers/attendance/index.js b/api/app/lib/controllers/attendance/index.js index d5b08c7..c01ca40 100644 --- a/api/app/lib/controllers/attendance/index.js +++ b/api/app/lib/controllers/attendance/index.js @@ -29,7 +29,9 @@ async function overtimeStatistic (ctx) { }) returnD.forEach(u => { - u.overtimeStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + let overtimeStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + u.overtimeDuration = overtimeStatistic.reduce((sum, os) => sum + os.duration, 0) + u.overtimeStatistic = overtimeStatistic }) ctx.status = 200; ctx.body = { @@ -204,7 +206,9 @@ async function vacateStatistic (ctx) { }) returnD.forEach(u => { - u.vacateStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + let vacateStatistic = sumRes.filter(s => s.pepUserId == u.pepUserId) + u.vacateDuration = vacateStatistic.reduce((sum, vs) => sum + vs.duration, 0) + u.vacateStatistic = vacateStatistic }) ctx.status = 200; ctx.body = { diff --git a/api/app/lib/controllers/auth/index.js b/api/app/lib/controllers/auth/index.js index 7b6c00f..022e665 100644 --- a/api/app/lib/controllers/auth/index.js +++ b/api/app/lib/controllers/auth/index.js @@ -10,9 +10,18 @@ async function login (ctx, next) { const models = ctx.fs.dc.models; const params = ctx.request.body; - const emisLoginRes = await ctx.app.fs.emisRequest.post('login', { - data: params - }) + let emisLoginRes = null + if (params.username && params.password) { + emisLoginRes = await ctx.app.fs.emisRequest.post('login', { + data: { ...params, code: 'HR' } + }) + } else if (params.token) { + emisLoginRes = await ctx.app.fs.emisRequest.get('user-info', { + query: { + token: params.token, code: 'HR' + } + }) + } if (!emisLoginRes) { throw "无此用户,请使用正确的登录信息" diff --git a/api/app/lib/controllers/salesDistribution/index.js b/api/app/lib/controllers/salesDistribution/index.js index 1e238cd..7da2d1f 100644 --- a/api/app/lib/controllers/salesDistribution/index.js +++ b/api/app/lib/controllers/salesDistribution/index.js @@ -47,20 +47,7 @@ async function salesList(ctx) { let rslt = [] res.rows.map(d => { - //let valid = false; let info = members.find(m => m.pepUserId == d.dataValues.pepUserId); - // if (info) { - // if (placeSearch) { - // let exist1 = d.dataValues.provinces.join(',').indexOf(placeSearch) != -1 - // let exist2 = d.dataValues.cities.join(',').indexOf(placeSearch) != -1 - // if (exist1 || exist2) { - // valid = true; - // } - // } else { - // valid = true; - // } - // } - //if (valid) { let item = { name: info.userName, userCode: info.userCode, @@ -71,10 +58,7 @@ async function salesList(ctx) { ...d.dataValues } rslt.push(item); - //} }) - // let end = Number(page) * Number(limit) + Number(limit) - // let arr = rslt.slice(Number(page) * Number(limit), end) ctx.status = 200; ctx.body = { count: res.count, @@ -92,7 +76,7 @@ async function salesList(ctx) { async function add(ctx) { try { const { models } = ctx.fs.dc; - const { pepUserId, provinces, cities } = ctx.request.body + const { pepUserId, provinces, cities, businessLines } = ctx.request.body const existRes = await models.SalesDistribution.findOne({ where: { pepUserId } @@ -102,7 +86,7 @@ async function add(ctx) { throw '当前销售人员信息已存在' } - let storageData = { pepUserId, provinces, cities, del: false } + let storageData = { pepUserId, provinces, cities, businessLines, del: false } if (existRes && existRes.del) { await models.SalesDistribution.update(storageData, { where: { pepUserId } @@ -123,7 +107,7 @@ async function add(ctx) { async function edit(ctx) { try { const { models } = ctx.fs.dc; - const { pepUserId, provinces, cities } = ctx.request.body + const { pepUserId, provinces, cities, businessLines } = ctx.request.body const existRes = await models.SalesDistribution.findOne({ where: { pepUserId } @@ -133,7 +117,7 @@ async function edit(ctx) { throw '当前销售人员信息不存在' } - let storageData = { pepUserId, provinces, cities, del: false } + let storageData = { pepUserId, provinces, cities, businessLines, del: false } await models.SalesDistribution.update(storageData, { where: { @@ -321,11 +305,12 @@ async function addSalesMemberBulk(ctx) { //处理编辑的 if (editArr.length) { for (let i in editArr) { - let { pepUserId, provinces, cities, del = false } = editArr[i]; + let { pepUserId, provinces, cities, businessLines, del = false } = editArr[i]; let dataToUpdate = { provinces, cities, + businessLines, del } await models.SalesDistribution.update(dataToUpdate, { where: { pepUserId: pepUserId } }); diff --git a/api/app/lib/models/sales_distribution.js b/api/app/lib/models/sales_distribution.js index bdc30dc..1d46830 100644 --- a/api/app/lib/models/sales_distribution.js +++ b/api/app/lib/models/sales_distribution.js @@ -51,6 +51,15 @@ module.exports = dc => { primaryKey: false, field: "del", autoIncrement: false + }, + businessLines: { + type: DataTypes.STRING, + allowNull: true, + defaultValue: null, + comment: null, + primaryKey: false, + field: "business_lines", + autoIncrement: false } }, { tableName: "sales_distribution", diff --git a/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql b/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql new file mode 100644 index 0000000..82a1085 --- /dev/null +++ b/doc/scripts/PEP V3.0.0/schema/1.sales_distribution_modify.sql @@ -0,0 +1,4 @@ + + +alter table sales_distribution + add "business_lines" text; \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/constans.jsx b/web/client/src/sections/humanAffairs/containers/salersDistribution/constans.jsx new file mode 100644 index 0000000..86adcba --- /dev/null +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/constans.jsx @@ -0,0 +1,4 @@ + + +export const businessLinesConst = ['市政', '地灾', '水利', '智慧城市', '工地', + '环保', '安防', '产品投标', '交通', '矿山', '产品线'] \ No newline at end of file diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js index 3723dc5..0a02075 100644 --- a/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/importSalersModal.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { Modal, Form, Button, Notification } from '@douyinfe/semi-ui'; import { IconUpload } from '@douyinfe/semi-icons'; import cityData from '../../components/city.json'; +import { businessLinesConst } from './constans' import XLSX from 'xlsx' //下载模板和上传文件读取 const ImportSalersModal = props => { @@ -38,7 +39,7 @@ const ImportSalersModal = props => { const dldCsvMb = () => { //表头 - let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市)\n" + let head = "员工编号,姓名,销售区域(省/直辖市),销售区域(市),业务线\n" //数据 //let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5 let templateCsv = "data:text/csv;charset=utf-8,\ufeff" + head; @@ -142,6 +143,19 @@ const ImportSalersModal = props => { return !noMark; } + const judgeLines = (businessLines) => { + if (!businessLines) {//可以不填 + return true; + } + let noMark = 0; + businessLines?.split('、')?.map(p => { + if (businessLinesConst.indexOf(p) == -1) { + noMark++ + } + }) + return !noMark; + } + const judgeNull = (value) => { return value ? String(value).trim().replace(/\s*/g, "") : null; } @@ -191,6 +205,7 @@ const ImportSalersModal = props => { let name = judgeNull(d['姓名']); let provinces = judgeNull(d['销售区域(省/直辖市)']); let cities = judgeNull(d['销售区域(市)']); + let businessLines = judgeNull(d['业务线']); if (!number) {//人员编号不为空,唯一,字母和数字 error(`第${i + 2}行人员编号为空,请填写`) return @@ -226,9 +241,14 @@ const ImportSalersModal = props => { error(`第${i + 2}行销售区域(市)错误`) return } + let bValid = judgeLines(businessLines); + if (!bValid) { + error(`第${i + 2}行业务线错误`) + return + } postData.push({ pepUserId: rzExist.pepUserId, name, number, - provinces: provinces || '', cities: cities || '', + provinces: provinces || '', cities: cities || '', businessLines: businessLines || '', del: false }) } @@ -252,6 +272,7 @@ const ImportSalersModal = props => {
姓名:必填,若与员工编号对应的项企用户姓名不同,将以项企数据为准;
销售区域(省/直辖市):必填,省或直辖市顿号隔开,如“北京市、江西省、江苏省”;
销售区域(市):非必填,归属所填省的地级市顿号隔开,如“南昌市、镇江市”。
+
业务线:非必填,顿号隔开,如“智慧城市、工地”。
diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx index 4477e3f..a40df0c 100644 --- a/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/personnelDistribution.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import { connect } from 'react-redux'; import moment from 'moment' import { Select, Input, Button, Popconfirm, Radio, Tooltip, Table, Pagination, Skeleton } from '@douyinfe/semi-ui'; @@ -100,19 +100,19 @@ const PersonnelDistribution = (props) => { title: '序号', dataIndex: 'id', key: 'id', - width: '5%', + width: 60, render: (text, record, index) => index + 1 }, { title: starHeader('姓名'), dataIndex: 'name', key: 'name', - width: '5%' + width: 80 }, { title: starHeader('部门名称'), dataIndex: 'department', key: 'department', - width: '18%', + width: 200, render: (text, r, index) => { let arrStr = text.map(t => t.name); return getMultis(arrStr); @@ -121,7 +121,7 @@ const PersonnelDistribution = (props) => { title: '销售区域(省/直辖市)', dataIndex: 'provinces', key: 'provinces', - width: '15%', + width: 160, render: (text, record, index) => { return getMultis(text?.split('、') || []); } @@ -129,40 +129,48 @@ const PersonnelDistribution = (props) => { title: '销售区域(市)', dataIndex: 'cities', key: 'cities', - width: '15%', + width: 160, render: (text, record, index) => { - return getMultis(text?.split('、') || []); + return text ? getMultis(text?.split('、') || []) : '-'; + } + }, { + title: '业务线', + dataIndex: 'businessLines', + key: 'businessLines', + width: 140, + render: (text, record, index) => { + return text ? getMultis(text?.split('、') || []) : '-'; } }, { title: starHeader('岗位'), dataIndex: 'post', key: 'post', - width: '11%', + width: 120, render: (text, record) => {text || '-'} }, { title: starHeader('入职时间'), dataIndex: 'hireDate', key: 'hireDate', - width: '8%', + width: 120, render: (text, record) => {text || '-'} }, { title: starHeader('转正时间'), dataIndex: 'regularDate', key: 'regularDate', - width: '8%', + width: 120, render: (text, record) => {text || '-'} }, { title: starHeader('工龄'), dataIndex: 'workYears', key: 'workYears', - width: '5%', + width: 120, render: (_, r, index) => { return (r.hireDate ? {String(moment(new Date()).diff(r.hireDate, 'years', true)).substring(0, 3) + '年'} : '-') }, }, { title: '操作', dataIndex: 'action', - width: '10%', + width: 120, render: (text, record) => { return
onEdit(record)}>编辑   @@ -186,7 +194,7 @@ const PersonnelDistribution = (props) => { } }); } - + const scroll = useMemo(() => ({}), []); return (
招聘
@@ -294,6 +302,7 @@ const PersonnelDistribution = (props) => { justifyContent: "space-between", padding: "20px 20px", }}> +
共{limits}条信息 diff --git a/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js index d5c2b33..09ca6a1 100644 --- a/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js +++ b/web/client/src/sections/humanAffairs/containers/salersDistribution/salesMemberModal.js @@ -3,10 +3,12 @@ import moment from 'moment'; import { connect } from "react-redux"; import { Select, Modal, Form, Notification } from "@douyinfe/semi-ui"; import cityData from '../../components/city.json'; +import { businessLinesConst } from './constans' const SalesMemberModal = (props) => { const { dispatch, actions, user, meetingList, onConfirm, getMultis, onCancel, close, rzMembers, dataToEdit } = props; const { humanAffairs } = actions; const form = useRef();//表单 + const [lineOptions, setLineOptions] = useState([]); const [options, setOptions] = useState([]); const [cityOptions, setCityOptions] = useState([]); const [peoplePro, setPeoplePro] = useState({}); //人员信息 @@ -18,6 +20,14 @@ const SalesMemberModal = (props) => { }) setOptions(optionItems); + + let lineOptions = businessLinesConst.map((l, index) => { + return + {l} + + }) + setLineOptions(lineOptions); + if (dataToEdit) { setPeoplePro(dataToEdit); onChange(dataToEdit.provinces?.split('、') || []);//市options @@ -59,6 +69,7 @@ const SalesMemberModal = (props) => { if (values.userCode == peoplePro.userCode) { values.provinces = values.provinces.join('、') values.cities = values.cities.join('、') + values.businessLines = values.businessLines.join('、') if (dataToEdit) { dispatch(humanAffairs.editSalesMember({ pepUserId: peoplePro.pepUserId, msg: '编辑销售人员信息', ...values })).then((res) => { if (res.success) { @@ -163,14 +174,6 @@ const SalesMemberModal = (props) => {
} - // const handleDeselect = (value) => {//删除 - // let ranges = cityData.find(td => td.name == value)?.children || [] - // if (ranges) { - // let formList = form.current.getValues().cities; - // } - - // } - const onClear = () => { form.current.setValue('cities', []) } @@ -212,36 +215,36 @@ const SalesMemberModal = (props) => { placeholder='请选择销售区域(省/直辖市)' multiple filter style={{ width: '100%' }} - // optionFilterProp='children' - // getPopupContainer={triggerNode => triggerNode.parentNode} - // filterOption={(input, option) => option.props.children - // .toLowerCase().indexOf(input.toLowerCase()) >= 0} - // value={selectedKeys || []} onClear={() => onClear()} onChange={value => onChange(value)} - //onDeselect={value => handleDeselect(value)} maxTagCount={5} > {options} triggerNode.parentNode} - // filterOption={(input, option) => option.props.children - // .toLowerCase().indexOf(input.toLowerCase()) >= 0} - // value={selectedKeys || []} - //onDeselect={value => handleDeselect(value)} maxTagCount={5} > {cityOptions} + + {lineOptions} + )