Browse Source

提交财务核算成本统计

Lijy 5 months ago
parent
commit
0341185e6d

+ 2 - 0
fhKeeper/formulahousekeeper/timesheet/src/permissions.js

@@ -63,6 +63,7 @@ const StringUtil = {
         financialExport: false, // 薪资数据导出 // 
         financialShare: false, // 导出分摊数据 //
         setFinanceAuditor: false, //设置财务审核人
+        financialBonusDetails: false, // 奖金明细 // 
 
         // 项目报表服务 // 
         reportAllProject: false, // 全部项目报表 //
@@ -289,6 +290,7 @@ const StringUtil = {
         
         arr[i] == '设备信息管理' ? obj.equipmentInformationManagement = true : ''
         arr[i] == '设备成本管理' ? obj.equipmentCostManagement = true : ''
+        arr[i] == '奖金明细' ? obj.financialBonusDetails = true : ''
     }
     return obj
   }

File diff suppressed because it is too large
+ 77 - 1986
fhKeeper/formulahousekeeper/timesheet/src/views/project/finance.vue


+ 153 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/project/financeComponents/common.js

@@ -0,0 +1,153 @@
+export const FINANCIAL_DETAILS = "资金明细"; // 资金明细
+export const INCOME_DETAIL = "奖金明细"; // 奖金明细
+
+export const fundCascadingPanel = [
+  {
+    value: "季度奖",
+    label: "季度奖",
+    children: [
+      {
+        value: "第一季度奖",
+        label: "第一季度奖",
+      },
+      {
+        value: "第二季度奖",
+        label: "第二季度奖",
+      },
+      {
+        value: "第三季度奖",
+        label: "第三季度奖",
+      },
+      {
+        value: "第四季度奖",
+        label: "第四季度奖",
+      },
+    ],
+  },
+  {
+    value: "半年奖",
+    label: "半年奖",
+    children: [
+      {
+        value: "上半年奖",
+        label: "上半年奖",
+      },
+      {
+        value: "下半年奖",
+        label: "下半年奖",
+      },
+    ],
+  },
+  {
+    value: "全年奖",
+    label: "全年奖",
+  },
+];
+
+
+export function processingChartOptions(list = []) {
+  const xAxisData = list.map(item => item.projectName)
+  const item = { type: 'bar', barWidth: 30, emphasis: { focus: 'series' } }
+  const QUARTER = 'quarter'
+  const YEAR = 'year'
+  const series = [
+    {
+      name: '第一季度奖',
+      filed: 'firstSeasonBonus',
+      stack: QUARTER,
+      itemStyle: { color: '#c1adad' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '第二季度奖',
+      filed: 'secondSeasonBonus',
+      stack: QUARTER,
+      itemStyle: { color: '#d7ad9b' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '第三季度奖',
+      filed: 'thirdSeasonBonus',
+      stack: QUARTER,
+      itemStyle: { color: '#e5dcd1' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '第四季度奖',
+      filed: 'forthSeasonBonus',
+      stack: QUARTER,
+      itemStyle: { color: '#e6d7d7' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '上半年奖',
+      filed: 'soonerHalfYearBonus',
+      stack: YEAR,
+      itemStyle: { color: '#c4cdd0' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '下半年奖',
+      filed: 'latterHalfYearBonus',
+      stack: YEAR,
+      itemStyle: { color: '#dac890' },
+      data: [],
+      ...item,
+    },
+    {
+      name: '全年奖',
+      filed: 'yearBonus',
+      itemStyle: { color: '#afbfad' },
+      data: [],
+      ...item,
+    }
+  ]
+
+  series.forEach((seriesItem) => {
+    const field = seriesItem.filed;
+    seriesItem.data = list.map((listItem) => parseFloat(listItem[field])); 
+  });
+  
+  const options = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: { 
+      data: series.map(item => item.name)
+    },
+    grid: {
+      top: '40px',
+      left: '80px',
+      right: '80px',
+      bottom: '50px'
+    },
+    xAxis: [
+      {
+        type: 'category',
+        data: xAxisData,
+        axisLabel: {
+          formatter: function (value) {
+            return value.replace(/(.{7})/g, '\$1\n');
+          },
+          rotate: 20
+        }
+      }
+    ],
+    yAxis: [
+      {
+        type: 'value'
+      }
+    ],
+    series
+  }
+
+  return options
+}

+ 378 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/project/financeComponents/incomeDetail.vue

@@ -0,0 +1,378 @@
+<template>
+  <div class="incomeDetail w-full h-full">
+    <!-- 头部 -->
+    <div class="incomeDetail-header flex-center">
+      <div class="item-center">
+        <i class="fa fa-align-justify" @click.prevent="switchMenu" style="cursor: pointer" />
+        <div class="m-lr">选择月份</div>
+        <el-date-picker size="small" v-model="selectMonth" :editable="false" format="yyyy-MM" value-format="yyyy-MM"
+          @change="retrieveDataAgain" :clearable="false" type="month" placeholder="选择月份" />
+      </div>
+      <div class="item-center">
+        <el-link type="primary" :underline="false" class="ml" @click="bonusDataExport">数据导出</el-link>
+        <el-link type="primary" :underline="false" class="ml" @click="clickOnFundingData">数据上传</el-link>
+        <el-link type="primary" :underline="false" class="ml" href="./upload/员工奖金模板.xlsx"
+          :download="'员工奖金模板' + '.xlsx'">模板下载</el-link>
+      </div>
+    </div>
+    <!-- 表格 -->
+    <div class="incomeDetail-table">
+      <el-table :data="tableList" highlight-current-row v-loading="tableListLoading" ref="tableListRef"
+        @selection-change="deleteSel" :height="300" style="width: 100%;">
+        <el-table-column type="selection" width="80" fixed="left"></el-table-column>
+        <el-table-column prop="jobNumber" label="工号"></el-table-column>
+        <el-table-column prop="userName" label="姓名"></el-table-column>
+        <el-table-column prop="bonusType" label="奖金类型"></el-table-column>
+        <el-table-column prop="totalBonusValue" label="奖金金额(元)"></el-table-column>
+        <el-table-column prop="name" label="分摊月份">
+          <template slot-scope="scope">
+            {{ scope.row.startYM }} - {{ scope.row.endYM }}
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="padding: 10px 14px;">
+        <el-button size="mini" type="primary" :disabled="deleteSelList.length == 0"
+          @click="batchDeletion">批量删除</el-button>
+      </div>
+    </div>
+    <!-- 图表 -->
+    <div class="incomeDetail-echart flex-1">
+      <!-- echarts 图 -->
+      <div class="incomeDetail-echart-echart flex-1" v-loading="echartsLoading">
+        <div id="clearfix" class="echarts-com w-full h-full">
+          <div id="containerEcharts" class="w-full h-full"></div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 资金数据导入 -->
+    <el-dialog title="资金数据导入" v-if="fundsImportIntoVisable" :visible.sync="fundsImportIntoVisable"
+      customClass="customWidth" width="500px">
+      <div class="importOfFundDataClass">
+        <el-form ref="form" label-width="100px">
+          <el-form-item label="分摊月份">
+            <el-date-picker v-model="sharingMonths" type="monthrange" value-format="yyyy-MM" range-separator="至"
+              start-placeholder="开始月份" end-placeholder="结束月份" :size="'small'" :picker-options="pickerOptions"
+              @change="sharingMonthsChange">
+            </el-date-picker>
+          </el-form-item>
+          <el-form-item label="奖金类型">
+            <el-cascader v-model="typeOfFundsValue" :options="typeOfFundsArray" :props="{ expandTrigger: 'hover' }"
+              :size="'small'" :show-all-levels="false"></el-cascader>
+          </el-form-item>
+          <el-form-item label="导入模板">
+            <el-upload class="upload-demo" :limit="1" :http-request="fundUpload" :size="'small'">
+              <el-button size="small" type="primary" :size="'small'">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+        </el-form>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFunds()" :loading="dataImportLoading">{{ $t('btn.determine')
+          }}</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 数据导出 -->
+    <el-dialog title="数据导出" v-if="dataExportVisable" :visible.sync="dataExportVisable" customClass="customWidth"
+      width="500px">
+      <div class="importOfFundDataClass">
+        <el-form ref="form" label-width="100px">
+          <el-form-item label="导出年份">
+            <el-date-picker v-model="selectYear" type="year" value-format="yyyy" :size="'small'">
+            </el-date-picker>
+          </el-form-item>
+        </el-form>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitExport()" :loading="dataExportLoading">导出</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import dayjs from 'dayjs';
+import { fundCascadingPanel, processingChartOptions } from './common'
+export default {
+  components: {},
+  data() {
+    return {
+      user: JSON.parse(sessionStorage.getItem("user")),
+      permissions: JSON.parse(sessionStorage.getItem("permissions")),
+      selectMonth: dayjs().format('YYYY-MM'),
+      selectYear: dayjs().format('YYYY'),
+      typeOfFundsValue: '',
+      deleteSelList: [],
+      missingFinanceUserList: [],
+      sharingMonths: [],
+      sharingMonthsTwo: [],
+      tableList: [],
+      tableListLoading: false,
+      echartsLoading: false,
+      dataExportLoading: false,
+      dataImportLoading: false,
+      showMissingDialog: false,
+      fundsImportIntoVisable: false,
+      dataExportVisable: false,
+      typeOfFundsArray: fundCascadingPanel,
+      pickerOptions: {
+        disabledDate(time) {
+          return time.getFullYear() > dayjs().format('YYYY');
+        }
+      },
+      importTemplateData: null,
+      echartsData: [],
+      myChart: null,
+    };
+  },
+  mounted() {
+    this.retrieveDataAgain()
+    this.scrollFunction()
+  },
+  methods: {
+    retrieveDataAgain() {
+      this.getEchartsData()
+      this.getTableList()
+    },
+    submitExport() {
+      this.http.downloadFile('/contractBonusDetail/exportContractBonus', {
+        year: this.selectYear
+      }, '奖金项目分摊.xlsx', err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      })
+    },
+    bonusDataExport() {
+      this.selectYear = dayjs().format('YYYY')
+      this.dataExportVisable = true
+    },
+    batchDeletion() {
+      this.$confirm(this.$t(this.$t('doyouwanttodeleteit')), this.$t('other.prompts'), {
+        confirmButtonText: this.$t('btn.determine'),
+        cancelButtonText: this.$t('btn.cancel'),
+        type: 'warning'
+      }).then(() => {
+        const ids = this.deleteSelList.map(item => item.id).join(',')
+        this.postData(`/contractBonusSummary/deleteBonusSummary`, { ids }).then((res) => {
+          this.$message.success('操作成功')
+          this.retrieveDataAgain()
+        })
+      })
+    },
+    sharingMonthsChange(val) {
+      const startData = val[0]
+      const endData = val[1]
+      const dateDetermination = dayjs(startData).isSame(dayjs(endData), 'year');
+      if (!dateDetermination) {
+        this.sharingMonths = JSON.parse(JSON.stringify(this.sharingMonthsTwo))
+        this.$message.warning('请选择同一年的月份')
+        return
+      }
+      this.sharingMonthsTwo = JSON.parse(JSON.stringify(val))
+    },
+    submitFunds() {
+      if (!this.typeOfFundsValue || this.typeOfFundsValue.length == 0) {
+        this.$message.warning('请选择奖金类型')
+        return
+      }
+      if (!this.importTemplateData) {
+        this.$message.warning('请选择导入模板')
+        return
+      }
+      let formData = new FormData();
+      formData.append("startYM", this.sharingMonths[0]);
+      formData.append("endYM", this.sharingMonths[1]);
+      formData.append("bonusType", this.typeOfFundsValue[this.typeOfFundsValue.length - 1]);
+      formData.append("file", this.importTemplateData);
+      this.dataImportLoading = true
+      this.http.uploadFile('/contractBonusDetail/transTemplateData', formData,
+        res => {
+          this.dataImportLoading = false
+          if (res.code == 'ok') {
+            this.fundsImportIntoVisable = false
+            this.$message({
+              message: '操作成功',
+              type: 'success'
+            })
+            this.retrieveDataAgain()
+          } else {
+            this.$message({
+              message: res.msg,
+              type: 'error'
+            })
+          }
+        }, error => {
+          this.dataImportLoading = false
+          this.$message({
+            message: error,
+            type: 'error'
+          })
+        })
+    },
+    fundUpload(file) {
+      this.importTemplateData = file.file
+    },
+    clickOnFundingData() {
+      this.typeOfFundsValue = ''
+      this.sharingMonths = [dayjs().format('YYYY-MM'), dayjs().format('YYYY-MM')]
+      this.importTemplateData = null
+      this.fundsImportIntoVisable = true
+    },
+    getTableList() {
+      this.tableListLoading = true
+      this.postData(`/contractBonusSummary/getBonusSummary`, { ym: this.selectMonth }).then((res) => {
+        this.tableList = res.data || []
+      }).finally(() => {
+        this.tableListLoading = false
+      })
+    },
+    getEchartsData() {
+      this.echartsLoading = true
+      this.postData(`/contractBonusDetail/getContractBonus`, { year: dayjs(this.selectMonth).format('YYYY') }).then(res => {
+        // this.echartsData = res.data || []
+        this.instantiateChart(res.data || [])
+      }).finally(() => {
+        this.echartsLoading = false
+      })
+    },
+    instantiateChart(list = []) {
+      const listNew = [...list]
+      if (this.myChart) {
+        this.myChart.dispose();
+        this.myChart = null
+      }
+      this.myChart = echarts.init(document.getElementById("containerEcharts"));
+      const option = processingChartOptions(listNew)
+      // 设置图表
+      this.myChart.resize({
+        // width: this.widthHtval
+      })
+
+      this.myChart.setOption(option, { notMerge: true });
+    },
+    deleteSel(sel) {
+      this.deleteSelList = sel
+    },
+    // 左右滚动
+    scrollFunction() {
+      this.domObj = document.getElementById('clearfix') // 通过id获取要设置的div
+      if (this.domObj.attachEvent) { // IE
+        this.domObj.attachEvent('onmousewheel', this.mouseScroll)
+      } else if (this.domObj.addEventListener) {
+        this.domObj.addEventListener('DOMMouseScroll', this.mouseScroll, false)
+      }
+      this.domObj.onmousewheel = this.domObj.onmousewheel = this.mouseScroll
+    },
+    mouseScroll(event) { // google 浏览器下
+      let detail = event.wheelDelta || event.detail
+      let moveForwardStep = -1
+      let moveBackStep = 1
+      let step = 0
+      step = detail > 0 ? moveForwardStep * 100 : moveBackStep * 100
+      event.preventDefault() // 阻止浏览器默认事件
+      this.domObj.scrollLeft = this.domObj.scrollLeft + step
+    },
+    switchMenu() {
+      this.$emit("switchMenu");
+    },
+    destroyChart() {
+      if (this.myChart) {
+        this.myChart.dispose();
+        this.myChart = null;
+      }
+    },
+    async postData(urls, param) {
+      return new Promise((resolve, reject) => {
+        this.http.post(urls, { ...param },
+          res => {
+            if (res.code == 'ok') {
+              resolve(res)
+            } else {
+              this.$message({
+                message: res.msg,
+                type: 'error'
+              })
+              reject(res)
+            }
+            resolve(res)
+          },
+          error => {
+            this.$message({
+              message: error,
+              type: "error"
+            });
+            reject(error)
+          }
+        )
+      });
+    },
+  },
+  beforeDestroy() {
+    this.destroyChart();
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.incomeDetail {
+  color: #606266;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+
+  .incomeDetail-header {
+    height: 40px;
+    background: #f2f2f2;
+    padding: 10px;
+
+    .incomeDetail-header-const {
+      width: 440px;
+    }
+  }
+
+  .incomeDetail-echart {
+    display: flex;
+    flex-direction: column;
+    padding: 8px;
+
+    .echarts-com {
+      overflow-x: auto;
+      position: relative;
+    }
+  }
+}
+
+.flex-1 {
+  flex: 1;
+}
+
+.ml {
+  margin-left: 14px;
+}
+
+.m-lr {
+  margin: 0 12px;
+}
+
+.item-center {
+  display: flex;
+  align-items: center;
+}
+
+.flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.w-full {
+  width: 100%;
+}
+
+.h-full {
+  height: 100%;
+}
+</style>

File diff suppressed because it is too large
+ 2175 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/project/financeComponents/salaryDetails.vue