Эх сурвалжийг харах

财务报表改造,项目报表改造,部门报表改造,

xjj 2 жил өмнө
parent
commit
dc41e47952

+ 10 - 1
config/router.config.js

@@ -29,7 +29,8 @@ export default [
           },
           {
             path: '/home/report/project',
-            component: './PurchaseAdmin/PurchaseList/Report/Project',
+            // component: './PurchaseAdmin/PurchaseList/Report/Project',
+            component: './PurchaseAdmin/PurchaseList/Report/ProjectTree',
           },
           {
             path: '/home/report/department',
@@ -51,6 +52,14 @@ export default [
             path: '/home/demo',
             component: './PurchaseAdmin/PurchaseList/Report/Demo',
           },
+          {
+            path: '/home/report/finance/resources',
+            component: './PurchaseAdmin/PurchaseList/Report/Finance/Resources',
+          },
+          {
+            path: '/home/report/finance/project',
+            component: './PurchaseAdmin/PurchaseList/Report/Finance/Project',
+          },
         ],
       },
       {

+ 11 - 3
src/pages/PurchaseAdmin/PurchaseList/Index.js

@@ -85,9 +85,17 @@ function LayoutDetail(props) {
                     </Menu.Item>
                   )}
                   {checkReport(3) && (
-                    <Menu.Item key="/home/report/finance">
-                      <Link to="/home/report/finance">财务报表</Link>
-                    </Menu.Item>
+                    // <Menu.Item key="/home/report/finance">
+                    //   <Link to="/home/report/finance">财务报表</Link>
+                    // </Menu.Item>
+                    <SubMenu key="/home/report/finance" title="财务报表">
+                      <Menu.Item key="/home/report/finance/resources">
+                        <Link to="/home/report/finance/resources">资源中心人日使用汇总表</Link>
+                      </Menu.Item>
+                      <Menu.Item key="/home/report/finance/project">
+                        <Link to="/home/report/finance/project">执行项目人日汇总表</Link>
+                      </Menu.Item>
+                    </SubMenu>
                   )}
                 </SubMenu>
               )}

+ 0 - 1
src/pages/PurchaseAdmin/PurchaseList/Report/DepCompareModal.js

@@ -133,7 +133,6 @@ function DepCompareModal(props) {
           pagination={false}
           onExpand={onExpand}
         /> */}
-        {console.log(depCompare)}
         {depCompare.length > 0 && (
           <Table
             title={() => '其他部门在所属项目下产生的工时'}

+ 85 - 15
src/pages/PurchaseAdmin/PurchaseList/Report/Department.js

@@ -1,24 +1,44 @@
 import React, { useEffect, useState, useRef } from 'react';
 import { connect } from 'dva';
-import { Form, Table, DatePicker, Input, Button } from 'antd';
+import { Form, Table, DatePicker, Input, Button, Empty, Card, Affix } from 'antd';
 import styles from './report.less';
 import UserRptModal from './UserRptModal';
 import DepCompareModal from './DepCompareModal';
 import moment from 'moment';
 import { downloadFile, getToken } from '@/utils/utils.js';
+import * as echarts from 'echarts';
+import { CloseOutlined } from '@ant-design/icons';
 
 const { RangePicker } = DatePicker;
+const initData = [
+  // 上个月第一天
+  moment()
+    .subtract(1, 'month')
+    .startOf('month'),
+  // 上个月最后一天
+  moment()
+    .subtract(1, 'month')
+    .endOf('month'),
+];
 
 function Department(props) {
   const { dispatch, loading, dep } = props;
   const [form] = Form.useForm();
   const [visible, setVisible] = useState(false);
   const [modalFilter, setModalFilter] = useState({});
+  const [current, setCurrent] = useState(null);
+  const chartRef = useRef(null);
   const columns = [
     {
       title: '部门名称',
       // render: record => <a onClick={() => showUserModal(record)}>{record.dep_name}</a>,
-      render: record => <a onClick={() => showDepCompare(record)}>{record.dep_name}</a>,
+      render: record => <a onClick={() => setCurrent(record)}>{record.dep_name}</a>,
+      width: '32%',
+    },
+    {
+      title: '有效利用率',
+      dataIndex: 'usage_percent',
+      render: percent => (percent * 100).toFixed(2) + '%',
     },
     {
       title: '执行项目人日',
@@ -53,9 +73,9 @@ function Department(props) {
       dataIndex: 'total_cnt',
     },
     {
-      title: '有效利用率',
-      dataIndex: 'usage_percent',
-      render: percent => (percent * 100).toFixed(2) + '%',
+      title: '操作',
+      width: 80,
+      render: item => <a onClick={() => showDepCompare(item)}>详情</a>,
     },
     // {
     //   title: '付费工时数',
@@ -92,7 +112,7 @@ function Department(props) {
   const renderSearch = () => {
     return (
       <Form layout="inline" form={form}>
-        <Form.Item label="时间" name="time" initialValue={[moment().startOf('years'), moment()]}>
+        <Form.Item label="时间" name="time" initialValue={initData}>
           <RangePicker placeholder="选择时间" allowClear={false} />
         </Form.Item>
         <Form.Item>
@@ -124,13 +144,51 @@ function Department(props) {
     });
     setVisible(true);
   };
+  const renderChart = item => {
+    let data = [
+      { value: item.type_project_cnt, name: '执行项目人日' },
+      { value: item.type_sale_cnt, name: '售前支持' },
+      { value: item.type_market_cnt, name: '市场品牌' },
+      { value: item.type_normal_cnt, name: '日常' },
+      { value: item.type_standardize_cnt, name: '标准化' },
+      { value: item.type_rd_cnt, name: '研发' },
+    ];
+    // 过滤为0的值
+    data = data.filter(item => item.value);
+    chartRef.current.setOption({
+      tooltip: {
+        trigger: 'item',
+      },
+      series: [
+        {
+          type: 'pie',
+          radius: '70%',
+          data: data,
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)',
+            },
+          },
+        },
+      ],
+    });
+  };
   useEffect(() => {
     // dispatch({
     //   type: 'report/queryUserReport',
     // });
     handleSearch();
+    chartRef.current = echarts.init(document.getElementById('chart'));
   }, []);
 
+  useEffect(() => {
+    if (current) {
+      renderChart(current);
+    }
+  }, [current]);
+
   return (
     <div>
       <div className={styles.topPart}>
@@ -139,15 +197,27 @@ function Department(props) {
           导出
         </Button>
       </div>
-      <Table
-        loading={loading}
-        rowKey="dep_id"
-        style={{ marginTop: 20 }}
-        columns={columns}
-        dataSource={dep.list}
-        pagination={false}
-        onExpand={onExpand}
-      />
+      <div style={{ marginTop: 20, display: 'flex' }}>
+        <Table
+          loading={loading}
+          rowKey="dep_id"
+          style={{ width: '100%' }}
+          columns={columns}
+          dataSource={dep.list}
+          pagination={false}
+          onExpand={onExpand}
+        />
+        <Affix offsetTop={20}>
+          <Card
+            extra={<CloseOutlined onClick={() => setCurrent(null)} />}
+            title={current?.dep_name}
+            style={{ display: current ? 'block' : 'none', marginLeft: 20 }}
+          >
+            <div id="chart" style={{ width: 400, height: 340 }}></div>
+          </Card>
+        </Affix>
+      </div>
+
       {/* <UserRptModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} /> */}
       <DepCompareModal filter={modalFilter} visible={visible} onCancel={() => setVisible(false)} />
     </div>

+ 156 - 0
src/pages/PurchaseAdmin/PurchaseList/Report/Finance/Project.js

@@ -0,0 +1,156 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { connect } from 'dva';
+import { Form, Table, DatePicker, Input, Button, Select, Modal } from 'antd';
+import styles from '../report.less';
+import moment from 'moment';
+import { downloadFile, getToken } from '@/utils/utils.js';
+import { queryFinanceProjDetail } from '@/services/workHours';
+
+const { RangePicker } = DatePicker;
+
+function FinanceProject(props) {
+  const { dispatch, loading, project } = props;
+  const [form] = Form.useForm();
+  const [visible, setVisible] = useState(false);
+  const [current, setCurrent] = useState(null);
+
+  const columns = [
+    {
+      title: '序号',
+      render: (_, __, index) => {
+        const { current, pageSize } = project.pagination;
+        return (current - 1) * pageSize + index + 1;
+      },
+    },
+    {
+      title: '项目名称',
+      dataIndex: 'name',
+      render: (name, item) => <a onClick={() => handleClick(item)}>{name}</a>,
+    },
+    { title: '项目编号', dataIndex: 'code' },
+    { title: '本月人日数', dataIndex: 'month_workload' },
+    { title: '截止到目前为止的总人日数', dataIndex: 'total_workload' },
+    { title: '项目预算人日 ', dataIndex: 'budget' },
+  ];
+
+  const filterRef = useRef({ pageSize: 20 });
+  const onChangePage = pagination => {
+    dispatch({
+      type: 'finance/queryFinanceProjReport',
+      payload: {
+        ...filterRef.current,
+        currentPage: pagination.current,
+      },
+    });
+  };
+  const handleSearch = () => {
+    const { time } = form.getFieldsValue();
+    filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
+    filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
+
+    dispatch({
+      type: 'finance/queryFinanceProjReport',
+      payload: {
+        ...filterRef.current,
+      },
+    });
+  };
+
+  const handleClick = item => {
+    setCurrent({
+      s_time: filterRef.current.s_time,
+      e_time: filterRef.current.e_time,
+      project_id: item.project_id,
+      project_code: item.code,
+      project_name: item.name,
+    });
+    setVisible(true);
+  };
+
+  const renderSearch = () => {
+    return (
+      <Form layout="inline" form={form}>
+        <Form.Item label="时间" name="time" initialValue={[moment().startOf('years'), moment()]}>
+          <RangePicker placeholder="选择时间" allowClear={false} />
+        </Form.Item>
+        <Form.Item>
+          <Button type="primary" loading={loading} onClick={handleSearch}>
+            查询
+          </Button>
+        </Form.Item>
+      </Form>
+    );
+  };
+  useEffect(() => {
+    handleSearch();
+  }, []);
+
+  return (
+    <div>
+      <div className={styles.topPart}>{renderSearch()}</div>
+      <Table
+        loading={loading}
+        style={{ marginTop: 20 }}
+        rowKey={`code`}
+        columns={columns}
+        dataSource={project.list}
+        pagination={project.pagination}
+        onChange={onChangePage}
+      />
+      <FinanceProjectModal data={current} visible={visible} onCancel={() => setVisible(false)} />
+    </div>
+  );
+}
+
+function FinanceProjectModal(props) {
+  const { visible, data, onCancel } = props;
+  const [list, setList] = useState([]);
+  const [loading, setLoading] = useState(false);
+  const columns = [
+    // { title: '项目名称', dataIndex: 'name' },
+    // { title: '项目编号', dataIndex: 'code' },
+    { title: '分项工作代码', dataIndex: 'name' },
+    { title: '本月人日数', dataIndex: 'month_workload' },
+    { title: '累计人日数', dataIndex: 'total_workload' },
+    { title: '项目预算人日 ', dataIndex: 'budget' },
+  ];
+
+  const queryList = async () => {
+    setLoading(true);
+    let res = await queryFinanceProjDetail({
+      s_time: data.s_time,
+      e_time: data.e_time,
+      project_id: data.project_id,
+    });
+    if (res) {
+      setList(res.data);
+    }
+    setLoading(false);
+  };
+  useEffect(() => {
+    if (visible && data) {
+      queryList();
+    }
+    if (!visible) {
+      setList([]);
+    }
+  }, [visible]);
+
+  return (
+    <Modal
+      title={data ? `${data.project_name}【${data.project_code}】` : '详情'}
+      width="80%"
+      open={visible}
+      onCancel={onCancel}
+      footer={false}
+      destroyOnClose
+    >
+      <Table columns={columns} loading={loading} dataSource={list} rowKey="name"></Table>
+    </Modal>
+  );
+}
+
+export default connect(({ finance, loading }) => ({
+  project: finance.project,
+  loading: loading.models.finance,
+}))(FinanceProject);

+ 78 - 0
src/pages/PurchaseAdmin/PurchaseList/Report/Finance/Resources.js

@@ -0,0 +1,78 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { connect } from 'dva';
+import { Form, Table, DatePicker, Input, Button, Select } from 'antd';
+import styles from '../report.less';
+import moment from 'moment';
+import { downloadFile, getToken } from '@/utils/utils.js';
+
+const { RangePicker } = DatePicker;
+
+function FinanceResource(props) {
+  const { dispatch, loading, resource } = props;
+  const [form] = Form.useForm();
+  const columns = [
+    {
+      title: '序号',
+      render: (_, __, index) => index + 1,
+    },
+    { title: '事业部名称 ', dataIndex: 'dep_name' },
+    { title: '资源中心人日使用量', dataIndex: 'workload' },
+    { title: '技术中心人日数', dataIndex: 'tech_workload' },
+    { title: '招采中心人日数', dataIndex: 'purchase_workload' },
+    { title: '技术中心售前人日数', dataIndex: 'tech_sale_workload' },
+    { title: '技术中心执行人日数', dataIndex: 'tech_exe_workload' },
+    { title: '招采中心售前人日数', dataIndex: 'purchase_sale_workload' },
+    { title: '招采中心执行人日数', dataIndex: 'purchase_exe_workload' },
+  ];
+
+  const handleSearch = () => {
+    const { time } = form.getFieldsValue();
+    let s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
+    let e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
+
+    dispatch({
+      type: 'finance/queryFinanceResReport',
+      payload: {
+        s_time,
+        e_time,
+      },
+    });
+  };
+
+  const renderSearch = () => {
+    return (
+      <Form layout="inline" form={form}>
+        <Form.Item label="时间" name="time" initialValue={[moment().startOf('years'), moment()]}>
+          <RangePicker placeholder="选择时间" allowClear={false} />
+        </Form.Item>
+        <Form.Item>
+          <Button type="primary" loading={loading} onClick={handleSearch}>
+            查询
+          </Button>
+        </Form.Item>
+      </Form>
+    );
+  };
+  useEffect(() => {
+    handleSearch();
+  }, []);
+
+  return (
+    <div>
+      <div className={styles.topPart}>{renderSearch()}</div>
+      <Table
+        loading={loading}
+        style={{ marginTop: 20 }}
+        rowKey={'dep_name'}
+        columns={columns}
+        dataSource={resource}
+        pagination={false}
+      />
+    </div>
+  );
+}
+
+export default connect(({ finance, loading }) => ({
+  resource: finance.resource,
+  loading: loading.models.finance,
+}))(FinanceResource);

+ 47 - 0
src/pages/PurchaseAdmin/PurchaseList/Report/Finance/models/finance.js

@@ -0,0 +1,47 @@
+import {
+  queryFinanceResReport,
+  queryFinanceProjReport,
+  queryFinanceProjDetail,
+} from '@/services/workHours';
+import { message } from 'antd';
+
+export default {
+  namespace: 'finance',
+  state: {
+    resource: [],
+    project: {
+      list: [],
+      pagination: {},
+    },
+  },
+
+  effects: {
+    *queryFinanceResReport({ payload = {} }, { call, put }) {
+      const res = yield call(queryFinanceResReport, payload);
+      if (res) {
+        yield put({
+          type: 'save',
+          payload: { resource: res.data },
+        });
+      }
+    },
+    *queryFinanceProjReport({ payload = {} }, { call, put }) {
+      const res = yield call(queryFinanceProjReport, payload);
+      if (res) {
+        yield put({
+          type: 'save',
+          payload: { project: res.data },
+        });
+      }
+    },
+  },
+
+  reducers: {
+    save(state, action) {
+      return {
+        ...state,
+        ...action.payload,
+      };
+    },
+  },
+};

+ 192 - 0
src/pages/PurchaseAdmin/PurchaseList/Report/ProjectTree.js

@@ -0,0 +1,192 @@
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import { connect } from 'dva';
+import { Form, Table, DatePicker, Input, Button, Select, Modal } from 'antd';
+import styles from './report.less';
+import moment from 'moment';
+import { downloadFile, getToken } from '@/utils/utils.js';
+import { queryFinanceProjDetail } from '@/services/workHours';
+
+const { RangePicker } = DatePicker;
+
+function ProjectTree(props) {
+  const { dispatch, loading, projectNew } = props;
+  const [form] = Form.useForm();
+  const [visible, setVisible] = useState(false);
+  const [current, setCurrent] = useState(null);
+  const [columnsFilter, setColumnsFilter] = useState({
+    budget: false,
+    dep: false,
+  });
+
+  const columns = useMemo(() => {
+    let arr = [
+      {
+        title: '名称',
+        dataIndex: 'name',
+        // render: (name, item) => <a onClick={() => handleClick(item)}>{name}</a>,
+      },
+      { title: '待审核工时', dataIndex: 'month_pending_audit_cnt', width: '10%' },
+      { title: '已审核工时', dataIndex: 'month_refuse_audit_cnt', width: '10%' },
+      { title: '已拒绝工时', dataIndex: 'month_pass_audit_cnt', width: '10%' },
+      { title: '总工时', dataIndex: 'total_workload', width: '10%' },
+    ];
+    if (columnsFilter.budget) {
+      arr.push({ title: '预算 ', dataIndex: 'budget', width: '10%' });
+    }
+    if (columnsFilter.dep) {
+      arr.splice(1, 0, { title: '所属部门', dataIndex: 'dep_name', width: '15%' });
+    }
+    return arr;
+  }, [columnsFilter]);
+
+  const filterRef = useRef({});
+  const cacheRef = useRef({});
+  const onChangePage = pagination => {
+    dispatch({
+      type: 'report/queryProjectReportNew',
+      payload: {
+        ...filterRef.current,
+        currentPage: pagination.current,
+      },
+    });
+  };
+  const handleSearch = () => {
+    const { time } = form.getFieldsValue();
+    filterRef.current.s_time = time[0] ? moment(time[0]).format('YYYY-MM-DD') : null;
+    filterRef.current.e_time = time[1] ? moment(time[1]).format('YYYY-MM-DD') : null;
+
+    dispatch({
+      type: 'report/queryProjectReportNew',
+      payload: {
+        ...filterRef.current,
+      },
+    });
+  };
+
+  const handleClick = item => {
+    setCurrent({
+      s_time: filterRef.current.s_time,
+      e_time: filterRef.current.e_time,
+      project_id: item.project_id,
+    });
+    setVisible(true);
+  };
+
+  const onExpand = (expanded, record) => {
+    // 加入缓存
+    cacheRef.current[record.key] = record;
+  };
+  const onExpandedRowsChange = expandedRows => {
+    let budget = false,
+      dep = false;
+    // 根据表格当前展开项决定是否显示预算和部门
+    const fn = data => {
+      data.forEach(record => {
+        // 判断是否展开
+        if(expandedRows.includes(record.key)) {
+          // 执行项目被展开则显示预算
+          if (record.cond.status == 0) {
+            budget = true;
+          }
+          // 子集含有部门则显示部门列
+          if (record.child[0]?.dep_name) {
+            dep = true;
+          }
+          if(record.child) fn(record.child)
+        }
+      })
+      
+    }
+    fn(projectNew)
+    
+    setColumnsFilter({ budget, dep });
+  };
+
+  const renderSearch = () => {
+    return (
+      <Form layout="inline" form={form}>
+        <Form.Item label="时间" name="time" initialValue={[moment().startOf('years'), moment()]}>
+          <RangePicker placeholder="选择时间" allowClear={false} />
+        </Form.Item>
+        <Form.Item>
+          <Button type="primary" loading={loading} onClick={handleSearch}>
+            查询
+          </Button>
+        </Form.Item>
+      </Form>
+    );
+  };
+  useEffect(() => {
+    handleSearch();
+  }, []);
+
+  return (
+    <div>
+      <div className={styles.topPart}>{renderSearch()}</div>
+      <Table
+        loading={loading}
+        style={{ marginTop: 20 }}
+        rowKey={`key`}
+        columns={columns}
+        dataSource={projectNew}
+        expandable={{
+          childrenColumnName: 'child',
+          onExpand,
+          onExpandedRowsChange,
+        }}
+        // pagination={projectNew.pagination}
+        onChange={onChangePage}
+      />
+      <ProjectTreeModal data={current} visible={visible} onCancel={() => setVisible(false)} />
+    </div>
+  );
+}
+
+function ProjectTreeModal(props) {
+  const { visible, data, onCancel } = props;
+  const [list, setList] = useState([]);
+  const [loading, setLoading] = useState(false);
+  const columns = [
+    { title: '项目名称', dataIndex: 'name' },
+    { title: '项目编号', dataIndex: 'name' },
+    { title: '分项工作代码', dataIndex: 'name' },
+    { title: '本月人日数', dataIndex: 'name' },
+    { title: '累计人日数', dataIndex: 'name' },
+    { title: '项目预算人日 ', dataIndex: 'name' },
+  ];
+
+  const queryList = async () => {
+    setLoading(true);
+    let res = await queryFinanceProjDetail(data);
+    if (res) {
+      setList(res.data);
+    }
+    setLoading(false);
+  };
+  useEffect(() => {
+    if (visible && data) {
+      queryList();
+    }
+    if (!visible) {
+      setList([]);
+    }
+  }, [visible]);
+
+  return (
+    <Modal
+      title="项目详情"
+      width="80%"
+      open={visible}
+      onCancel={onCancel}
+      footer={false}
+      destroyOnClose
+    >
+      <Table columns={columns} loading={loading} dataSource={list} rowKey="project_name"></Table>
+    </Modal>
+  );
+}
+
+export default connect(({ report, loading }) => ({
+  projectNew: report.projectNew,
+  loading: loading.models.report,
+}))(ProjectTree);

+ 12 - 2
src/pages/PurchaseAdmin/PurchaseList/Report/models/report.js

@@ -273,11 +273,21 @@ export default {
     },
 
     *queryProjectReportNew({ payload }, { call, put }) {
-      const res = yield call(queryProjectNew, payload);
+      const res = yield call(queryProjectReportNew, payload);
       if (!res) return;
+      const initData = (data, parentKey) => {
+        let currentKey = parentKey + (data.id || data.name);
+        data.key = currentKey;
+        if (data.child) {
+          data.child.forEach(item => {
+            initData(item, currentKey + '-');
+          });
+        }
+      };
+      initData(res.data, '');
       yield put({
         type: 'save',
-        payload: { projectNew: res.data },
+        payload: { projectNew: res.data.child || [] },
       });
     },
   },