产品-薪酬统计分析管理

This commit is contained in:
黎永顺 2023-04-24 09:28:25 +08:00
parent 8f3320ae11
commit ac9aab6f5f
5 changed files with 380 additions and 46 deletions

View File

@ -42,3 +42,15 @@ export const reportStatisticsReportGetData = (params) => {
export const reportStatisticsItemSave = (params) => { export const reportStatisticsItemSave = (params) => {
return postFetch("/api/bs/hrmsalary/report/statistics/item/save", params); return postFetch("/api/bs/hrmsalary/report/statistics/item/save", params);
}; };
//保存数据范围及负责设置
export const reportStatisticsItemSaveSearchCondition = (params) => {
return postFetch("/api/bs/hrmsalary/report/statistics/item/saveSearchCondition", params);
};
//删除自定义统计项目
export const reportStatisticsItemDelete = (params) => {
return postFetch("/api/bs/hrmsalary/report/statistics/item/delete", params);
};
//获取薪酬统计报表查询条件
export const reportStatisticsItemGetSearchCondition = (params) => {
return postFetch("/api/bs/hrmsalary/report/statistics/item/getSearchCondition", params);
};

View File

@ -5,8 +5,9 @@
* Date: 2023/4/10 * Date: 2023/4/10
*/ */
import React, { Component } from "react"; import React, { Component } from "react";
import { Button } from "antd"; import { Button, message, Modal } from "antd";
import { import {
WeaBrowser,
WeaCheckbox, WeaCheckbox,
WeaDialog, WeaDialog,
WeaError, WeaError,
@ -17,7 +18,7 @@ import {
WeaLocaleProvider, WeaLocaleProvider,
WeaTable WeaTable
} from "ecCom"; } from "ecCom";
import { statisticsItemGetform } from "../../../apis/statistics"; import { reportStatisticsItemSave, statisticsItemGetform } from "../../../apis/statistics";
import "../index.less"; import "../index.less";
const { getLabel } = WeaLocaleProvider; const { getLabel } = WeaLocaleProvider;
@ -29,22 +30,174 @@ class CustomStatisticsItemsModal extends Component {
loading: false, loading: false,
columns: [], columns: [],
dataSource: [], dataSource: [],
formData: {} formData: {
itemValue: "", itemValueSpan: "",
itemName: ""
}
}; };
} }
componentWillReceiveProps(nextProps, nextContext) { componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.visible !== this.props.visible && nextProps.visible) { if (nextProps.visible !== this.props.visible && nextProps.visible) {
this.statisticsItemGetform(); this.statisticsItemGetform({ id: nextProps.statisticsItemId });
} else {
this.setState({
columns: [],
dataSource: [],
formData: {
itemValue: "", itemValueSpan: "",
itemName: ""
}
});
} }
} }
handleSaveStatisticalItems = () => {
const { dataSource, formData } = this.state;
const { id: statReportId, statisticsItemId } = this.props;
const { itemValue, itemName } = formData;
const isNoRules = _.some(dataSource, it => !!it.m2mValue || !!it.ratioValue || !!it.totalValue || !!it.y2yValue);
const isChainRequired = _.some(dataSource, it => !!it.m2mValue && (!it.m2mLowerLimit || !it.m2mUpperLimit));
const isChainValRight = _.some(dataSource, it => !!it.m2mValue && it.m2mLowerLimit !== 0 && it.m2mUpperLimit !== 0 && (Number(it.m2mLowerLimit) > Number(it.m2mUpperLimit)));
const isYoyRequired = _.some(dataSource, it => !!it.y2yValue && (!it.y2yLowerLimit || !it.y2yUpperLimit));
const isYoyValRight = _.some(dataSource, it => !!it.y2yValue && it.y2yLowerLimit !== 0 && it.y2yUpperLimit !== 0 && (Number(it.y2yLowerLimit) > Number(it.y2yUpperLimit)));
if (!itemValue && !itemName) {
this.refs.proError.showError();
this.refs.nameError.showError();
return;
}
if (!itemValue) {
this.refs.proError.showError();
return;
}
if (!itemName) {
this.refs.nameError.showError();
return;
}
if (!isNoRules) {
message.warning(getLabel(111, "请至少设置一个统计规则"));
return;
}
if (isChainRequired) {
message.warning(getLabel(111, "请完善环比增幅正常区间设置上下限"));
return;
}
if (isChainValRight) {
message.warning(getLabel(111, "环比增幅上下限设置错误"));
return;
}
if (isYoyRequired) {
message.warning(getLabel(111, "请完善同比增幅正常区间设置上下限"));
return;
}
if (isYoyValRight) {
message.warning(getLabel(111, "同比增幅上下限设置错误"));
return;
}
let payload = { statReportId, itemValue: itemValue.split(","), itemName };
payload = {
id: statisticsItemId,
...payload,
..._.reduce(dataSource, (pre, cur) => {
if (!!cur.m2mValue || !!cur.ratioValue || !!cur.totalValue || !!cur.y2yValue) {
const { y2yLowerLimit, y2yUpperLimit, m2mLowerLimit, m2mUpperLimit } = cur;
if (!!cur.m2mValue) {
return {
...pre,
[`${cur["id"]}Rule`]: {
m2mValue: cur.m2mValue.toString(),
ratioValue: cur.ratioValue.toString(),
totalValue: cur.totalValue.toString(),
y2yValue: cur.y2yValue.toString(),
m2mLowerLimit: m2mLowerLimit.toString(),
m2mUpperLimit: m2mUpperLimit.toString()
}
};
}
if (!!cur.m2mValue) {
return {
...pre,
[`${cur["id"]}Rule`]: {
m2mValue: cur.m2mValue.toString(),
ratioValue: cur.ratioValue.toString(),
totalValue: cur.totalValue.toString(),
y2yValue: cur.y2yValue.toString(),
y2yLowerLimit: y2yLowerLimit.toString(),
y2yUpperLimit: y2yUpperLimit.toString()
}
};
}
if (!!cur.y2yValue && !!cur.y2yValue) {
return {
...pre,
[`${cur["id"]}Rule`]: {
m2mValue: cur.m2mValue.toString(),
ratioValue: cur.ratioValue.toString(),
totalValue: cur.totalValue.toString(),
y2yValue: cur.y2yValue.toString(),
m2mLowerLimit: m2mLowerLimit.toString(),
m2mUpperLimit: m2mUpperLimit.toString(),
y2yLowerLimit: y2yLowerLimit.toString(),
y2yUpperLimit: y2yUpperLimit.toString()
}
};
}
return {
...pre,
[`${cur["id"]}Rule`]: {
m2mValue: cur.m2mValue.toString(),
ratioValue: cur.ratioValue.toString(),
totalValue: cur.totalValue.toString(),
y2yValue: cur.y2yValue.toString()
}
};
}
return { ...pre };
}, {})
};
if (statisticsItemId) {
Modal.confirm({
title: getLabel(111, "信息确认"),
content: getLabel(111, `确定要编辑统计项吗?编辑后,可能需要重新设置分析图设置。`),
onOk: () => this.reportStatisticsItemSave(payload)
});
} else {
this.reportStatisticsItemSave(payload);
}
};
reportStatisticsItemSave = (payload) => {
this.setState({ loading: true });
reportStatisticsItemSave(payload).then(({ status, errormsg }) => {
this.setState({ loading: false });
if (status) {
this.setState({
formData: {
itemValue: "", itemName: ""
}
}, () => this.props.onCancel(true));
} else {
message.error(errormsg);
}
}).catch(() => this.setState({ loading: false }));
};
statisticsItemGetform = (payload) => { statisticsItemGetform = (payload) => {
statisticsItemGetform(payload).then(({ status, data }) => { statisticsItemGetform(payload).then(({ status, data }) => {
if (status) { if (status) {
const { ruleData } = data; const { formData } = this.state;
const { ruleData, baseForm } = data;
const { data: dataDetail } = baseForm;
const { columns, data: dataSource } = ruleData; const { columns, data: dataSource } = ruleData;
this.setState({ columns, dataSource }); this.setState({
columns, dataSource,
formData: {
...formData,
itemName: dataDetail ? dataDetail.itemName : "",
itemValue: dataDetail ? _.map(dataDetail.itemValue, it => it.id).join() : "",
itemValueSpan: dataDetail ? _.map(dataDetail.itemValue, it => it.name).join() : ""
}
});
} }
}); });
}; };
@ -53,6 +206,13 @@ class CustomStatisticsItemsModal extends Component {
this.setState({ this.setState({
dataSource: _.map(dataSource, it => { dataSource: _.map(dataSource, it => {
if (it.id === id) { if (it.id === id) {
if (key !== "totalValue" && !!value && value !== "0") {
return {
...it,
totalValue: Number(value),
[key]: Number(value)
};
}
return { return {
...it, ...it,
[key]: Number(value) [key]: Number(value)
@ -66,6 +226,13 @@ class CustomStatisticsItemsModal extends Component {
const { dataSource } = this.state; const { dataSource } = this.state;
this.setState({ this.setState({
dataSource: _.map(dataSource, it => { dataSource: _.map(dataSource, it => {
if (key !== "totalValue" && !!val && val !== "0") {
return {
...it,
totalValue: Number(val),
[key]: Number(val)
};
}
return { return {
...it, ...it,
[key]: Number(val) [key]: Number(val)
@ -87,10 +254,22 @@ class CustomStatisticsItemsModal extends Component {
}) })
}); });
}; };
handleChangeStatisticalItems = (itemValue, _names, datas) => {
const { formData } = this.state;
this.setState({
formData: {
...formData,
itemValue,
itemValueSpan: _.map(datas, it => it.name).join(","),
itemName: datas.length === 1 ? _.map(datas, it => it.names).join(",") : ""
}
});
};
render() { render() {
const { loading, columns, dataSource, formData } = this.state; const { loading, columns, dataSource, formData } = this.state;
const { itemName } = formData; const { itemName, itemValue, itemValueSpan } = formData;
const { statisticsItemId } = this.props;
const cols = _.map(columns, it => { const cols = _.map(columns, it => {
const { text, column } = it; const { text, column } = it;
if (column === "ruleName" || column === "ratio" || column === "m2m" || column === "y2y") { if (column === "ruleName" || column === "ratio" || column === "m2m" || column === "y2y") {
@ -155,8 +334,9 @@ class CustomStatisticsItemsModal extends Component {
{...this.props} hasScroll buttons={[]} initLoadCss {...this.props} hasScroll buttons={[]} initLoadCss
title={ title={
<div className="itemsTitle"> <div className="itemsTitle">
<span>{getLabel(111, "新建自定义统计项目")}</span> <span>{statisticsItemId ? getLabel(111, "编辑自定义统计项目") : getLabel(111, "新建自定义统计项目")}</span>
<Button type="primary" loading={loading}>{getLabel(111, "保存")}</Button> <Button type="primary" loading={loading}
onClick={this.handleSaveStatisticalItems}>{getLabel(111, "保存")}</Button>
</div> </div>
} }
style={{ width: 900, height: 500 }} style={{ width: 900, height: 500 }}
@ -165,13 +345,45 @@ class CustomStatisticsItemsModal extends Component {
<div className="statisticItemsBox"> <div className="statisticItemsBox">
<WeaFormItem label={getLabel(111, "统计项目")} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> <WeaFormItem label={getLabel(111, "统计项目")} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<WeaError tipPosition="bottom" ref="proError" error={getLabel(111, "此项必填")}> <WeaError tipPosition="bottom" ref="proError" error={getLabel(111, "此项必填")}>
<WeaBrowser
title={getLabel(111, "统计项目")}
type={162}
viewAttr={3}
isSingle={false}
value={itemValue}
replaceDatas={itemValue ? _.map(itemValue.split(","), (it, idx) => ({
id: it,
name: itemValueSpan.split(",")[idx]
})) : []}
completeParams={{
type: 162,
fielddbtype: "browser.salaryItems",
f_weaver_belongto_usertype: "0"
}}
conditionDataParams={{
type: "browser.salaryItems",
fielddbtype: "browser.salaryItems",
f_weaver_belongto_usertype: "0"
}}
dataParams={{
type: "browser.salaryItems",
f_weaver_belongto_usertype: "0"
}}
destDataParams={{
type: "browser.salaryItems",
f_weaver_belongto_usertype: "0"
}}
isMultCheckbox
inputStyle={{ width: "100%" }}
onChange={this.handleChangeStatisticalItems}
/>
</WeaError> </WeaError>
</WeaFormItem> </WeaFormItem>
<WeaFormItem label={getLabel(111, "统计项名称")} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}> <WeaFormItem label={getLabel(111, "统计项名称")} labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<WeaError tipPosition="bottom" ref="nameError" error={getLabel(111, "此项必填")}> <WeaError tipPosition="bottom" ref="nameError" error={getLabel(111, "此项必填")}>
<WeaInput <WeaInput value={itemName} viewAttr={3}
value={itemName} onChange={itemName => this.setState({ formData: { ...formData, itemName } })}/>
viewAttr={3}/>
</WeaError> </WeaError>
</WeaFormItem> </WeaFormItem>
<div className="customRuleTableWrapper"> <div className="customRuleTableWrapper">

View File

@ -84,8 +84,8 @@ class ReportContent extends Component {
<Spin spinning={loading}> <Spin spinning={loading}>
<iframe <iframe
style={{ border: 0, width: "100%", height: "100%" }} style={{ border: 0, width: "100%", height: "100%" }}
src="http://localhost:7607/#/reportTable" // src="http://localhost:7607/#/reportTable"
// src="/spa/hrmSalary/hrmSalaryCalculateDetail/index.html#/reportTable" src="/spa/hrmSalary/hrmSalaryCalculateDetail/index.html#/reportTable"
id="atdTable" id="atdTable"
/> />
</Spin> </Spin>

View File

@ -20,10 +20,16 @@ import {
} from "ecCom"; } from "ecCom";
import CustomStatisticsItemsModal from "./customStatisticsItemsModal"; import CustomStatisticsItemsModal from "./customStatisticsItemsModal";
import moment from "moment"; import moment from "moment";
import { Button } from "antd"; import { Button, message, Modal } from "antd";
import { condition } from "./condition"; import { condition } from "./condition";
import { getSearchs } from "../../../util"; import { getSearchs } from "../../../util";
import { reportStatisticsItemSave } from "../../../apis/statistics"; import {
reportStatisticsItemDelete,
reportStatisticsItemGetSearchCondition,
reportStatisticsItemSaveSearchCondition,
statisticsItemList
} from "../../../apis/statistics";
import { commonEnumList } from "../../../apis/ruleconfig";
import "../index.less"; import "../index.less";
@ -36,9 +42,12 @@ class StatisticalMicroSettingsSlide extends Component {
loading: false, loading: false,
selectedRowKeys: [], selectedRowKeys: [],
conditions: [], conditions: [],
salaryMonth: [moment().startOf("year").format("YYYY-MM"), moment().format("YYYY-MM")], dataSource: [],
unitTypeList: [],
salaryMonth: [],
statisticalItemPayload: { statisticalItemPayload: {
visible: false, id: "", dimension: "" visible: false, id: "", dimension: "",
statisticsItemId: ""
} }
}; };
} }
@ -62,32 +71,99 @@ class StatisticalMicroSettingsSlide extends Component {
this.setState({ conditions }); this.setState({ conditions });
nextProps.form.initFormFields(condition); nextProps.form.initFormFields(condition);
} }
if (nextProps.id !== this.props.id && !_.isEmpty(nextProps.id)) {
this.reportStatisticsItemGetSearchCondition(nextProps.id);
this.statisticsItemList(nextProps.id).then(r => {
});
this.setState({
salaryMonth: [moment().startOf("year").format("YYYY-MM"), moment().format("YYYY-MM")]
});
}
if (nextProps.visible !== this.props.visible && !nextProps.visible) {
nextProps.form.resetForm();
this.setState({ selectedRowKeys: [] });
}
} }
reportStatisticsItemSave = () => { reportStatisticsItemGetSearchCondition = (id) => {
const { salaryMonth } = this.state; reportStatisticsItemGetSearchCondition({ id }).then(({ status, data }) => {
if (status) {
console.log(data);
}
});
};
reportStatisticsItemSaveSearchCondition = () => {
const { salaryMonth, dataSource } = this.state;
const { form, id, dimension } = this.props; const { form, id, dimension } = this.props;
const [salaryStartMonth, salaryEndMonth] = salaryMonth; const [salaryStartMonth, salaryEndMonth] = salaryMonth;
const { hiredate, ...extra } = form.getFormDatas(); const { hiredate, department, employee, position, subCompany, taxAgent } = form.getFormDatas();
const { value, valueSpan } = taxAgent;
if (!salaryEndMonth && !salaryStartMonth) { if (!salaryEndMonth && !salaryStartMonth) {
this.refs.weaError.showError(); this.refs.weaError.showError();
return; return;
} }
const payload = { const payload = {
dimension, id, dimension, id,
hiredate: hiredate.value, items: [], hiredate: hiredate.value,
department: _.map(department.valueObj, it => ({ id: it.id, name: it.name })),
employee: _.map(employee.valueObj, it => ({ id: it.id, name: it.name })),
position: _.map(position.valueObj, it => ({ id: it.id, name: it.name })),
subCompany: _.map(subCompany.valueObj, it => ({ id: it.id, name: it.name })),
taxAgent: value ? _.map(value.split(","), (it, idx) => ({ id: it, name: valueSpan.split(",")[idx] })) : [],
items: dataSource,
salaryEndMonth, salaryEndMonth,
salaryStartMonth salaryStartMonth
}; };
console.log(payload, extra);
return;
this.setState({ loading: true }); this.setState({ loading: true });
reportStatisticsItemSave(payload).then(({ status, data }) => { reportStatisticsItemSaveSearchCondition(payload).then(({ status, errormsg }) => {
this.setState({ loading: false }); this.setState({ loading: false });
console.log(status, data); if (status) {
message.success(getLabel(111, "保存成功"));
} else {
message.error(errormsg);
}
}).catch(() => this.setState({ loading: false })); }).catch(() => this.setState({ loading: false }));
}; };
reportStatisticsItemDelete = () => {
Modal.confirm({
title: getLabel(111, "信息确认"),
content: getLabel(111, "确认要删除吗?"),
onOk: () => {
const { selectedRowKeys } = this.state;
reportStatisticsItemDelete(selectedRowKeys).then(({ status, errormsg }) => {
if (status) {
message.success(getLabel(111, "删除成功"));
this.setState({
selectedRowKeys: []
}, () => {
this.statisticsItemList(this.props.id).then(r => {
});
});
} else {
message.error(errormsg || getLabel(111, "删除失败"));
}
});
}
});
};
statisticsItemList = async (statisticsReportId = "") => {
const { data: unitTypeList } = await this.commonEnumList();
statisticsItemList({ statisticsReportId }).then(({ status, data }) => {
if (status) {
this.setState({
dataSource: data,
unitTypeList: _.map(unitTypeList, it => ({ key: it.value.toString(), showname: it.defaultLabel }))
});
}
});
};
commonEnumList = () => {
const payload = {
enumClass: "com.engine.salary.report.enums.UnitTypeEnum"
};
return commonEnumList(payload);
};
renderGroupTitle = () => { renderGroupTitle = () => {
return <div className="groupTitleWrapper"> return <div className="groupTitleWrapper">
<span>{getLabel(111, "统计数据范围")}</span> <span>{getLabel(111, "统计数据范围")}</span>
@ -95,6 +171,7 @@ class StatisticalMicroSettingsSlide extends Component {
</div>; </div>;
}; };
renderProjectTitle = () => { renderProjectTitle = () => {
const { selectedRowKeys } = this.state;
const { id, dimension } = this.props; const { id, dimension } = this.props;
return <div className="groupPorjectTitleWrapper"> return <div className="groupPorjectTitleWrapper">
<div> <div>
@ -104,40 +181,56 @@ class StatisticalMicroSettingsSlide extends Component {
/> />
</div> </div>
<div> <div>
<WeaButtonIcon buttonType="del" type="primary"/> <WeaButtonIcon
buttonType="del" type="primary" disabled={_.isEmpty(selectedRowKeys)}
onClick={this.reportStatisticsItemDelete}
/>
<WeaButtonIcon <WeaButtonIcon
buttonType="add" type="primary" buttonType="add" type="primary"
onClick={() => this.setState({ onClick={() => this.setState({
statisticalItemPayload: { visible: true, id, dimension } statisticalItemPayload: {
visible: true, id, dimension,
statisticsItemId: ""
}
})} })}
/> />
</div> </div>
</div>; </div>;
}; };
drop = datas => {
console.log("datas", datas);
};
render() { render() {
const { salaryMonth, conditions, selectedRowKeys, loading, statisticalItemPayload } = this.state; const {
salaryMonth, conditions, selectedRowKeys, loading,
statisticalItemPayload, dataSource, unitTypeList
} = this.state;
const { id, dimension } = this.props;
const columns = [ const columns = [
{ {
title: "统计项名称", title: "统计项名称",
dataIndex: "itemName" dataIndex: "itemName",
render: (txt, record) => {
return (
<a href="javascript: void(0);" onClick={() => this.setState({
statisticalItemPayload: { visible: true, id, dimension, statisticsItemId: record.id }
})}>{txt}</a>
);
}
}, },
{ {
title: "统计单位", title: "统计单位",
dataIndex: "unitType", dataIndex: "unitType",
render: () => { render: (txt, record) => {
return <WeaSelect options={[]} style={{ width: 150 }}/>; return <WeaSelect
value={!_.isNil(txt) ? txt.toString() : ""} options={unitTypeList} style={{ width: 150 }}
onChange={unitType => this.customStatisticsItemsRef.reportStatisticsItemSave({ id: record.id, unitType })}
/>;
} }
} }
]; ];
const rowSelection = { const rowSelection = {
selectedRowKeys, selectedRowKeys,
onChange: (selectedRowKeys) => { onChange: (selectedRowKeys) => {
this.setState({ selectedRowKeys }, () => { this.setState({ selectedRowKeys });
});
} }
}; };
return ( return (
@ -151,7 +244,7 @@ class StatisticalMicroSettingsSlide extends Component {
height={100} height={100}
measureY="%" measureY="%"
direction={"right"} direction={"right"}
title={<TitleDialog loading={loading} onSave={this.reportStatisticsItemSave}/>} title={<TitleDialog loading={loading} onSave={this.reportStatisticsItemSaveSearchCondition}/>}
content={ content={
<React.Fragment> <React.Fragment>
<WeaSearchGroup title={getLabel(111, "统计时间范围")} col={2} showGroup needTigger> <WeaSearchGroup title={getLabel(111, "统计时间范围")} col={2} showGroup needTigger>
@ -170,17 +263,25 @@ class StatisticalMicroSettingsSlide extends Component {
} }
<WeaSearchGroup title={this.renderProjectTitle()} showGroup needTigger> <WeaSearchGroup title={this.renderProjectTitle()} showGroup needTigger>
<WeaTable <WeaTable
rowKey="id"
columns={columns} columns={columns}
dataSource={[{}]} dataSource={dataSource}
draggable={true} draggable={true}
onDrop={this.drop} onDrop={dataSource => this.setState({ dataSource })}
pagination={false} pagination={false}
rowSelection={rowSelection} rowSelection={rowSelection}
/> />
<CustomStatisticsItemsModal {...statisticalItemPayload} <CustomStatisticsItemsModal
onCancel={() => this.setState({ ref={dom => this.customStatisticsItemsRef = dom}
statisticalItemPayload: { visible: false, id: "", dimension: "" } {...statisticalItemPayload}
})} onCancel={(isRefresh) => this.setState({
statisticalItemPayload: {
visible: false,
id: "",
dimension: "",
statisticsItemId: ""
}
}, () => isRefresh && this.statisticsItemList(this.props.id))}
/> />
</WeaSearchGroup> </WeaSearchGroup>
</React.Fragment> </React.Fragment>

View File

@ -98,7 +98,16 @@ class Index extends Component {
> >
<WeaLeftRightLayout <WeaLeftRightLayout
leftWidth={210} leftWidth={210}
leftCom={<LeftTab ref={dom => this.leftTabRef = dom} onChangeTab={report => this.setState({ report })}/>} leftCom={
<LeftTab
ref={dom => this.leftTabRef = dom}
onChangeTab={report =>
this.setState({
report,
statisticalPayload: { visible: false, id: "", dimension: "" }
})}
/>
}
> >
<div className="rightLayout"> <div className="rightLayout">
<div className="layoutHeader"> <div className="layoutHeader">