소스 검색

Merge remote-tracking branch 'origin/master'

ysm 11 달 전
부모
커밋
3831a5a3e2
48개의 변경된 파일1091개의 추가작업 그리고 157개의 파일을 삭제
  1. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm/.gitignore
  2. 4 4
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/relatedTasks.vue
  3. 39 5
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/relatedProducts/relatedProducts.vue
  4. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/information.vue
  5. 5 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/products.vue
  6. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/relatedTasks.vue
  7. 34 12
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue
  8. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts
  9. 4 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/component/relatedBusiness.vue
  10. 5 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/relatedBusiness.vue
  11. 58 17
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue
  12. 3 3
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/operationRecord.vue
  13. 6 6
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue
  14. 62 12
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/customInstructions.ts
  15. 28 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts
  16. 2 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/Clue.java
  17. 6 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java
  18. 7 4
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/ClueServiceImpl.java
  19. 61 17
      fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/ClueMapper.xml
  20. 6 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  21. 2 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskController.java
  22. 18 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/WeiXinCorpController.java
  23. 4 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/WxCorpInfoService.java
  24. 51 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  25. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/task/TimingTask.java
  26. 19 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/controller/WxCorpInfoController.java
  27. 14 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/ProdProcedureTeam.java
  28. 6 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/User.java
  29. 5 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/WxCorpInfoService.java
  30. 2 2
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/PlanServiceImpl.java
  31. 117 7
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  32. 76 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  33. 2 1
      fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ProdProcedureTeamMapper.xml
  34. 0 4
      fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ReportMapper.xml
  35. 2 2
      fhKeeper/formulahousekeeper/plugIn/form-design-master/.vscode/settings.json
  36. 26 0
      fhKeeper/formulahousekeeper/plugIn/form-design-master/src/components.d.ts
  37. 1 0
      fhKeeper/formulahousekeeper/plugIn/form-design-master/src/design/WidgetFormItem.vue
  38. 11 0
      fhKeeper/formulahousekeeper/plugIn/form-design-master/src/generate/GenerateForm.vue
  39. 50 3
      fhKeeper/formulahousekeeper/plugIn/form-design-master/src/generate/GenerateFormItem.vue
  40. 1 0
      fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/generate/GenerateForm.vue.d.ts
  41. 6 2
      fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/generate/GenerateFormItem.vue.d.ts
  42. 1 1
      fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.css
  43. 69 16
      fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.es.js
  44. 1 1
      fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.es.js.map
  45. 9 5
      fhKeeper/formulahousekeeper/timesheet-workshop/src/views/statistic/index.vue
  46. 235 0
      fhKeeper/formulahousekeeper/timesheet/src/components/cascadeSelection.vue
  47. 10 10
      fhKeeper/formulahousekeeper/timesheet/src/components/select.vue
  48. 15 13
      fhKeeper/formulahousekeeper/timesheet/src/views/project/list.vue

+ 3 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/.gitignore

@@ -22,3 +22,6 @@ dist-ssr
 *.njsproj
 *.sln
 *.sw?
+
+!plugIn/form-design-master/update/node_modules/
+!plugIn/form-design-master/update/dist/

+ 4 - 4
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/relatedTasks.vue

@@ -3,7 +3,7 @@
         <div class="flex justify-between">
             <div class="title">相关任务</div>
             <div>
-                <el-button type="primary" @click="newTask()">新建任务</el-button>
+                <el-button type="primary" @click="newTask()" v-permission="['tasksAdd']">新建任务</el-button>
             </div>
         </div>
         <div class="flex-1 overflow-auto pt-3">
@@ -15,11 +15,11 @@
                         }}</el-button>
                     </template>
                 </el-table-column>
-                <el-table-column prop="priorityStr" label="优先级" width="130" />
+                <el-table-column prop="priorityStr" sortable label="优先级" width="130" />
                 <el-table-column prop="statusStr" label="状态" width="130" />
                 <el-table-column prop="executorNamesStr" label="执行人" width="130" />
-                <el-table-column prop="startTimes" label="开始时间" width="130" />
-                <el-table-column prop="endTimes" label="截至时间" width="130" />
+                <el-table-column prop="startTimes" sortable label="开始时间" width="130" />
+                <el-table-column prop="endTimes" sortable label="截至时间" width="130" />
             </el-table>
         </div>
     </div>

+ 39 - 5
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/relatedProducts/relatedProducts.vue

@@ -1,5 +1,5 @@
 <template>
-    <div>
+    <div class="pt-2">
         <el-table ref="productTableRef" :data="productTable" border :row-class-name="tableRowClassName"
             @row-click="tableRowItem" :style="{ width: '100%', height: heightClass }">
             <el-table-column label="序号" width="60" align="center">
@@ -55,11 +55,22 @@
                 </template>
             </el-table-column>
         </el-table>
+
+        <div class="flex w-full justify-between pt-2 pb-1">
+            <div>整单折扣率(%)</div>
+            <div class="flex">
+                <div>已选中产品:<span class="text-[red] pr-2">{{ selectedQuantity }}</span>个</div>
+                <div class="pl-4">
+                    总金额:<span class="pr-1">{{ totalAmount }}</span> 元
+                </div>
+            </div>
+        </div>
     </div>
 </template>
   
 <script lang="ts" setup>
-import { ref, reactive, onMounted, inject, watchEffect } from "vue";
+import { ElNotification } from "element-plus";
+import { ref, reactive, onMounted, inject, watchEffect, computed } from "vue";
 
 const props = defineProps<{
     productTableList: any,
@@ -116,14 +127,37 @@ function returnData() {
     let jsonstr = JSON.stringify(productTable.value)
     let json = '[{"index":0}]'
     if (jsonstr == json) {
-        return false
+        return []
+    }
+
+    const list = productTable.value.filter((item: any) => item.productId)
+    const incompleteProduct = list.find((item: any) => !item.sellingPrice || !item.quantity || !item.discount);
+    if (incompleteProduct) {
+        ElNotification.closeAll();
+        ElNotification({
+            title: '提示',
+            message: `相关产品【${incompleteProduct.productName}】请填写完整`,
+            type: 'warning',
+        });
+        return false;
     }
-    productTable.value.forEach((item: any) => {
+
+    list.forEach((item: any) => {
         delete item.index
     });
-    return productTable.value
+    return list || []
 }
 
+const selectedQuantity = computed(() => {
+    return productTable.value.filter((item: any) => item.productId).length
+})
+
+const totalAmount = computed(() => {
+    return productTable.value.filter((item: any) => item.productId).reduce((pre: number, cur: any) => {
+        return pre + (cur.totalPrice || 0)
+    }, 0)
+})
+
 watchEffect(() => {
     const { productTableList, height, productTableListValue = [] } = props
     console.log(productTableListValue, '<==== 数据')

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/information.vue

@@ -32,7 +32,7 @@
             </div>
             <div class="formItem flex pt-5 pb-1">
                 <div class="w-22 text-right text-gray-500">负责人:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.creatorName }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.inchargerName }}</div>
             </div>
             <div class="formItem flex pt-5 pb-1">
                 <div class="w-22 text-right text-gray-500">预计成交日期:</div>

+ 5 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/products.vue

@@ -58,6 +58,7 @@ import { post } from '@/utils/request';
 import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
 import { UPDATEINSET } from '../api';
 import { all } from 'axios';
+import { judgmentaAmounteEqual } from '@/utils/tools';
 
 const emits = defineEmits(['refreshData']);
 const props = defineProps<{
@@ -78,10 +79,13 @@ const allLoading = reactive({
 
 function editProduct() {
     let productTableListData = relatedProductsRef?.value?.returnData()
+    const { id, name, customerId, contactsId, amountOfMoney, expectedTransactionDate, stageId, inchargerId, remark } = information.value
+    if(!productTableListData || judgmentaAmounteEqual({ amountOfMoney }, productTableListData)) {
+      return
+    }
     productTableListData.forEach((item: any) => {
         delete item.id
     })
-    const { id, name, customerId, contactsId, amountOfMoney, expectedTransactionDate, stageId, inchargerId, remark } = information.value
     const formData = { id, name, customerId, contactsId, amountOfMoney, expectedTransactionDate, stageId, inchargerId, remark }
     allLoading.editProductLoading = true
     post(UPDATEINSET, { ...formData, businessItemProductList: JSON.stringify(productTableListData) }).then((_res) => {

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/relatedTasks.vue

@@ -3,7 +3,7 @@
         <div class="flex justify-between">
             <div class="title">相关任务</div>
             <div>
-                <el-button type="primary">新建任务</el-button>
+                <el-button type="primary" v-permission="['tasksAdd']">新建任务</el-button>
             </div>
         </div>
         <div class="flex-1 overflow-auto pt-3">

+ 34 - 12
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue

@@ -29,8 +29,8 @@
               </el-select>
             </el-form-item>
             <el-form-item label="创建时间">
-              <el-date-picker v-model="businessOpportunityForm.startTime" type="date" placeholder="请选择" :clearable="false"
-                format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+              <el-date-picker v-model="businessOpportunityForm.startTime" type="date" placeholder="请选择"
+                :clearable="false" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
             </el-form-item>
             <el-form-item label="">
               <el-date-picker v-model="businessOpportunityForm.endTime" type="date" placeholder="请选择" :clearable="false"
@@ -47,14 +47,17 @@
     <div class="flex-1 p-5 overflow-auto">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
         <div class="flex justify-end pb-3">
-          <el-button v-permission="['businessAddAnEdit']" type="primary" @click="editNewBusiness(false)">新建商机</el-button>
+          <el-button v-permission="['businessAddAnEdit']" type="primary"
+            @click="editNewBusiness(false)">新建商机</el-button>
           <el-button type="primary" @click="showVisible('batchTransferVisible')"
             :disabled="batchTableData.length <= 0">批量转移</el-button>
           <el-button type="primary" @click="batchDeteleItem()" :disabled="batchTableData.length <= 0">批量删除</el-button>
           <el-button type="primary" @click="showVisible('stageSetVisible')">阶段设置</el-button>
           <el-button type="primary" @click="showVisible('deteleBusinessVisible')">回收站</el-button>
-          <el-button v-permission="['businessImport']" type="primary" @click="showVisible('importVisible')">导入</el-button>
-          <el-button v-permission="['businessExport']" type="primary" @click="exportBusinessTableList()" :loading="allLoading.exoprtLoading">导出</el-button>
+          <el-button v-permission="['businessImport']" type="primary"
+            @click="showVisible('importVisible')">导入</el-button>
+          <el-button v-permission="['businessExport']" type="primary" @click="exportBusinessTableList()"
+            :loading="allLoading.exoprtLoading">导出</el-button>
         </div>
         <div class="flex-1 w-full overflow-hidden">
           <el-table ref="businessTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
@@ -70,16 +73,19 @@
             </el-table-column>
             <el-table-column label="操作" fixed="right" width="200">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click="editNewBusiness(scope.row)" v-permission="['businessAddAnEdit']">编辑</el-button>
-                <el-button link type="primary" size="large" @click="newTask(scope.row)" v-permission="['tasksAdd']">新建任务</el-button>
-                <el-button link type="danger" size="large"
-                  @click="businessDeteleItem(scope.row.id, scope.row.name)" v-permission="['businessAddAnEdit']">删除</el-button>
+                <el-button link type="primary" size="large" @click="editNewBusiness(scope.row)"
+                  v-permission="['businessAddAnEdit']">编辑</el-button>
+                <el-button link type="primary" size="large" @click="newTask(scope.row)"
+                  v-permission="['tasksAdd']">新建任务</el-button>
+                <el-button link type="danger" size="large" @click="businessDeteleItem(scope.row.id, scope.row.name)"
+                  v-permission="['businessAddAnEdit']">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
         </div>
         <div class="flex justify-end pt-3">
-          <el-pagination layout="total, prev, pager, next, sizes" :total="businessTotalTable"
+          <el-pagination layout="total, prev, pager, next, sizes" :page-size="businessOpportunityForm.pageFrom"
+            @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
             :hide-on-single-page="true" />
         </div>
       </div>
@@ -173,7 +179,7 @@ import { useRouter, useRoute } from "vue-router";
 import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET, BUSINESSDETELE, BATCHTRANSFER, MODURL, tableColumn, BUSIESS_GETSATE, URL_IMPOERBUSINESS, BUSIESS_INFO, URL_EXPORTBUSINESS } from './api'
 import { GETTABLELIST } from '@/pages/product/api'
 import { post, get, uploadFile } from "@/utils/request";
-import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate, confirmAction, downloadTemplate, downloadFile } from '@/utils/tools'
+import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate, confirmAction, downloadTemplate, downloadFile, judgmentaAmounteEqual } from '@/utils/tools'
 import { createTask } from '@/components/TaskModal/taskFunction'
 import { formatDateTime } from '@/utils/times'
 import { GenerateForm } from '@zmjs/form-design';
@@ -246,7 +252,12 @@ const productTableListValue = ref([])
 
 function editBusiness(visibles: boolean) {
   businessTemplateRef.value?.getData().then((res: any) => {
-    let productTableListData = relatedProductsRef?.value?.returnData() || []
+    let productTableListData = relatedProductsRef?.value?.returnData()
+    console.log(!productTableListData, judgmentaAmounteEqual({...businessTemplateValue.value, ...res}, productTableListData))
+    if(!productTableListData || judgmentaAmounteEqual({...businessTemplateValue.value, ...res}, productTableListData)) {
+      return
+    }
+
     productTableListData.forEach((item: any) => {
       delete item.id
     })
@@ -393,6 +404,17 @@ function editProduct(row: any) {
   })
 }
 
+function handleSizeChange(val: number) {
+  businessOpportunityForm.pageIndex = 1
+  businessOpportunityForm.pageFrom = val
+  getBusinessTableList()
+}
+
+function handleCurrentChange(val: number) {
+  businessOpportunityForm.pageIndex = val
+  getBusinessTableList()
+}
+
 function showVisible(type: keyof typeof allVisible) { // 显示弹窗
   allVisible[type] = true
 }

+ 2 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts

@@ -8,8 +8,8 @@ interface businessOpportunityFormType {
   inchargerId: string | number;
   startTime: string | number;
   endTime: string | number;
-  pageIndex: string | number;
-  pageFrom: string | number;
+  pageIndex: number;
+  pageFrom: number;
 }
 
 interface fixedDataInterface {

+ 4 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/component/relatedBusiness.vue

@@ -57,6 +57,7 @@ import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
 import { formatDateTime } from '@/utils/times';
 import { GETGENERATEFOEM, UPDATEINSET } from '@/pages/business/api';
 import { GETTABLELIST } from '@/pages/product/api';
+import { judgmentaAmounteEqual } from '@/utils/tools';
 
 const router = useRouter()
 const globalPopup = inject<GlobalPopup>('globalPopup')
@@ -95,6 +96,9 @@ function toBusDetal(row: any) {
 function editBusiness() {
     businessTemplateRef.value?.getData().then((res: any) => {
         let productTableListData = relatedProductsRef?.value?.returnData()
+        if(!productTableListData || judgmentaAmounteEqual({ ...businessTemplateValue.value, ...res }, productTableListData)) {
+            return
+        }
         let newForm = {
             ...res,
             expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',

+ 5 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/relatedBusiness.vue

@@ -54,7 +54,7 @@ import { GenerateForm } from '@zmjs/form-design';
 import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
 import { GETTABLELIST } from '@/pages/product/api';
 import { GETGENERATEFOEM, UPDATEINSET } from '@/pages/business/api';
-import { setTemplateDataDisable } from '@/utils/tools';
+import { judgmentaAmounteEqual, setTemplateDataDisable } from '@/utils/tools';
 import { formatDateTime } from '@/utils/times';
 
 const emits = defineEmits(['refreshData']);
@@ -86,7 +86,10 @@ const allLoading = reactive({
 
 function editBusiness(visibles: boolean) {
     businessTemplateRef.value?.getData().then((res: any) => {
-        let productTableListData = relatedProductsRef?.value?.returnData() || []
+        let productTableListData = relatedProductsRef?.value?.returnData()
+        if(!productTableListData || judgmentaAmounteEqual({ ...businessTemplateValue.value, ...res }, productTableListData)) {
+            return
+        }
         productTableListData.forEach((item: any) => {
             delete item.id
         })

+ 58 - 17
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue

@@ -3,54 +3,63 @@
         <div class="flex justify-between">
             <div class="title">基本信息</div>
             <div>
-                <el-button type="primary" @click="showVisible('newBusinessisible')">转为商机</el-button>
+                <el-button type="primary" v-permission="['threadEdit', userInfo.id == information.inchargerId]"
+                    @click="showVisible('newBusinessisible')">转为商机</el-button>
                 <el-button type="primary" @click="claimClues()" v-if="!information.inchargerName">认领</el-button>
                 <el-button type="primary" @click="showVisible('clueDialogVisible')" v-else>转移</el-button>
-                <el-button type="primary" @click="editClue(information)">编辑</el-button>
+                <el-button type="primary" @click="editClue(information)" v-permission="['tasksAdd']">编辑</el-button>
             </div>
         </div>
         <div class="form flex flex-wrap justify-between">
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">线索名称:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.clueName }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.clueName }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">线索来源:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.clueSourceValue }}
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.clueSourceValue }}
                 </div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">电话号码:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.phone }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.phone }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">邮箱:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.email }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.email }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">客户行业:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
                     information.customerIndustryValue
-                }}</div>
+                    }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">客户级别:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.customerLevelValue
-                }}
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.customerLevelValue
+                    }}
                 </div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">客户地址:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.address }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.address }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">负责人:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.inchargerName }}
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.inchargerName }}
                 </div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">创建人:</div>
-                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1">{{ information.createName }}</div>
+                <div class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap ml-1" v-ellipsis-tooltip>{{
+                    information.createName }}</div>
             </div>
             <div class="formItem flex pt-3 pb-1">
                 <div class="w-20 text-right text-gray-500">创建时间:</div>
@@ -59,7 +68,7 @@
             </div>
             <div class="formItem flex pt-3 pb-1" style="width: 100%;">
                 <div class="w-20 text-right text-gray-500">备注:</div>
-                <div class="flex-1 ml-1 text ">
+                <div class="flex-1 ml-1 text" v-ellipsis-tooltip>
                     {{ information.remark }}
                 </div>
             </div>
@@ -89,7 +98,8 @@
                 <div class="flex justify-between items-center border-b pb-3 dialog-header">
                     <h4 :id="titleId">{{ allText.clueText }}</h4>
                     <div>
-                        <el-button type="primary" :loading="allLoading.clueLoading" @click="transferClues()">转移</el-button>
+                        <el-button type="primary" :loading="allLoading.clueLoading"
+                            @click="transferClues()">转移</el-button>
                         <el-button @click="dialogVisible.clueDialogVisible = false">取消</el-button>
                     </div>
                 </div>
@@ -120,7 +130,7 @@
             <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
                 <GenerateForm ref="generateFormDataRef" :data="generateFormData" :value="generateFormVal" />
                 <div>相关产品</div>
-                <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList" />
+                <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList" :productTableListValue="{}" />
             </div>
         </el-dialog>
     </div>
@@ -128,7 +138,7 @@
 <script lang="ts" setup>
 import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
 import { GenerateForm } from '@zmjs/form-design';
-import { formatDate, confirmAction, backPath } from '@/utils/tools'
+import { formatDate, confirmAction, backPath, judgmentaAmounteEqual } from '@/utils/tools'
 import { GETTEMPLATE, UNDATEFORM, GETPERSONNEL, UNDATECLAIM, GETTEMPLATETWO } from '../../constant'
 import { get, post } from '@/utils/request'
 import { useStore } from '@/store/index'
@@ -137,6 +147,7 @@ import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
 import { all } from 'axios';
 import { formatDateTime } from '@/utils/times';
 import { UPDATEINSET } from '@/pages/business/api';
+import { GETTABLELIST } from '@/pages/product/api';
 
 interface personnelInterface {
     id: string | number,
@@ -191,6 +202,9 @@ const transferOptions = ref<personnelInterface[]>([]) // 转移人员列表
 function transferBusiness() {
     generateFormDataRef.value?.getData().then((res: any) => {
         let productTableListData = relatedProductsRef?.value?.returnData()
+        if(!productTableListData || judgmentaAmounteEqual({ ...res }, productTableListData)) {
+            return
+        }
         let newForm = {
             ...res,
             expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
@@ -304,6 +318,32 @@ async function getSystemField() {
     })
 }
 
+function getProductTableList() {
+    post(GETTABLELIST, { pageIndex: -1, pageSize: -1 }).then((res) => {
+        if (res.code == 'ok') {
+            const { record, total } = res.data
+            productTableList.value = record.map((item: any) => {
+                const { id, productName, productCode, unit, unitName, typeName, type, price, inventory } = item
+                return {
+                    id,
+                    productId: id,
+                    productName,
+                    productCode,
+                    unit,
+                    unitName,
+                    price,
+                    type,
+                    typeName,
+                    inventory,
+                    quantity: '',
+                    discount: '',
+                    totalPrice: ''
+                }
+            })
+        }
+    })
+}
+
 watchEffect(() => {
     receiveAssignment(props)
 });
@@ -317,6 +357,7 @@ onMounted(async () => {
     let newData = JSON.parse(data.data[0].config)
     generateFormData.value = newData
     getSystemField()
+    getProductTableList()
 });
 </script>
 <style scoped lang="scss">

+ 3 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/operationRecord.vue

@@ -5,7 +5,7 @@
         </div>
         <div class="flex-1 overflow-auto pt-5">
             <el-table :data="operationRecordtable" border style="width: 100%;height: 100%;">
-                <el-table-column prop="creatTime" label="操作时间" width="140" />
+                <el-table-column prop="creatTime" label="操作时间" width="150" />
                 <el-table-column prop="userName" label="操作人" width="120" />
                 <el-table-column prop="name" label="操作内容" />
             </el-table>
@@ -13,7 +13,7 @@
     </div>
 </template>
 <script lang="ts" setup>
-import { formatDate } from '@/utils/tools';
+import { formatDateMinutes } from '@/utils/times';
 import { ref, onMounted, watchEffect } from 'vue'
 
 const props = defineProps<{
@@ -24,7 +24,7 @@ const operationRecordtable = ref([])
 
 watchEffect(() => {
     props.data.forEach((item: any) => {
-        item.creatTime = formatDate(new Date(item.creatTime))
+        item.creatTime = formatDateMinutes(new Date(item.creatTime))
     })
     operationRecordtable.value = props.data
 });

+ 6 - 6
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -52,12 +52,12 @@
     <div class="flex-1 p-5 overflow-auto">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
         <div class="flex justify-end pb-3">
-          <el-button type="primary" @click="editClue(false)">新建线索</el-button>
+          <el-button type="primary" v-permission="['threadAdd']" @click="editClue(false)">新建线索</el-button>
           <el-button type="primary" :disabled="batchTableData.length <= 0" @click="batchTransfer()">批量转移</el-button>
           <el-button type="primary" :disabled="batchTableData.length <= 0" @click="batchDeletes()">批量删除</el-button>
           <el-button type="primary" @click="showDeteleClue(true)">回收站</el-button>
-          <el-button type="primary" @click="dialogVisible.importVisible = true">导入</el-button>
-          <el-button type="primary" @click="exportTheadTableList()" :loading="allLoading.exoprtLoading">导出</el-button>
+          <el-button type="primary" v-permission="['threadImport']" @click="dialogVisible.importVisible = true">导入</el-button>
+          <el-button type="primary" v-permission="['threadExport']" @click="exportTheadTableList()" :loading="allLoading.exoprtLoading">导出</el-button>
         </div>
         <div class="flex-1 w-full overflow-hidden">
           <el-table :show-overflow-tooltip="tableShowOverflowTooltip" ref="clueTableRef" :data="clueTable" border v-loading="allLoading.clueTableLading"
@@ -78,11 +78,11 @@
             <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
             <el-table-column label="操作" fixed="right" width="200">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click="editClue(scope.row)">编辑</el-button>
+                <el-button link type="primary" size="large" @click="editClue(scope.row)" v-permission="['threadEdit']">编辑</el-button>
                 <!-- <el-button link type="primary" size="large"
                   @click="dialogVisible.taskModalVisible = true">新建任务</el-button> -->
-                <el-button link type="primary" size="large" @click="newTask(scope.row)">新建任务</el-button>
-                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.row)">删除</el-button>
+                <el-button link type="primary" size="large" @click="newTask(scope.row)" v-permission="['tasksAdd']">新建任务</el-button>
+                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.row)" v-permission="['threadEdit']">删除</el-button>
               </template>
             </el-table-column>
           </el-table>

+ 62 - 12
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/customInstructions.ts

@@ -1,22 +1,30 @@
-import { Directive, ObjectDirective } from 'vue';
+import { Directive, ObjectDirective, DirectiveBinding, render, createVNode, h } from 'vue';
+import { ElTooltip } from 'element-plus';
+import 'element-plus/theme-chalk/el-tooltip.css';
 
 // 权限控制
-const PermissionDirective: Directive = {
-    mounted(el: HTMLElement, binding: { value: string[] }, vnode: any) {
+const PermissionDirective: Directive = { // 数组, 权限 code 和 布尔值,
+    // mounted(el: HTMLElement, binding: { value: (string | boolean)[] }, vnode: any) {
+    updated(el: HTMLElement, binding: { value: (string | boolean)[] }, vnode: any) {
         const routePath: string = vnode.ctx.appContext.config.globalProperties.$route.path;
         const userInfo: { userInfo: { functionList: { code: string }[] } } | null = JSON.parse(localStorage.getItem('storeInfo') || '');
         const authorityCodes: string[] = (userInfo?.userInfo?.functionList || []).map(({ code }) => code);
-        const permissions: string[] = binding.value;
+        const permissions: (string | boolean)[] = binding.value;
+        const hasPermission: any[] = (binding.value || []).filter(item => typeof item !== 'boolean');
 
         if (!Array.isArray(permissions)) {
             console.error('权限必须以数组形式提供');
             return;
         }
 
-        if (!permissions.every(permission => authorityCodes.includes(permission))) {
+        if (permissions.some((element): element is boolean => element === true)) {
+            return;
+        }
+
+        if (!hasPermission.every(permission => authorityCodes.includes(permission))) {
             el.parentNode && el.parentNode.removeChild(el);
         }
-    }
+    },
 };
 
 // input 字符串数字
@@ -29,7 +37,50 @@ const PositiveIntegerDirective: ObjectDirective = {
     },
 };
 
-function handleInput(event: Event) {
+// 检测文字是否被省略并显示工具提示
+const EllipsisTooltipDirective: ObjectDirective = {
+    mounted(el: HTMLElement) {
+        const tooltipVNode = createVNode(ElTooltip, {
+            content: el.textContent,
+            placement: 'top',
+            effect: 'dark'
+        }, {
+            default: () => h('span', '')
+        });
+
+        const tooltipWrapper = document.createElement('div');
+        document.body.appendChild(tooltipWrapper);
+        render(tooltipVNode, tooltipWrapper);
+
+        el.addEventListener('mouseenter', (event: MouseEvent) => {
+            if (isTextEllipsis(el)) {
+                tooltipVNode.component!.props.content = el.textContent;
+                tooltipVNode.component!.props.visible = true;
+                const targetElement = event.currentTarget as HTMLElement;
+                const { clientWidth, clientHeight } = targetElement;
+                const { top, left } = targetElement.getBoundingClientRect();
+
+                tooltipWrapper.style.position = 'absolute';
+                tooltipWrapper.style.left = `${left + 10}px`;
+                tooltipWrapper.style.top = `${top}px`;
+                // tooltipWrapper.style.width = `${clientWidth}px`;
+                // tooltipWrapper.style.height = `${clientHeight}px`;
+            }
+        });
+
+        el.addEventListener('mouseleave', () => {
+            tooltipVNode.component!.props.visible = false;
+        });
+    },
+    unmounted(el: HTMLElement) {
+        el.removeEventListener('mouseenter', () => { });
+        el.removeEventListener('mouseleave', () => { });
+    }
+}
+
+
+// 对应方法
+function handleInput(event: Event) { // 处理输入
     const input = event.target as HTMLInputElement;
     const regex = /^\d*\.?\d*$/;
     if (!regex.test(input.value)) {
@@ -37,16 +88,15 @@ function handleInput(event: Event) {
     }
 }
 
-function extractPath(str: any) {
-    const startIndex = str.indexOf('/');
-    const endIndex = str.indexOf('/', startIndex + 1);
-    return str.slice(startIndex, endIndex !== -1 ? endIndex : undefined);
+function isTextEllipsis(element: HTMLElement): boolean { // 判断文字是否被省略
+    return element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
 }
 
 // 导出的自定义指令
 const customize = [
     { key: 'permission', directive: PermissionDirective, name: '角色权限' },
-    { key: 'enterNumber', directive: PositiveIntegerDirective, name: 'input正整数' }
+    { key: 'enterNumber', directive: PositiveIntegerDirective, name: 'input正整数' },
+    { key: 'ellipsisTooltip', directive: EllipsisTooltipDirective, name: '文字省略显示工具提示' },
 ]
 
 export default customize;

+ 28 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

@@ -1,4 +1,5 @@
 import { defalutModalForm } from "@/components/TaskModal/api";
+import { ElNotification } from "element-plus";
 import { ElMessageBox } from "element-plus";
 import { IMPORTTIMELIST } from '../pages/api'
 import { get, post } from "./request";
@@ -263,3 +264,30 @@ export function setTemplateDataDisable(list: Array<any>, fieldList: Array<any>)
   })
   return result;
 } 
+
+/**
+ * 新建商机指定方法,用来判断商机金额需与产品总金额是否相等
+ * @param mob 商机表单
+ * @param arr 相关产品
+ * @returns Boolean
+ */
+export function judgmentaAmounteEqual(mob: any, arr: any) {
+  if(!arr || arr.length <= 0) {
+    return false;
+  }
+  let flag = false;
+  const amounte = mob.amountOfMoney || 0;
+  const totalAmounte = arr.reduce((pre: number, cur: any) => pre + (cur.totalPrice || 0), 0);
+
+  if (amounte != totalAmounte) {
+    ElNotification.closeAll();
+    ElNotification({
+      title: '提示',
+      message: `商机金额${amounte > totalAmounte ? '大于' : '小于'}产品总金额,请修改`,
+      type: 'warning',
+    });
+    flag = true;
+  }
+
+  return flag;
+}

+ 2 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/Clue.java

@@ -166,6 +166,8 @@ public class Clue extends Model<Clue> {
     private List<UploadFile> files;
     @TableField(exist = false)
     private List<Task> taskList;
+    @TableField(exist = false)
+    private Integer isDesc;
 
 
     @Override

+ 6 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java

@@ -288,6 +288,12 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
                 // 文件上传成功的响应消息
                 HttpRespMsg msg = new HttpRespMsg();
                 msg.setMsg("上传成功");
+                ActionLog log = new ActionLog();
+                log.setItemId(bo.getId());
+                log.setCode("business");
+                log.setUserId(user.getId());
+                log.setName("上传了文件");
+                log.setCreatTime(new Date());
                 return msg;
             } catch (IOException e) {
                 e.printStackTrace();

+ 7 - 4
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/ClueServiceImpl.java

@@ -187,7 +187,7 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
             String executorId = task.getExecutorId();
             List<String> ids1 = new ArrayList<>();
             List<String> userNames = new ArrayList<>();
-            if (!executorId.isEmpty()) {
+            if (executorId != null) {
                 for (String id : executorId.split(",")) {
                     ids1.add(id);
                 }
@@ -312,16 +312,19 @@ public class ClueServiceImpl extends ServiceImpl<ClueMapper, Clue> implements Cl
                 uploadFileMapper.insert(uf);
                 // 构建上传文件的目标路径
                 String filePath1 = filePath + realName;
-
                 // 在服务器上创建文件
                 File dest = new File(filePath1);
-
                 // 将上传的文件保存到目标路径
                 file.transferTo(dest);
-
                 // 文件上传成功的响应消息
                 HttpRespMsg msg = new HttpRespMsg();
                 msg.setMsg("上传成功");
+                ActionLog log = new ActionLog();
+                log.setItemId(clue.getId());
+                log.setCode("clue");
+                log.setUserId(user.getId());
+                log.setName("上传了文件");
+                log.setCreatTime(new Date());
                 return msg;
             } catch (IOException e) {
                 e.printStackTrace();

+ 61 - 17
fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/ClueMapper.xml

@@ -57,6 +57,7 @@
                (select name from sys_dict where c.customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
                (select name from sys_dict where c.customer_level_id = id and code = 'CustomLevel') customerLevelValue
         from clue c
+        left join sys_dict sd on c.customer_level_id = sd.id
         where
             c.company_id = #{companyId} and is_delete = #{isDelete}
         <if test="inchargerId != null and inchargerId != ''  ">
@@ -83,7 +84,21 @@
         <if test="customerIndustryId != null">
         and  c.customer_industry_id =  #{customerIndustryId}
         </if>
-        ORDER BY c.id DESC
+        ORDER BY
+        <choose>
+            <when test="isDesc == null or isDesc == ''">
+                c.id DESC
+            </when>
+            <when test="isDesc == 0">
+                sd.seq ASC, c.id DESC
+            </when>
+            <when test="isDesc == 1">
+                sd.seq DESC, c.id DESC
+            </when>
+            <otherwise>
+                c.id DESC
+            </otherwise>
+        </choose>
         limit #{pageIndex},#{pageFrom}
     </select>
     <select id="list1" resultType="com.management.platform.entity.Clue">
@@ -113,6 +128,7 @@
         (select name from sys_dict where c.customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
         (select name from sys_dict where c.customer_level_id = id and code = 'CustomLevel') customerLevelValue
         from clue c
+        left join sys_dict sd on c.customer_level_id = sd.id
         where
         c.company_id = #{clue.companyId} and is_delete = #{clue.isDelete}
         and (c.incharger_id in
@@ -142,13 +158,27 @@
         <if test="clue.customerIndustryId != null">
             and  c.customer_industry_id =  #{clue.customerIndustryId}
         </if>
-        ORDER BY c.id DESC
+        ORDER BY
+        <choose>
+            <when test="isDesc == null or isDesc == ''">
+                c.id DESC
+            </when>
+            <when test="isDesc == 0">
+                sd.seq ASC, c.id DESC
+            </when>
+            <when test="isDesc == 1">
+                sd.seq DESC, c.id DESC
+            </when>
+            <otherwise>
+                c.id DESC
+            </otherwise>
+        </choose>
         limit #{clue.pageIndex},#{clue.pageFrom}
     </select>
     <select id="list2" resultType="com.management.platform.entity.Clue">
         select c.id,
         c.company_id,
-        (select company_name from company where company_id = c.id) companyName,
+        (select company_name from company where c.company_id = c.id) companyName,
         c.clue_name,
         c.clue_source_id,
         c.phone,
@@ -172,8 +202,9 @@
         (select name from sys_dict where c.customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
         (select name from sys_dict where c.customer_level_id = id and code = 'CustomLevel') customerLevelValue
         from clue c
+        left join sys_dict sd on c.customer_level_id = sd.id
         where
-        c.company_id = #{clue.companyId} and is_delete = #{clue.isDelete}
+        c.company_id = #{clue.companyId} and c.is_delete = #{clue.isDelete}
         and (c.incharger_id = #{userId} or c.incharger_id is null)
         <if test="clue.inchargerId != null and clue.inchargerId != ''  ">
           and c.incharger_id =#{clue.inchargerId}
@@ -199,7 +230,21 @@
         <if test="clue.customerIndustryId != null">
             and  c.customer_industry_id =  #{clue.customerIndustryId}
         </if>
-        ORDER BY c.id DESC
+        ORDER BY
+        <choose>
+            <when test="clue.isDesc == null or clue.isDesc == ''">
+                c.id DESC
+            </when>
+            <when test="clue.isDesc == 0">
+                sd.seq ASC, c.id DESC
+            </when>
+            <when test="clue.isDesc == 1">
+                sd.seq DESC, c.id DESC
+            </when>
+            <otherwise>
+                c.id DESC
+            </otherwise>
+        </choose>
         limit #{clue.pageIndex},#{clue.pageFrom}
     </select>
     <select id="getTotal" resultType="java.lang.Integer">
@@ -268,35 +313,34 @@
     </select>
     <select id="getTotal2" resultType="java.lang.Integer">
         select
-        COUNT(c.id)
+            count(c.id)
         from clue c
         where
-        c.company_id = #{clue.companyId} and is_delete = #{clue.isDelete}
+        c.company_id = #{clue.companyId} and c.is_delete = #{clue.isDelete}
+        and (c.incharger_id = #{userId} or c.incharger_id is null)
         <if test="clue.inchargerId != null and clue.inchargerId != ''  ">
-          and  c.incharger_id =#{clue.inchargerId}
+            and c.incharger_id =#{clue.inchargerId}
         </if>
-        and c.incharger_id = #{userId} and c.incharger_id is null
         <if test="clue.startTime != null and clue.endTime != null ">
-            and c.create_time BETWEEN #{clue.startTime} and #{clue.endTime}
+            and  c.create_time BETWEEN  #{clue.startTime} and #{clue.endTime}
         </if>
         <if test="clue.clueName != null and clue.clueName != '' ">
-            and c.clue_name LIKE CONCAT('%', #{clue.clueName}, '%')
+            and  c.clue_name  LIKE CONCAT('%', #{clue.clueName}, '%')
         </if>
         <if test="clue.phone != null and clue.phone != '' ">
             and  c.phone LIKE CONCAT('%', #{clue.phone}, '%')
         </if>
-        <if test="clue.email != null and clue.email != '' ">
-            and  c.email LIKE CONCAT('%', #{clue.email}, '%')
-        </if>
-
         <if test="clue.customerLevelId != null and clue.customerLevelId != '' ">
             and  c.customer_level_id =  #{clue.customerLevelId}
         </if>
+        <if test="clue.email != null and clue.email != '' ">
+            and  c.email LIKE CONCAT('%', #{clue.email}, '%')
+        </if>
         <if test="clue.clueSourceId != null">
-            and c.clue_source_id = #{clue.clueSourceId}
+            and  c.clue_source_id =  #{clue.clueSourceId}
         </if>
         <if test="clue.customerIndustryId != null">
-            and c.customer_industry_id = #{clue.customerIndustryId}
+            and  c.customer_industry_id =  #{clue.customerIndustryId}
         </if>
     </select>
 

+ 6 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -1466,6 +1466,9 @@ public class ReportController {
                             if (parentDept != null) {
                                 report.setAuditDeptid(parentDept.getDepartmentId());
                                 report.setAuditDeptManagerid(parentDept.getManagerId());
+                            } else {
+                                //没有上级部门,直接算部门审核通过
+                                report.setDepartmentAuditState(1);
                             }
                         } else {
                             report.setAuditDeptid(department.getDepartmentId());
@@ -1487,6 +1490,9 @@ public class ReportController {
                             if (parentDept != null) {
                                 report.setAuditDeptid(parentDept.getDepartmentId());
                                 report.setAuditDeptManagerid(parentDept.getManagerId());
+                            } else {
+                                //没有上级部门,直接算部门审核通过
+                                report.setDepartmentAuditState(1);
                             }
                         } else {
                             report.setAuditDeptid(department.getDepartmentId());

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskController.java

@@ -188,7 +188,8 @@ public class TaskController {
             //针对依斯呗的校验
             if(user.getCompanyId()==3092){
                 Project project = projectService.getById(task.getProjectId());
-                if(task.getGroupId()!=null&&(project.getCategory()!=null&&project.getCategory()==696)){
+                //除了报价项目  售后报价项目和研发项目不管控  其它项目都管控
+                if(task.getGroupId()!=null&&(project.getCategory()!=null && !(project.getCategory()==644 || project.getCategory()==647 || project.getCategory()==697))){
                     TaskGroup taskGroup = taskGroupService.getById(task.getGroupId());
                     if(taskGroup.getManDay()==null){
                         msg.setError("创建失败,请先分配任务分组的预估工时");

+ 18 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/WeiXinCorpController.java

@@ -4045,4 +4045,22 @@ public class WeiXinCorpController {
         }
         return msg;
     }
+
+
+    @RequestMapping("/testGetApprovalList")
+    public HttpRespMsg testGetApprovalList(String startDate,String endDate) throws Exception {
+        HttpRespMsg msg=new HttpRespMsg();
+        JSONArray jsonArrayFilter = new JSONArray();
+        JSONObject filter1 = new JSONObject();
+        filter1.put("key","record_type");
+        filter1.put("value",1);
+        jsonArrayFilter.add(filter1);
+        JSONObject filter2 = new JSONObject();
+        filter2.put("key","sp_status ");
+        filter2.put("value",2);
+        jsonArrayFilter.add(filter2);
+        JSONArray approvalInfo = wxCorpInfoService.getApprovalInfo(7, startDate, endDate, "", jsonArrayFilter);
+        msg.setData(approvalInfo.toArray());
+        return msg;
+    }
 }

+ 4 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/WxCorpInfoService.java

@@ -1,5 +1,6 @@
 package com.management.platform.service;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.management.platform.entity.LeaveSheet;
@@ -81,5 +82,7 @@ public interface WxCorpInfoService extends IService<WxCorpInfo> {
 
     public String getTemplateDetail(Integer companyId) throws Exception;
 
-    public String applyEvent(HttpServletRequest request,JSONObject data) throws Exception;
+    JSONArray getApprovalInfo(Integer companyId, String startDate, String endDate, String newCursor, JSONArray filterArray) throws Exception;
+
+    public String applyEvent(HttpServletRequest request, JSONObject data) throws Exception;
 }

+ 51 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -89,6 +89,12 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
     //提交审批至企业微信
     public static final String APPLY_EVENT = "https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN";
 
+    //批量获取企业审批单号
+    public static final String BATCH_GET_APPROVAL_INFO = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token=ACCESS_TOKEN";
+
+    //获取审批申请详情
+    public static final String GET_APPROVAL_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=ACCESS_TOKEN";
+
 
     public static final int TEXT_CARD_MSG_BUSTRIP_WAITING_AUDIT = 0;//出差待审核
     public static final int TEXT_CARD_MSG_BUSTRIP_AGREE = 1;//出差审核通过
@@ -2405,6 +2411,51 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
         return null;
     }
 
+    /**
+     * 批量获取审批单号
+     * PS:Integer recordType 1-请假;2-打卡补卡;3-出差;4-外出;5-加班; 6- 调班;7-会议室预定;8-退款审批;9-红包报销审批
+     *    Integer sp_status 1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付
+     * */
+    @Override
+    public JSONArray getApprovalInfo(Integer companyId, String startDate, String endDate, String newCursor, JSONArray filterArray) throws Exception {
+        DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDateTime startDateTime = LocalDate.parse(startDate, df).atTime(LocalTime.MIN);
+        LocalDateTime endDateTime = LocalDate.parse(endDate, df).atTime(LocalTime.MAX);
+        long startTime = startDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() -
+                LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        long endTime = endDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() -
+                LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id",companyId));
+        String url=BATCH_GET_APPROVAL_INFO.replace("ACCESS_TOKEN",getCorpAccessToken(wxCorpInfo));
+        HttpHeaders headers = new HttpHeaders();
+        RestTemplate restTemplate = new RestTemplate();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        JSONObject requestMap = new JSONObject();
+        requestMap.put("starttime",startTime);
+        requestMap.put("endtime",endTime);
+        requestMap.put("new_cursor",newCursor);
+        requestMap.put("size",100);
+        requestMap.put("filters",filterArray);
+        HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+        ResponseEntity<String> ResponseEntity = restTemplate.postForEntity(url, entity, String.class);
+        if (ResponseEntity.getStatusCode() == HttpStatus.OK) {
+            JSONArray jsonArray=new JSONArray();
+            String resp = ResponseEntity.getBody();
+            JSONObject jsonObject = JSONObject.parseObject(resp);
+            JSONArray sp_no_list = jsonObject.getJSONArray("sp_no_list");
+            jsonArray.addAll(sp_no_list);
+            if(jsonObject.containsKey("new_next_cursor")){
+                String new_next_cursor = jsonObject.getString("new_next_cursor");
+                JSONArray approvalInfo = getApprovalInfo(companyId, startDate, endDate, new_next_cursor, filterArray);
+                jsonArray.addAll(approvalInfo);
+            }
+            return jsonArray;
+        }
+        return null;
+    }
+
     /**
      * 提交审批到企业微信
      * */

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/task/TimingTask.java

@@ -971,7 +971,7 @@ public class TimingTask {
                         String deptAuditorId = null;
                         if (report.getDepartmentAuditState() == 0) {
                             deptAuditorId = report.getAuditDeptManagerid();
-                            if (!deptAuditorId.equals(pAuditorId)) {
+                            if (deptAuditorId != null &&!deptAuditorId.equals(pAuditorId)) {
                                 //不是同一个人,需要单独统计
                                 if (auditorMap.get(deptAuditorId) == null) {
                                     auditorMap.put(deptAuditorId, 1L);

+ 19 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/controller/WxCorpInfoController.java

@@ -79,5 +79,24 @@ public class WxCorpInfoController {
     public HttpRespMsg batchTransferLicense(HttpServletRequest request,String handoverId,String takeoverId) throws Exception {
         return wxCorpInfoService.batchTransferLicense(request,handoverId,takeoverId);
     }
+
+
+
+    @RequestMapping("/testGetApprovalList")
+    public HttpRespMsg testGetApprovalList(String startDate,String endDate) throws Exception {
+        HttpRespMsg msg=new HttpRespMsg();
+        JSONArray jsonArrayFilter = new JSONArray();
+        JSONObject filter1 = new JSONObject();
+        filter1.put("key","record_type");
+        filter1.put("value",1);
+        jsonArrayFilter.add(filter1);
+        JSONObject filter2 = new JSONObject();
+        filter2.put("key","sp_status ");
+        filter2.put("value",2);
+        jsonArrayFilter.add(filter2);
+        JSONArray approvalInfo = wxCorpInfoService.getApprovalInfo(7, startDate, endDate, "", jsonArrayFilter);
+        msg.setData(approvalInfo.toArray());
+        return msg;
+    }
 }
 

+ 14 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/ProdProcedureTeam.java

@@ -4,6 +4,8 @@ import java.math.BigDecimal;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
 import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import com.baomidou.mybatisplus.annotation.TableField;
 import java.io.Serializable;
@@ -89,6 +91,9 @@ public class ProdProcedureTeam extends Model<ProdProcedureTeam> {
     @TableField(exist = false)
     private User user;
 
+    @TableField(exist = false)
+    private String planProcedureIds;
+
     /**
      * 是否为更换人员动作
      */
@@ -102,6 +107,15 @@ public class ProdProcedureTeam extends Model<ProdProcedureTeam> {
     @TableField("steel_num_array")
     private String steelNumArray;
 
+
+    /**
+     * 分配日期
+     */
+    @TableField("distribute_date")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate distributeDate;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

+ 6 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/User.java

@@ -313,6 +313,12 @@ public class User extends Model<User> {
     @TableField(exist = false)
     private String  totalResult;
 
+    @TableField(exist = false)
+    private String  totalPlanResult;
+
+    @TableField(exist = false)
+    private String  totalSurplusResult;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

+ 5 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/WxCorpInfoService.java

@@ -1,5 +1,6 @@
 package com.management.platform.service;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.management.platform.entity.User;
@@ -79,4 +80,8 @@ public interface WxCorpInfoService extends IService<WxCorpInfo> {
      * @throws Exception
      */
     public String getCorpAgentAccessToken(WxCorpInfo corpInfo) throws Exception;
+
+    JSONArray getApprovalInfo(Integer companyId, String startDate, String endDate,String newCursor, JSONArray jsonArrayFilter) throws Exception;
+
+    String getApprovalInfoDetail(Integer companyId, String spNo) throws Exception;
 }

+ 2 - 2
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/PlanServiceImpl.java

@@ -342,7 +342,6 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         }
         List<ProdProcedure> procedureList = prodProcedureMapper.selectList(
                 new QueryWrapper<ProdProcedure>()
-                        .select("DISTINCT version_number")
                         .lambda()
                         .eq(plan.getProductId() != null, ProdProcedure::getProductId, plan.getProductId())
                         .eq(user.getCompanyId()!=null,ProdProcedure::getCompanyId,user.getCompanyId()).orderByAsc(ProdProcedure::getSeq)
@@ -358,7 +357,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         List<PlanProcedureTotal> oldPlanProcedureTotals=new ArrayList<>();
         List<ProdProcedure> list;
         if(plan.getId()==null){
-            list = procedureList.stream().filter(pl -> pl.getVersionNumber().equals(procedureList.get(0).getVersionNumber())).collect(Collectors.toList());
+            list = procedureList.stream().filter(pl -> pl.getVersionNumber().equals(procedureList.get(procedureList.size()-1).getVersionNumber())).collect(Collectors.toList());
             if(plan.getProductSchedulingNum()!=null){
                 if(count(new QueryWrapper<Plan>().eq("product_scheduling_num",plan.getProductSchedulingNum()))>0){
                     msg.setError("当前排产工单号已存在");
@@ -1300,6 +1299,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
                 ProdProcedureTeam prodProcedureTeam=new ProdProcedureTeam();
                 prodProcedureTeam.setCompanyId(companyId);
                 prodProcedureTeam.setPlanProcedureId(planProcedureTotal.getId());
+                prodProcedureTeam.setDistributeDate(LocalDate.now());
                 prodProcedureTeam.setUserId(team[i]);
                 int finalI = i;
                 //已存在的人员更新处理

+ 117 - 7
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -4112,16 +4112,126 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         }
 //        totalUser.setPersonWorkHoursWages(totalList);
         personWorkHoursWagesList.addAll(totalList);
-        userList.forEach(u->{
+        //日期范围内所有分配数据
+        List<ProdProcedureTeam> allProcedureTeamList = prodProcedureTeamMapper.selectList(new LambdaQueryWrapper<ProdProcedureTeam>().between(ProdProcedureTeam::getDistributeDate, startDate, endDate));
+        //日期范围内所有派工数据
+        List<Integer> ids = allProcedureTeamList.stream().map(ProdProcedureTeam::getPlanProcedureId).distinct().collect(Collectors.toList());
+        List<PlanProcedureTotal> planProcedureTotalList = planProcedureTotalMapper.selectList(new LambdaQueryWrapper<PlanProcedureTotal>().in(PlanProcedureTotal::getId, ids));
+        for (User u : userList) {
             List<Map<String, Object>> mapList = personWorkHoursWagesList.stream().filter(pl -> String.valueOf(pl.get("userId")).equals(u.getId())).collect(Collectors.toList());
+            for (String date : dataStringList) {
+                List<ProdProcedureTeam> targetTeams = allProcedureTeamList.stream().filter(a -> a.getDistributeDate().format(dtf1).equals(date) && u.getId().equals(a.getUserId())).collect(Collectors.toList());
+                //找到当前人员分组的
+                Optional<Map<String, Object>> createDateValue = mapList.stream().filter(m -> String.valueOf(m.get("crateDate")).equals(date)).findFirst();
+                if (createDateValue.isPresent()) {
+                    Map<String, Object> map = createDateValue.get();
+                    if (targetTeams.size() > 0) {
+                        //计算本人被分配数据
+                        double workTime = targetTeams.stream().mapToDouble(ProdProcedureTeam::getWorkTime).sum();
+                        double cost = targetTeams.stream().mapToDouble(i -> i.getJobOfMoney().doubleValue()).sum();
+                        map.put("planCost", String.format("%.2f", cost));
+                        map.put("planWorkTime", String.format("%.2f", workTime));
+                        List<Integer> totalIds = targetTeams.stream().map(ProdProcedureTeam::getPlanProcedureId).distinct().collect(Collectors.toList());
+                        //获取与本人分配相关的所有派工数据
+                        List<PlanProcedureTotal> targetPlanTotals = planProcedureTotalList.stream().filter(p -> totalIds.contains(p.getId())).collect(Collectors.toList());
+                        BigDecimal lastWorkTime = new BigDecimal(0);
+                        BigDecimal lastCost = new BigDecimal(0);
+                        for (PlanProcedureTotal targetPlanTotal : targetPlanTotals) {
+                            //不同的分配数据对应派工数据不同 需要分开计算
+                            //分别计算每个派工单的已填报和总预算
+                            List<ProdProcedureTeam> teamList = allProcedureTeamList.stream().filter(a -> targetPlanTotal.getId().equals(a.getPlanProcedureId())).collect(Collectors.toList());
+                            List<String> teamIds = teamList.stream().map(ProdProcedureTeam::getUserId).distinct().collect(Collectors.toList());
+                            //获当前分配日期下的所分配人员工时成本总和
+                            double totalWorkingHours = targetPlanTotal.getTotalWorkingHours();
+                            double totalWages = targetPlanTotal.getTotalWages();
+                            //获当前分配日期下的所填报人员工时成本总和
+                            double workTimeSum = targetPlanTotal.getTotalFillTime();
+                            BigDecimal decimal = new BigDecimal(targetPlanTotal.getTotalProgress());
+                            decimal = decimal.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+                            BigDecimal costSum = new BigDecimal(targetPlanTotal.getTotalWages());
+                            costSum = costSum.multiply(decimal).setScale(1, RoundingMode.HALF_UP);
+
+                            BigDecimal decimalCost = new BigDecimal(totalWages);
+                            decimalCost = decimalCost.subtract(costSum).setScale(1, RoundingMode.HALF_UP);
+                            BigDecimal decimalWorkTime = new BigDecimal(totalWorkingHours);
+                            decimalWorkTime = decimalWorkTime.subtract(new BigDecimal(workTimeSum)).setScale(1, RoundingMode.HALF_UP);
+                            //根据分配人数重新计算平均值
+                            decimalCost = decimalCost.divide(new BigDecimal(teamIds.size()), 1, RoundingMode.HALF_UP);
+                            decimalWorkTime = decimalWorkTime.divide(new BigDecimal(teamIds.size()), 1, RoundingMode.HALF_UP);
+                            lastWorkTime=lastWorkTime.add(decimalWorkTime);
+                            lastCost=lastCost.add(decimalCost);
+                        }
+                        if (lastWorkTime.doubleValue() >0 && lastCost.doubleValue() > 0) {
+                            map.put("surplusCost", lastCost.doubleValue());
+                            map.put("surplusTime", lastWorkTime.doubleValue());
+                        }
+                    }
+                } else {
+                    Map map = new HashMap();
+                    map.put("crateDate", date);
+                    map.put("cost", 0);
+                    map.put("workTime", 0);
+                    map.put("departmentName", u.getDepartmentName());
+                    if (targetTeams.size() > 0) {
+                        double workTime = targetTeams.stream().mapToDouble(ProdProcedureTeam::getWorkTime).sum();
+                        double cost = targetTeams.stream().mapToDouble(i -> i.getJobOfMoney().doubleValue()).sum();
+                        map.put("planCost", String.format("%.2f", cost));
+                        map.put("planWorkTime", String.format("%.2f", workTime));
+                        List<Integer> totalIds = targetTeams.stream().map(ProdProcedureTeam::getPlanProcedureId).distinct().collect(Collectors.toList());
+                        //获取与本人分配相关的所有派工数据
+                        List<PlanProcedureTotal> targetPlanTotals = planProcedureTotalList.stream().filter(p -> totalIds.contains(p.getId())).collect(Collectors.toList());
+                        BigDecimal lastWorkTime = new BigDecimal(0);
+                        BigDecimal lastCost = new BigDecimal(0);
+                        for (PlanProcedureTotal targetPlanTotal : targetPlanTotals) {
+                            //不同的分配数据对应派工数据不同 需要分开计算
+                            //分别计算每个派工单的已填报和总预算
+                            List<ProdProcedureTeam> teamList = allProcedureTeamList.stream().filter(a -> targetPlanTotal.getId().equals(a.getPlanProcedureId())).collect(Collectors.toList());
+                            List<String> teamIds = teamList.stream().map(ProdProcedureTeam::getUserId).distinct().collect(Collectors.toList());
+                            //获当前分配日期下的所分配人员工时成本总和
+                            double totalWorkingHours = targetPlanTotal.getTotalWorkingHours();
+                            double totalWages = targetPlanTotal.getTotalWages();
+                            //获当前分配日期下的所填报人员工时成本总和
+                            double workTimeSum = targetPlanTotal.getTotalFillTime();
+                            BigDecimal decimal = new BigDecimal(targetPlanTotal.getTotalProgress());
+                            decimal = decimal.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+                            BigDecimal costSum = new BigDecimal(targetPlanTotal.getTotalWages());
+                            costSum = costSum.multiply(decimal).setScale(1, RoundingMode.HALF_UP);
+
+                            BigDecimal decimalCost = new BigDecimal(totalWages);
+                            decimalCost = decimalCost.subtract(costSum).setScale(1, RoundingMode.HALF_UP);
+                            BigDecimal decimalWorkTime = new BigDecimal(totalWorkingHours);
+                            decimalWorkTime = decimalWorkTime.subtract(new BigDecimal(workTimeSum)).setScale(1, RoundingMode.HALF_UP);
+                            //根据分配人数重新计算平均值
+                            decimalCost = decimalCost.divide(new BigDecimal(teamIds.size()), 1, RoundingMode.HALF_UP);
+                            decimalWorkTime = decimalWorkTime.divide(new BigDecimal(teamIds.size()), 1, RoundingMode.HALF_UP);
+                            lastWorkTime=lastWorkTime.add(decimalWorkTime);
+                            lastCost=lastCost.add(decimalCost);
+                        }
+                        if (lastWorkTime.doubleValue() >0 && lastCost.doubleValue() > 0) {
+                            map.put("surplusCost", lastCost.doubleValue());
+                            map.put("surplusTime", lastWorkTime.doubleValue());
+                        }
+                    }
+                    map.put("userId", u.getId());
+                    map.put("userName", u.getName());
+                    mapList.add(map);
+                }
+            }
             u.setPersonWorkHoursWages(mapList);
-            u.setDepartmentCascade(u.getId().equals("0")?"小计":convertDepartmentIdToCascade(u.getDepartmentId(),departmentList));
+            u.setDepartmentCascade(u.getId().equals("0") ? "小计" : convertDepartmentIdToCascade(u.getDepartmentId(), departmentList));
             double workTime = mapList.stream().mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("workTime")))).sum();
-            BigDecimal bigDecimal=new BigDecimal(workTime);
-//            bigDecimal=bigDecimal.multiply(new BigDecimal(60)).setScale(2, BigDecimal.ROUND_HALF_UP);
-             double cost = mapList.stream().mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("cost")))).sum();
-            u.setTotalResult(String.valueOf(bigDecimal.doubleValue())+"分钟 "+String.format("%.2f",cost)+"元");
-        });
+            BigDecimal bigDecimal = new BigDecimal(workTime);
+            double cost = mapList.stream().mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("cost")))).sum();
+            double planWorkTime = mapList.stream().filter(mt -> mt.get("planWorkTime") != null).mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("planWorkTime")))).sum();
+            BigDecimal planBigDecimal = new BigDecimal(planWorkTime);
+            double planCost = mapList.stream().filter(mt -> mt.get("planCost") != null).mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("planCost")))).sum();
+            double surplusWorkTime = mapList.stream().filter(mt -> mt.get("surplusTime") != null).mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("surplusTime")))).sum();
+            BigDecimal surplusBigDecimal = new BigDecimal(surplusWorkTime);
+            double surplusCost = mapList.stream().filter(mt -> mt.get("surplusCost") != null).mapToDouble(mt -> Double.valueOf(String.valueOf(mt.get("surplusCost")))).sum();
+            u.setTotalResult(String.valueOf(bigDecimal.doubleValue()) + "分钟 " + String.format("%.2f", cost) + "元");
+            u.setTotalPlanResult(String.valueOf(planBigDecimal.doubleValue()) + "分钟 " + String.format("%.2f", planCost) + "元");
+            u.setTotalSurplusResult(String.valueOf(surplusBigDecimal.doubleValue()) + "分钟 " + String.format("%.2f", surplusCost) + "元");
+        }
         resultMap.put("total",userIPage.getTotal());
         resultMap.put("records",userList);
         httpRespMsg.setData(resultMap);

+ 76 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -76,6 +76,13 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
     public static final String GET_CORP_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET";
 
 
+    //批量获取企业审批单号
+    public static final String BATCH_GET_APPROVAL_INFO = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token=ACCESS_TOKEN";
+
+    //获取审批申请详情
+    public static final String GET_APPROVAL_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=ACCESS_TOKEN";
+
+
     public static final int TEXT_CARD_MSG_BUSTRIP_WAITING_AUDIT = 0;//出差待审核
     public static final int TEXT_CARD_MSG_BUSTRIP_AGREE = 1;//出差审核通过
     public static final int TEXT_CARD_MSG_BUSTRIP_DENY = 2;//出差审核驳回
@@ -1980,4 +1987,73 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
         return msg;
     }
 
+
+
+    /**
+     * 批量获取审批单号
+     * PS:Integer recordType 1-请假;2-打卡补卡;3-出差;4-外出;5-加班; 6- 调班;7-会议室预定;8-退款审批;9-红包报销审批
+     *    Integer sp_status 1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付
+     * */
+    @Override
+    public JSONArray getApprovalInfo(Integer companyId, String startDate, String endDate, String newCursor, JSONArray filterArray) throws Exception {
+        DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDateTime startDateTime = LocalDate.parse(startDate, df).atTime(LocalTime.MIN);
+        LocalDateTime endDateTime = LocalDate.parse(endDate, df).atTime(LocalTime.MAX);
+        long startTime = startDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() -
+                LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        long endTime = endDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() -
+                LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id",companyId));
+        String url=BATCH_GET_APPROVAL_INFO.replace("ACCESS_TOKEN",getCorpAccessToken(wxCorpInfo));
+        HttpHeaders headers = new HttpHeaders();
+        RestTemplate restTemplate = new RestTemplate();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        JSONObject requestMap = new JSONObject();
+        requestMap.put("starttime",startTime);
+        requestMap.put("endtime",endTime);
+        requestMap.put("new_cursor",newCursor);
+        requestMap.put("size",100);
+        requestMap.put("filters",filterArray);
+        HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+        ResponseEntity<String> ResponseEntity = restTemplate.postForEntity(url, entity, String.class);
+        if (ResponseEntity.getStatusCode() == HttpStatus.OK) {
+            JSONArray jsonArray=new JSONArray();
+            String resp = ResponseEntity.getBody();
+            JSONObject jsonObject = JSONObject.parseObject(resp);
+            JSONArray sp_no_list = jsonObject.getJSONArray("sp_no_list");
+            jsonArray.addAll(sp_no_list);
+            if(jsonObject.containsKey("new_next_cursor")){
+                String new_next_cursor = jsonObject.getString("new_next_cursor");
+                JSONArray approvalInfo = getApprovalInfo(companyId, startDate, endDate, new_next_cursor, filterArray);
+                jsonArray.addAll(approvalInfo);
+            }
+            return jsonArray;
+        }
+        return null;
+    }
+
+
+    @Override
+    public String getApprovalInfoDetail(Integer companyId,String spNo) throws Exception {
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id",companyId));
+        String url=GET_APPROVAL_DETAIL.replace("ACCESS_TOKEN",getCorpAccessToken(wxCorpInfo));
+        HttpHeaders headers = new HttpHeaders();
+        RestTemplate restTemplate = new RestTemplate();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        JSONObject requestMap = new JSONObject();
+        requestMap.put("sp_no",spNo);
+        HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+        ResponseEntity<String> ResponseEntity = restTemplate.postForEntity(url, entity, String.class);
+        if (ResponseEntity.getStatusCode() == HttpStatus.OK) {
+            String resp = ResponseEntity.getBody();
+            return resp;
+        }
+        return null;
+    }
+
+
 }

+ 2 - 1
fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ProdProcedureTeamMapper.xml

@@ -17,11 +17,12 @@
         <result column="status" property="status" />
         <result column="is_change" property="isChange" />
         <result column="steel_num_array" property="steelNumArray" />
+        <result column="distribute_date" property="distributeDate" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, company_id, user_id, work_time, job_of_money, progress, checker_id, checker_name, update_time, plan_procedure_id, status, is_change, steel_num_array
+        id, company_id, user_id, work_time, job_of_money, progress, checker_id, checker_name, update_time, plan_procedure_id, status, is_change, steel_num_array, distribute_date
     </sql>
 
     <select id="getReportForWorkList" resultType="java.util.HashMap" >

+ 0 - 4
fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ReportMapper.xml

@@ -136,11 +136,7 @@
 
     <select id="getPersonWorkHoursWagesList" resultType="java.util.Map">
         select b.id AS userId,c.department_name AS departmentName,b.name AS userName,DATE_FORMAT(a.create_date,'%Y%m%d') AS crateDate,IFNULL(SUM(a.cost),0) AS cost,IFNULL(SUM(a.working_time),0) AS workTime
-        ,SUM(ppt.`work_time`) AS planWorkTime,SUM(ppt.`job_of_money`) AS planCost,
-        IF((SUM(ppt.`work_time`)-IFNULL(SUM(a.working_time),0))&lt;0,0,FORMAT((SUM(ppt.`work_time`)-IFNULL(SUM(a.working_time),0)),1)) AS surplusTime,
-        IF((SUM(ppt.`job_of_money`)-IFNULL(SUM(a.cost),0))&lt;0,0,FORMAT((SUM(ppt.`job_of_money`)-IFNULL(SUM(a.cost),0)),1)) AS surplusCost
         from report a
-        LEFT JOIN `prod_procedure_team` ppt ON ppt.id=a.`user_procedure_team_id`
         left join user b on a.creator_id=b.id
         left join department c on c.department_id=b.department_id
         where a.company_id=#{companyId}

+ 2 - 2
fhKeeper/formulahousekeeper/plugIn/form-design-master/.vscode/settings.json

@@ -2,8 +2,8 @@
   "prettier.enable": false,
   "typescript.tsdk": "node_modules/typescript/lib",
   "editor.codeActionsOnSave": {
-    "source.fixAll.eslint": true,
-    "source.fixAll.stylelint": true
+    "source.fixAll.eslint": "explicit",
+    "source.fixAll.stylelint": "explicit"
   },
   "files.associations": {
     "*.css": "postcss"

+ 26 - 0
fhKeeper/formulahousekeeper/plugIn/form-design-master/src/components.d.ts

@@ -6,6 +6,32 @@ declare module 'vue' {
   export interface GlobalComponents {
     CodeEditor: typeof import('./components/CodeEditor.vue')['default']
     ComponentGroup: typeof import('./components/ComponentGroup.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElCascader: typeof import('element-plus/es')['ElCascader']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
+    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElImage: typeof import('element-plus/es')['ElImage']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElRate: typeof import('element-plus/es')['ElRate']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSlider: typeof import('element-plus/es')['ElSlider']
+    ElSpace: typeof import('element-plus/es')['ElSpace']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
   }
 }
 

+ 1 - 0
fhKeeper/formulahousekeeper/plugIn/form-design-master/src/design/WidgetFormItem.vue

@@ -156,6 +156,7 @@ const handleDeleteClick = () => {
           :max="element.options.max"
           :min="element.options.min"
           :disabled="element.options.disabled"
+          controls-position="right"
         />
       </template>
 

+ 11 - 0
fhKeeper/formulahousekeeper/plugIn/form-design-master/src/generate/GenerateForm.vue

@@ -17,6 +17,8 @@
         :config="data.config"
         :disabled="disabled"
         :request="request"
+        :widgetFormData="widgetForm.list"
+        @updateWidgetForm="updateWidgetForm"
       />
     </template>
   </el-form>
@@ -122,6 +124,14 @@ export default defineComponent({
       })
     }
 
+    const updateWidgetForm = (list: any[]) => {
+      // console.log(list, '<=====')
+      const listIndex = state.widgetForm.list.findIndex((item: any) => item.type === 'grid')
+      state.widgetForm.list[listIndex].columns = list
+      // console.log(state.widgetForm.list);
+      state.model = {...state.model, contactsId: ''}
+    }
+
     watch(
       () => props.data,
       (val) => {
@@ -159,6 +169,7 @@ export default defineComponent({
       ...toRefs(state),
       getData,
       reset,
+      updateWidgetForm
     }
   },
 })

+ 50 - 3
fhKeeper/formulahousekeeper/plugIn/form-design-master/src/generate/GenerateFormItem.vue

@@ -18,6 +18,8 @@
         :element="colItem"
         :config="config"
         :disabled="disabled"
+        :widgetFormData="element.columns"
+        @updateWidgetForm="updateWidgetForm"
       />
     </div>
   </div>
@@ -53,7 +55,7 @@
   >
     <template v-if="element.type === 'input'">
       <el-input
-        v-model="data"
+        v-model.trim="data"
         :style="{ width: element.options.width }"
         :placeholder="element.options.placeholder"
         :maxlength="parseInt(element.options.maxlength)"
@@ -86,7 +88,7 @@
 
     <template v-if="element.type === 'password'">
       <el-input
-        v-model="data"
+        v-model.trim="data"
         :style="{ width: element.options.width }"
         :placeholder="element.options.placeholder"
         :maxlength="parseInt(element.options.maxlength)"
@@ -120,7 +122,7 @@
 
     <template v-if="element.type === 'textarea'">
       <el-input
-        v-model="data"
+        v-model.trim="data"
         type="textarea"
         resize="none"
         :rows="element.options.rows"
@@ -142,6 +144,7 @@
         :max="element.options.max"
         :min="element.options.min"
         :disabled="disabled || element.options.disabled"
+        controls-position="right"
       />
     </template>
 
@@ -235,6 +238,7 @@
         :filterable="element.options.filterable"
         :disabled="disabled || element.options.disabled"
         :style="{ width: element.options.width }"
+        @change="(value: any) => specializedHandleSelect(value, element)"
       >
         <el-option
           v-for="item of element.options.remote
@@ -345,6 +349,7 @@ const props = defineProps<{
   updatedModel: any
   disabled: boolean
   request?: Function
+  widgetFormData?: any
 }>()
 const originData = props.model[props.element.model]
 const data = computed({
@@ -381,6 +386,48 @@ async function download(defaultValue: string, label: string) {
   a.download = `${label}.${defaultValue.split('.')[1]}`
   a.click()
 }
+
+const emits = defineEmits(['updateWidgetForm'])
+const specializedHandleSelect = (val: any, element: any) => {
+  const field = element.model
+  if(field == 'customerId') {
+    // console.log(props, '<===== props')
+    let list = JSON.parse(JSON.stringify(props.widgetFormData))
+    for(var i in list) {
+      if(list[i].list[0].model == 'contactsId') {
+        let item = list[i].list[0]
+        const token: any = sessionStorage.getItem('token')
+        // fetch(item.options.remoteFunc, {
+        fetch(`${item.options.remoteFunc}?customerId=${val}`, {
+          headers: {
+            "Content-type": " application/x-www-form-urlencoded; charset=UTF-8",
+            "Token": token
+          }
+        })
+          .then(resp => resp.json())
+          .then((json) => {
+            const res = json.data
+            if (res instanceof Array) {
+              item.options.remoteOptions = res.map(data => ({
+                label: data[item.options.props.label],
+                value: data[item.options.props.value],
+                children: data[item.options.props.children],
+              }))
+            }
+            list[i].list[0].options = {...list[i].list[0].options, disabled: false, defaultValue: ''}
+          })
+      }
+    }
+    setTimeout(() => {
+      console.log('开始执行')
+      updateWidgetForm(list)
+    }, 100)
+  }
+}
+
+const updateWidgetForm = (list: any) => {
+  emits('updateWidgetForm', list)
+}
 </script>
 <style scoped>
 :deep(.el-upload--picture-card) {

+ 1 - 0
fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/generate/GenerateForm.vue.d.ts

@@ -16,6 +16,7 @@ declare const _default: import("vue").DefineComponent<{
 }, {
     getData: () => Promise<unknown>;
     reset: () => void;
+    updateWidgetForm: (list: any[]) => void;
     generateForm: import("vue").Ref<any>;
     model: import("vue").Ref<any>;
     updatedModel: import("vue").Ref<any>;

+ 6 - 2
fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/generate/GenerateFormItem.vue.d.ts

@@ -6,14 +6,18 @@ declare const _default: import("vue").DefineComponent<__VLS_TypePropsToRuntimePr
     updatedModel: any;
     disabled: boolean;
     request?: Function | undefined;
-}>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, Record<string, any>, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
+    widgetFormData?: any;
+}>, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "updateWidgetForm"[], "updateWidgetForm", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
     config: WidgetForm['config'];
     element: any;
     model: any;
     updatedModel: any;
     disabled: boolean;
     request?: Function | undefined;
-}>>>, {}>;
+    widgetFormData?: any;
+}>>> & {
+    onUpdateWidgetForm?: ((...args: any[]) => any) | undefined;
+}, {}>;
 export default _default;
 declare type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
 declare type __VLS_TypePropsToRuntimeProps<T> = {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.css


+ 69 - 16
fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.es.js

@@ -27558,7 +27558,7 @@ var _export_sfc = (sfc, props) => {
   }
   return target;
 };
-const _withScopeId = (n) => (pushScopeId("data-v-1a926725"), n = n(), popScopeId(), n);
+const _withScopeId = (n) => (pushScopeId("data-v-fd931432"), n = n(), popScopeId(), n);
 const _hoisted_1$6 = { key: 12 };
 const _hoisted_2$5 = {
   key: 1,
@@ -27575,9 +27575,11 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
     model: null,
     updatedModel: null,
     disabled: { type: Boolean },
-    request: null
+    request: null,
+    widgetFormData: null
   },
-  setup(__props) {
+  emits: ["updateWidgetForm"],
+  setup(__props, { emit: emits }) {
     const props = __props;
     const originData = props.model[props.element.model];
     const data2 = computed({
@@ -27609,6 +27611,41 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
       a2.download = `${label}.${defaultValue.split(".")[1]}`;
       a2.click();
     }
+    const specializedHandleSelect = (val, element) => {
+      const field = element.model;
+      if (field == "customerId") {
+        let list = JSON.parse(JSON.stringify(props.widgetFormData));
+        for (var i in list) {
+          if (list[i].list[0].model == "contactsId") {
+            let item = list[i].list[0];
+            const token = sessionStorage.getItem("token");
+            fetch(`${item.options.remoteFunc}?customerId=${val}`, {
+              headers: {
+                "Content-type": " application/x-www-form-urlencoded; charset=UTF-8",
+                "Token": token
+              }
+            }).then((resp) => resp.json()).then((json) => {
+              const res = json.data;
+              if (res instanceof Array) {
+                item.options.remoteOptions = res.map((data22) => ({
+                  label: data22[item.options.props.label],
+                  value: data22[item.options.props.value],
+                  children: data22[item.options.props.children]
+                }));
+              }
+              list[i].list[0].options = __spreadProps(__spreadValues({}, list[i].list[0].options), { disabled: false, defaultValue: "" });
+            });
+          }
+        }
+        setTimeout(() => {
+          console.log("\u5F00\u59CB\u6267\u884C");
+          updateWidgetForm(list);
+        }, 100);
+      }
+    };
+    const updateWidgetForm = (list) => {
+      emits("updateWidgetForm", list);
+    };
     return (_ctx, _cache) => {
       const _component_GenerateFormItem = resolveComponent("GenerateFormItem", true);
       const _component_el_table_column = ElTableColumn;
@@ -27651,8 +27688,10 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
                 "updated-model": __props.updatedModel,
                 element: colItem,
                 config: __props.config,
-                disabled: __props.disabled
-              }, null, 8, ["request", "model", "updated-model", "element", "config", "disabled"]);
+                disabled: __props.disabled,
+                widgetFormData: __props.element.columns,
+                onUpdateWidgetForm: updateWidgetForm
+              }, null, 8, ["request", "model", "updated-model", "element", "config", "disabled", "widgetFormData"]);
             }), 128))
           ], 4);
         }), 128))
@@ -27692,6 +27731,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             key: 0,
             modelValue: unref(data2),
             "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(data2) ? data2.value = $event : null),
+            modelModifiers: { trim: true },
             style: normalizeStyle({ width: __props.element.options.width }),
             placeholder: __props.element.options.placeholder,
             maxlength: parseInt(__props.element.options.maxlength),
@@ -27728,6 +27768,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             key: 1,
             modelValue: unref(data2),
             "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isRef(data2) ? data2.value = $event : null),
+            modelModifiers: { trim: true },
             style: normalizeStyle({ width: __props.element.options.width }),
             placeholder: __props.element.options.placeholder,
             maxlength: parseInt(__props.element.options.maxlength),
@@ -27765,6 +27806,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             key: 2,
             modelValue: unref(data2),
             "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => isRef(data2) ? data2.value = $event : null),
+            modelModifiers: { trim: true },
             type: "textarea",
             resize: "none",
             rows: __props.element.options.rows,
@@ -27784,7 +27826,8 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             style: normalizeStyle({ width: __props.element.options.width }),
             max: __props.element.options.max,
             min: __props.element.options.min,
-            disabled: __props.disabled || __props.element.options.disabled
+            disabled: __props.disabled || __props.element.options.disabled,
+            "controls-position": "right"
           }, null, 8, ["modelValue", "style", "max", "min", "disabled"])) : createCommentVNode("", true),
           __props.element.type === "radio" ? (openBlock(), createBlock(_component_el_radio_group, {
             key: 4,
@@ -27891,7 +27934,8 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             clearable: __props.element.options.clearable,
             filterable: __props.element.options.filterable,
             disabled: __props.disabled || __props.element.options.disabled,
-            style: normalizeStyle({ width: __props.element.options.width })
+            style: normalizeStyle({ width: __props.element.options.width }),
+            onChange: _cache[10] || (_cache[10] = (value) => specializedHandleSelect(value, __props.element))
           }, {
             default: withCtx(() => [
               (openBlock(true), createElementBlock(Fragment, null, renderList(__props.element.options.remote ? __props.element.options.remoteOptions : __props.element.options.options, (item) => {
@@ -27907,7 +27951,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
           __props.element.type === "switch" ? (openBlock(), createBlock(_component_el_switch, {
             key: 10,
             modelValue: unref(data2),
-            "onUpdate:modelValue": _cache[10] || (_cache[10] = ($event) => isRef(data2) ? data2.value = $event : null),
+            "onUpdate:modelValue": _cache[11] || (_cache[11] = ($event) => isRef(data2) ? data2.value = $event : null),
             "active-text": __props.element.options.activeText,
             "inactive-text": __props.element.options.inactiveText,
             disabled: __props.disabled || __props.element.options.disabled
@@ -27915,7 +27959,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
           __props.element.type === "slider" ? (openBlock(), createBlock(_component_el_slider, {
             key: 11,
             modelValue: unref(data2),
-            "onUpdate:modelValue": _cache[11] || (_cache[11] = ($event) => isRef(data2) ? data2.value = $event : null),
+            "onUpdate:modelValue": _cache[12] || (_cache[12] = ($event) => isRef(data2) ? data2.value = $event : null),
             min: __props.element.options.min,
             max: __props.element.options.max,
             step: __props.element.options.step,
@@ -27962,7 +28006,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
             key: 14,
             style: { "margin-top": "-4px" },
             type: "text",
-            onClick: _cache[12] || (_cache[12] = ($event) => download(__props.element.options.defaultValue, __props.element.label))
+            onClick: _cache[13] || (_cache[13] = ($event) => download(__props.element.options.defaultValue, __props.element.label))
           }, {
             default: withCtx(() => [
               _hoisted_6$4
@@ -27972,7 +28016,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
           __props.element.type === "cascader" ? (openBlock(), createBlock(_component_el_cascader, {
             key: 15,
             modelValue: unref(data2),
-            "onUpdate:modelValue": _cache[13] || (_cache[13] = ($event) => isRef(data2) ? data2.value = $event : null),
+            "onUpdate:modelValue": _cache[14] || (_cache[14] = ($event) => isRef(data2) ? data2.value = $event : null),
             options: __props.element.options.remoteOptions,
             placeholder: __props.element.options.placeholder,
             filterable: __props.element.options.filterable,
@@ -27997,7 +28041,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
     };
   }
 });
-var GenerateFormItem = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-1a926725"]]);
+var GenerateFormItem = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-fd931432"]]);
 const _sfc_main$7 = defineComponent({
   name: "FormGenerate",
   components: {
@@ -28081,6 +28125,11 @@ const _sfc_main$7 = defineComponent({
         }
       });
     };
+    const updateWidgetForm = (list) => {
+      const listIndex2 = state.widgetForm.list.findIndex((item) => item.type === "grid");
+      state.widgetForm.list[listIndex2].columns = list;
+      state.model = __spreadProps(__spreadValues({}, state.model), { contactsId: "" });
+    };
     watch(() => props.data, (val) => {
       var _a3;
       state.widgetForm = (_a3 = val && JSON.parse(JSON.stringify(val))) != null ? _a3 : getWidgetForm();
@@ -28106,7 +28155,8 @@ const _sfc_main$7 = defineComponent({
     };
     return __spreadProps(__spreadValues({}, toRefs(state)), {
       getData,
-      reset
+      reset,
+      updateWidgetForm
     });
   }
 });
@@ -28132,8 +28182,10 @@ function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
           element: _ctx.widgetForm.list[index],
           config: _ctx.data.config,
           disabled: _ctx.disabled,
-          request: _ctx.request
-        }, null, 8, ["model", "updated-model", "element", "config", "disabled", "request"]);
+          request: _ctx.request,
+          widgetFormData: _ctx.widgetForm.list,
+          onUpdateWidgetForm: _ctx.updateWidgetForm
+        }, null, 8, ["model", "updated-model", "element", "config", "disabled", "request", "widgetFormData", "onUpdateWidgetForm"]);
       }), 128))
     ]),
     _: 1
@@ -28496,7 +28548,8 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
               style: normalizeStyle({ width: __props.element.options.width }),
               max: __props.element.options.max,
               min: __props.element.options.min,
-              disabled: __props.element.options.disabled
+              disabled: __props.element.options.disabled,
+              "controls-position": "right"
             }, null, 8, ["model-value", "style", "max", "min", "disabled"])) : createCommentVNode("", true),
             __props.element.type === "radio" ? (openBlock(), createBlock(_component_el_radio_group, {
               key: 4,

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
fhKeeper/formulahousekeeper/plugIn/form-design-master/update/dist/index.es.js.map


+ 9 - 5
fhKeeper/formulahousekeeper/timesheet-workshop/src/views/statistic/index.vue

@@ -90,16 +90,20 @@
                     <template slot-scope="scope">
                         <div v-for="(items, indexs) in scope.row.personWorkHoursWages" :key="indexs" @click="showReportDetail(scope.row,item,0)" :class="`${scope.row.departmentCascade== '小计' ? '' : 'colorText'}`">
                             <div v-if="items.crateDate == item">
-                              <div  style="color: black;">{{items.planWorkTime}}分钟  {{items.planCost}}元</div>
-                               <div> {{items.workTime}}分钟  {{items.cost}}元</div> 
-                               <div style="color: red;">{{items.surplusTime}}分钟  {{items.surplusCost}}元</div>
+                              <div  style="color: black;" v-if="items.planWorkTime">平均 {{items.planWorkTime}}分钟  {{items.planCost}}元</div>
+                               <div>已填 {{items.workTime}}分钟  {{items.cost}}元</div> 
+                               <div style="color: red;" v-if="items.surplusTime">剩余 {{items.surplusTime}}分钟  {{items.surplusCost}}元</div>
                             </div>
                         </div>
                     </template>
                 </el-table-column>
-                <el-table-column align="center" prop="totalResult" label="合计" min-width="150">
+                <el-table-column align="center" prop="totalResult" label="合计" min-width="180">
                    <template slot-scope="scope" >
-                    <span :class="`${'colorText'}`" @click="showReportDetail(scope.row,item,1)">{{scope.row.totalResult}}</span>
+                    <div @click="showReportDetail(scope.row,item,1)">
+                      <div  style="color: black;">平均 {{scope.row.totalPlanResult}}</div>
+                      <div style="color: #02a7f0;"  @click="showReportDetail(scope.row,item,1)">已填 {{scope.row.totalResult}}</div> 
+                      <div style="color: red;">剩余 {{scope.row.totalSurplusResult}}</div>
+                    </div>
                   </template>
                 </el-table-column>
             </el-table>

+ 235 - 0
fhKeeper/formulahousekeeper/timesheet/src/components/cascadeSelection.vue

@@ -0,0 +1,235 @@
+<template>
+    <div class='cascadeSelection'>
+        <!-- 框框 -->
+        <div ref="focusDiv" tabindex="0" :class="`input ${size} ${disabled ? 'inputDisabled' : ''}`"
+            :style="`width: ${width}`" @focus="handleFocus" @blur="handleBlur">
+            <!-- 默认提示文字 -->
+            <span class="placeholderColor" v-if="resultText.length == 0">请选择</span>
+            <!-- 选中数据 -->
+            <div v-if="resultText.length > 0" class="textEllipsisNowrap">
+                <template v-for="(item, index) in resultText[0]">
+                    <TranslationOpenDataText type='departmentName' :openid='item'></TranslationOpenDataText>
+                    <span v-if="index < resultText[0].length - 1" class="textSpan">/</span>
+                </template>
+            </div>
+
+            <i v-if="resultText.length > 0" class="el-icon-circle-close iostu" @click.stop="clearDelete"></i>
+
+            <!-- 级联面板 -->
+            <div class="absoluteWeight" v-if="isFocused" :style="`top: ${sizeTop[size]}`">
+                <el-cascader-panel v-model="modelValue" ref="cascaderPanelRef" :options="options" :props="props"
+                    @change="panelChange">
+                    <template slot-scope="{ node, data }">
+                        <TranslationOpenDataText type='departmentName' :openid='data.label'></TranslationOpenDataText>
+                    </template>
+                </el-cascader-panel>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: '',
+    components: {},
+    props: {
+        modelValue: {
+            type: Array,
+            default: () => []
+        },
+        size: {
+            type: String,
+            default: 'default',
+        },
+        width: {
+            type: String,
+            default: '100%',
+        },
+        options: {
+            type: Array,
+            default: () => [],
+        },
+        props: {
+            type: Object,
+            default: () => {
+                return { checkStrictly: true, expandTrigger: 'hover' }
+            }
+        },
+        disabled: {
+            type: Boolean,
+            default: false
+        }
+    },
+    data() {
+        return {
+            isFocused: false,
+            resultText: [],
+            sizeTop: {
+                default: '40px',
+                medium: '36px',
+                small: '32px',
+                mini: '24px'
+            }
+        }
+    },
+    computed: {},
+    watch: {},
+    created() { },
+    mounted() {
+        setTimeout(() => {
+            if ((this.modelValue || []).length > 0) {
+                this.resultText = this.findDepartmentNames(this.options, this.props.multiple ? this.modelValue : [this.modelValue]);
+            }
+        }, 500)
+    },
+    model: {
+        prop: 'modelValue',
+        event: 'getValue'
+    },
+    methods: {
+        panelChange(val) {
+            this.resultText = this.findDepartmentNames(this.options, this.props.multiple ? val : [val]);
+            this.updateModelValue()
+        },
+        findDepartmentNames(data, values) {
+            const findLabels = (valueList) => {
+                return valueList.map(value => {
+                    const findLabel = (data, value) => {
+                        for (let item of data) {
+                            if (item.value === value) {
+                                return item.label;
+                            }
+                            if (item.children) {
+                                const label = findLabel(item.children, value);
+                                if (label) return label;
+                            }
+                        }
+                        return null;
+                    };
+                    return findLabel(data, value);
+                }).filter(label => label !== null);
+            };
+            return values.map(valueList => findLabels(valueList));
+        },
+        handleFocus() {
+            if(this.disabled) {
+                return
+            }
+            this.isFocused = true
+            this.$refs.focusDiv.classList.add('focused');
+        },
+        handleBlur(event) {
+            if(this.disabled) {
+                return
+            }
+            if (this.$refs.focusDiv.contains(event.relatedTarget)) {
+                this.$refs.focusDiv.focus();
+                event.preventDefault();
+                return;
+            }
+            this.isFocused = false
+            this.$refs.focusDiv.classList.remove('focused');
+        },
+        clearDelete() {
+            this.$refs.focusDiv.blur()
+            this.resultText = [];
+            this.$emit('getValue', []);
+            this.$emit('change', []);
+        },
+        updateModelValue() {
+            this.$emit('getValue', this.modelValue);
+            this.$emit('change', this.modelValue);
+        }
+    },
+}
+</script>
+<style scoped lang='scss'>
+.cascadeSelection {
+    position: relative;
+    max-height: 40px;
+
+    .default {
+        height: 40px;
+        line-height: 40px;
+    }
+
+    .medium {
+        height: 36px;
+        line-height: 36px;
+    }
+
+    .small {
+        height: 32px;
+        line-height: 32px;
+    }
+
+    .mini {
+        height: 28px;
+        line-height: 28px;
+    }
+
+    .input {
+        position: relative;
+        font-size: 14px;
+        background-color: #FFF;
+        background-image: none;
+        border-radius: 4px;
+        border: 1px solid #DCDFE6;
+        box-sizing: border-box;
+        color: #606266;
+        display: inline-block;
+        outline: 0;
+        padding: 0 15px;
+        -webkit-transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
+        transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
+        width: 100%;
+    }
+
+    .input:focus {
+        border-color: #409EFF;
+    }
+
+    .placeholderColor {
+        color: #C0C4CC;
+    }
+
+    .absoluteWeight {
+        position: absolute;
+        z-index: 99;
+        background: #fff;
+        left: 0;
+    }
+
+    .textSpan {
+        display: inline-block;
+        padding: 0 3px
+    }
+
+    .textEllipsisNowrap {
+        width: 100%;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+
+    .iostu {
+        position: absolute;
+        top: 50%;
+        margin-top: -4px;
+        right: 8px;
+        color: #C0C4CC;
+        transition: All 0.2s ease-in-out;
+    }
+
+    .iostuHover {
+        transform: rotate(-180deg);
+    }
+
+    .inputDisabled {
+        background-color: #f5f7fa !important;
+        border-color: #e4e7ed !important;
+        color: #c0c4cc !important;
+        cursor: not-allowed !important;
+    }
+}
+</style>

+ 10 - 10
fhKeeper/formulahousekeeper/timesheet/src/components/select.vue

@@ -276,7 +276,7 @@ export default {
                     }
                 }
             }
-            console.log(this.options, this.subjectId)
+            // console.log(this.options, this.subjectId)
             if(this.multiSelect) { 
                 for(var i in this.options) {
                     for(var j in this.subjectId) {
@@ -288,7 +288,7 @@ export default {
                 }
             }
         }
-        console.log(this.subject, this.subjectId)
+        // console.log(this.subject, this.subjectId)
 
         var thats = this
         var phoneArr = []
@@ -300,7 +300,7 @@ export default {
         }, 500)
         
         thats.fistArrList = phoneArr
-        console.log(thats.fistArrList)
+        // console.log(thats.fistArrList)
         this.dailyListIndex = this.idx
     },
     methods: {
@@ -315,9 +315,9 @@ export default {
                     "selectedDepartmentIds": [],// 非必填,已选部门ID列表。用于多次选人时可重入,single模式下请勿填入多个id
                     "selectedUserIds": []// 非必填,已选用户ID列表。用于多次选人时可重入,single模式下请勿填入多个id
                         },function(res){
-                            console.log(res)
+                            // console.log(res)
                             if (res.err_msg == "selectEnterpriseContact:ok"){
-                                console.log(res, '数据来源')
+                                // console.log(res, '数据来源')
                                 if(typeof res.result == 'string'){
                                     res.result = JSON.parse(res.result) //由于目前各个终端尚未完全兼容,需要开发者额外判断result类型以保证在各个终端的兼容性
                                 }
@@ -328,7 +328,7 @@ export default {
                                         var user = selectedUserList[i];
                                         userId = user.id; // 已选的单个成员ID
                                         userName = user.name;// 已选的单个成员名称
-                                        console.log(userId, userName)
+                                        // console.log(userId, userName)
                                 }
                                 for(var s in that.options) {
                                     if(that.options[s].name == userId) {
@@ -349,7 +349,7 @@ export default {
                 other: this.other,
                 name: this.selectName
             }
-            console.log(obj)
+            // console.log(obj)
             this.$emit("selectCal", obj)
         },
         selectCli() {
@@ -400,11 +400,11 @@ export default {
             this.transitionBoxLiIdx = index
         },
         liClick(item, itemIndex) {
-            console.log(item, '进来的')
+            // console.log(item, '进来的')
             let nameId = item.auditorId || item.id
             this.selectName = item.auditorName || item.name
             if(!this.multiSelect) {
-                console.log('我进来了', this.flg)
+                // console.log('我进来了', this.flg)
                 if(this.flgs) {
                     let obj = {
                         id: nameId,
@@ -540,7 +540,7 @@ export default {
                         }
                     })
                     // this.options = arr
-                    console.log('将要赋值')
+                    // console.log('将要赋值')
                     var newArr = arr.length > 0 ? arr : res.data.records
                     this.$set(this, 'options', newArr)
                     this.cursor = res.data.nextCursor

+ 15 - 13
fhKeeper/formulahousekeeper/timesheet/src/views/project/list.vue

@@ -471,13 +471,13 @@
                             </el-select>
                         </div>
                     </el-form-item>
-
                     <el-form-item :label="$t('subordinatedepartments')" :prop="user.companyId == 936 ? 'deptId' : false" :class="title == $t('newproject') && user.companyId == 936 ? 'wpgCssClass' : ''" v-if="user.timeType.projectWithDept">
                         <el-cascader v-model="addForm.deptId" :options="departmentList" :placeholder="$t('defaultText.pleaseChoose')" :disabled="canOnlyModParticipator"
                             :props="{ checkStrictly: true, expandTrigger: 'hover' }" clearable filterable @change="cascaderChange" style="width: 100%"
-                            v-if="user.userNameNeedTranslate != 1">
+                            v-if="user.userNameNeedTranslate != 1"
                         ></el-cascader>
-                        <vueCascader :size="'medium'" :widthStr="'430'" :filterable="true" :clearable="true" :subject="departmentList" :subjectId="addForm.deptId" :radios="true" :distinction="'20'" :disabled="canOnlyModParticipator" @vueCasader="vueCasader" v-if="user.userNameNeedTranslate == 1" ></vueCascader>
+                        <!-- <vueCascader :size="'medium'" :widthStr="'430'" :filterable="true" :clearable="true" :subject="departmentList" :subjectId="addForm.deptId" :radios="true" :distinction="'20'" :disabled="canOnlyModParticipator" @vueCasader="vueCasader" v-if="user.userNameNeedTranslate == 1" ></vueCascader> -->
+                        <vueCascadeSelection v-model="addForm.deptId" :options="departmentList" :props="{ checkStrictly: true, expandTrigger: 'hover' }" :disabled="canOnlyModParticipator" v-if="user.userNameNeedTranslate == 1"></vueCascadeSelection>
                     </el-form-item>
 
                     <!-- 供应商 -->
@@ -509,25 +509,25 @@
                     </el-form-item> -->
                     <el-form-item :label="$t('Allparticipants')" v-show="addForm.isPublic == 0" :class="title == $t('newproject') && user.companyId == 936 ? 'wpgCssClass' : ''">
                         <el-tooltip placement="top" effect="light" v-if="user.userNameNeedTranslate != 1">
-
                             <div slot="content" style="width:780px">{{addForm.userNames}}</div>
                             <el-input  @focus="showChooseMembTree" v-model="addForm.userNames"></el-input>
                         </el-tooltip>
 
                         <el-tooltip placement="top" effect="light" v-if="user.userNameNeedTranslate == 1">
-                            <div slot="content" style="width:780px">
+                            <div slot="content" style="max-width: 780px;max-height: 400px;overflow-y: auto;">
                                 <span v-for="(item, index) in addFormUserNames" :key="index">
-                                    <!-- {{item}} -->
                                     <TranslationOpenDataText type='userName' :openid='item'></TranslationOpenDataText>
                                     <span v-if="index < addFormUserNames.length - 1">,</span>
                                 </span>
                             </div>
                             <div @click="showChooseMembTree" style="width: 800px;overflow:hidden;white-space:nowrap;height:40px;border: 1px solid #DCDFE6;border-radius: 4px;box-sizing: border-box;padding: 0 10px">
-                                <span v-for="(item, index) in addFormUserNames" :key="index">
-                                    <!-- {{item}} -->
-                                    <TranslationOpenDataText type='userName' :openid='item'></TranslationOpenDataText>
-                                    <span v-if="index < addFormUserNames.length - 1">,</span>
-                                </span>
+                                <template v-for="(item, index) in addFormUserNames">
+                                    <template v-if="index <= 13">
+                                        <TranslationOpenDataText type='userName' :openid='item'></TranslationOpenDataText>
+                                        <span v-if="index < addFormUserNames.length - 1">,</span>
+                                        <span v-if="(addFormUserNames || []).length > 13 && index == 13">...</span>
+                                    </template>
+                                </template>
                             </div>
                         </el-tooltip>
                     </el-form-item>
@@ -538,7 +538,7 @@
                                 <span style="float: right; color: #8492a6;" v-if="user.companyId == 936">{{ item.jobNumber }}</span>
                             </el-option>
                         </el-select>
-                        <selectCat v-if="user.userNameNeedTranslate == 1" :size="'medium'" :subject="participator" :subjectId="addForm.inchargerId" :distinction="'3'" @selectCal="selectCal" :disabled="canOnlyModParticipator || projectManagerEdit || isShowProjectName"></selectCat>
+                        <selectCat v-if="user.userNameNeedTranslate == 1" :filterable="true" :size="'medium'" :subject="participator" :subjectId="addForm.inchargerId" :distinction="'3'" @selectCal="selectCal" :disabled="canOnlyModParticipator || projectManagerEdit || isShowProjectName"></selectCat>
                     </el-form-item>
 
                     <span v-if="user.companyId != 469">
@@ -1759,11 +1759,13 @@ a {
     // 自定义select组件
     import selectCat from "@/components/select.vue"
     import vueCascader from "@/components/cascader.vue"
+    import vueCascadeSelection from "@/components/cascadeSelection.vue"
     export default {
         components:{
             projectgantt,
             selectCat,
             vueCascader,
+            vueCascadeSelection
         },
         data() {
             return {
@@ -4672,7 +4674,7 @@ a {
                         var findUser = list[0];    
                         this.participator.push(findUser);
                     } else {
-                        console.log('未找到用户: '+u);
+                        // console.log('未找到用户: '+u);
                     }
                     
                 })