浏览代码

Merge branch 'master' of http://47.100.37.243:10191/wutt/manHourHousekeeper

# Conflicts:
#	fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/Task.java
#	fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/TaskMapper.java
zhouyy 6 月之前
父节点
当前提交
b06c050309
共有 42 个文件被更改,包括 1261 次插入296 次删除
  1. 4 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js
  2. 0 3
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue
  3. 67 14
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessInfo.vue
  4. 238 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessOpportunityStage.vue
  5. 23 3
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue
  6. 9 3
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/relatedBusinessOpportunities.vue
  7. 49 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/contactsInfo.vue
  8. 15 2
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/detail.vue
  9. 47 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/customerInfo.vue
  10. 15 2
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/detail.vue
  11. 26 4
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/detail.vue
  12. 15 2
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/detail.vue
  13. 47 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/threadInfo.vue
  14. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/workbench.vue
  15. 11 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java
  16. 14 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskExecutorController.java
  17. 5 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserController.java
  18. 8 8
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserCorpwxTimeController.java
  19. 41 32
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/Task.java
  20. 12 5
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/TaskExecutor.java
  21. 21 16
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/TimeType.java
  22. 5 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/TaskMapper.java
  23. 2 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/UserMapper.java
  24. 4 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ProjectService.java
  25. 2 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/UserService.java
  26. 6 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/LeaveSheetServiceImpl.java
  27. 94 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java
  28. 71 67
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  29. 7 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/UserServiceImpl.java
  30. 40 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  31. 2 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TaskExecutorMapper.xml
  32. 138 84
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TaskMapper.xml
  33. 3 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TimeTypeMapper.xml
  34. 4 0
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/UserMapper.xml
  35. 4 12
      fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue
  36. 6 1
      fhKeeper/formulahousekeeper/timesheet/src/i18n/en.json
  37. 6 1
      fhKeeper/formulahousekeeper/timesheet/src/i18n/zh.json
  38. 1 0
      fhKeeper/formulahousekeeper/timesheet/src/permissions.js
  39. 159 16
      fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue
  40. 17 15
      fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue
  41. 1 1
      fhKeeper/formulahousekeeper/timesheet/src/views/workReport/daily.vue
  42. 21 0
      fhKeeper/formulahousekeeper/timesheet_h5/src/views/my/children/center.vue

+ 4 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js

@@ -103,4 +103,7 @@ export const TOP_DATA_OF_SALES_ORDER_LIST = `/order/pageOrderByPin` // 销售订
 
 export const GET_SALES_BRIEFINGS = `/order/salesKit` // 获取销售简报
 export const OBTAIN_DATA_SUMMARY = `/order/dataSummary` // 获取数据汇总
-export const STAGE_OF_OBTAINING_BUSINESS_OPPORTUNITIES = `/order/businessOpportunityStage` // 获取商机阶段
+export const STAGE_OF_OBTAINING_BUSINESS_OPPORTUNITIES = `/order/businessOpportunityStage` // 获取商机阶段
+export const ACQUISITION_STAGE = `/business-opportunity/getStage` // 获取阶段
+export const PROMOTION_STAGE = `/business-opportunity/saveStageId` // 推进阶段
+export const STAGE_NOTES = `/business-opportunity/saveReason` // 阶段备注

+ 0 - 3
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue

@@ -381,9 +381,6 @@ function noTopMounted(row) {
 }
 
 function toDetail(item) {
-  if (queryParameters.value?.key == 'tasks') {
-    return
-  }
   router.navigateTo({
     pathName: 'details',
     success: () => {

+ 67 - 14
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessInfo.vue

@@ -1,30 +1,32 @@
 <template>
   <div class="flex flex-col h-full">
     <div class="bg-white info flex-1 overflow-y-auto cellnormall">
-      <van-cell title="商机名称" :value="info.name" />
-      <van-cell title="客户名称" :value="info.customerName" />
-      <van-cell title="联系人姓名" :value="info.contactsName" />
+      <van-cell title="商机名称" :value="infoData.name" />
+      <van-cell title="客户名称" :value="infoData.customerName" />
+      <van-cell title="联系人姓名" :value="infoData.contactsName" />
       <van-cell title="商机金额">
         <template #default>
-          <span class="text-[#FF8B32]" v-if="info.amountOfMoney">¥ {{ info.amountOfMoney }}</span>
+          <span class="text-[#FF8B32]" v-if="infoData.amountOfMoney">¥ {{ infoData.amountOfMoney }}</span>
         </template>
       </van-cell>
-      <van-cell title="预计成交" :value="info.expectedTransactionDate" />
-      <van-cell title="商机阶段" :value="info.stageValue" />
+      <van-cell title="预计成交" :value="infoData.expectedTransactionDate" />
+      <van-cell title="商机阶段" :value="infoData.stageValue" />
       <van-cell title="负责人">
         <template #default>
-          <TranslationComponent :openId="info.inchargerName" />
+          <TranslationComponent :openId="infoData.inchargerName" />
         </template>
       </van-cell>
-      <van-cell title="备注" :value="info.remark" />
+      <van-cell title="备注" :value="infoData.remark" />
     </div>
     <div class="bottomButton">
-      <van-button type="primary" class="w-full block" v-if="!info.contactsName"
+      <van-button type="primary" class="w-full block" v-if="!infoData.contactsName"
         @click="shoContactDialag()">关联联系人</van-button>
-      <van-button type="warning" class="w-full block" v-if="info.inchargerName"
+      <van-button type="warning" class="w-full block" v-if="infoData.inchargerName"
         @click="showDialogCli()">转移商机</van-button>
-      <van-button type="primary" class="w-full block" v-if="!info.inchargerName"
+      <van-button type="primary" class="w-full block" v-if="!infoData.inchargerName"
         @click="claimAndClaim()">认领商机</van-button>
+      <van-button type="default" class="w-full block" v-permission="[routingInformation.jurisdiction.edit]" @click="jumpEdit()">编辑商机</van-button>
+      <van-button type="danger" class="w-full block" v-permission="[routingInformation.jurisdiction.delete]" @click="deleteBusinessOpportunity()">删除商机</van-button>
     </div>
 
     <!-- 转移弹窗 -->
@@ -59,16 +61,20 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import { showConfirmDialog } from 'vant';
 import { useLifecycle } from '@hooks/useCommon.js';
 import { BUSINESS_OPPORTUNITY_TRANSFER, GET_CONTACTS_WITH_MORE_I_DS, CONTACT_PERSON_ASSOCIATED_WITH_BUSINESS_OPPORTUNITY } from '@hooks/useApi'
+import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
 import requests from "@common/requests";
 import useShowToast from '@hooks/useToast'
 import useInfoStore from '@store/useInfoStore'
+import useFixedData from "@store/useFixedData.js"
 import useRouterStore from "@store/useRouterStore.js";
-const router = useRouterStore()
+import { routingInfos } from "@utility/generalVariables"
 
+const router = useRouterStore()
+const fixedData = useFixedData()
 const userInfo = useInfoStore()
 const { toastSuccess, toastFail, toastText } = useShowToast()
 const props = defineProps({
@@ -78,6 +84,13 @@ const props = defineProps({
     default: () => ({})
   }
 })
+const infoData = ref(props.info)
+const routingInformation = routingInfos['business']
+
+watch(() => props.info, (newValue) => {
+  infoData.value = newValue
+  getAllContactsList()
+})
 
 const showDialog = ref(false);
 const showSelect = ref(false);
@@ -86,6 +99,46 @@ const showContactDialog = ref(false)
 const dialogSelection = ref({});
 const allContactsList = ref([]);
 
+function deleteBusinessOpportunity() {
+  const { name = '', searchFiled = {}, deteleFiled = '' } = routingInformation
+  const row = infoData.value
+  const foemVal = { [routingInformation.key == 'tasks' ? 'taskIds' : 'ids']: row.id }
+  showConfirmDialog({
+    title: `删除${name}`,
+    message: `确定删除【${row[searchFiled?.search]}】${name}吗?`,
+  }).then(() => {
+    requests.post(deteleFiled, { ...foemVal }).then((res) => {
+      toastSuccess('删除成功')
+      router.navigateBack({
+        success: () => {
+          router.emit('moduleListDetailParameter', {
+            row: JSON.stringify(routingInformation)
+          })
+        }
+      })
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
+}
+
+function jumpEdit() {
+  const formJson = fixedData.formJson[routingInformation.key] || []
+  const formList = resetListData(formJson?.list)
+  const filedObj = getListFieldKey(formList, infoData.value)
+  const formVal = { ...filedObj, id: infoData.value.id }
+
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(routingInformation),
+        filedValue: JSON.stringify(formVal)
+      })
+    }
+  })
+}
+
 function shoContactDialag() {
   dialogSelection.value = {}
   showContactDialog.value = true
@@ -181,7 +234,7 @@ function getAllContactsList() {
 
 useLifecycle({
   load: () => {
-    getAllContactsList()
+    
   },
   init: () => {
     getAllContactsList()

+ 238 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessOpportunityStage.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="w-full h-full flex flex-col">
+    <div class="flex-1 overflow-y-auto my-3 px-6">
+      <van-steps direction="vertical" :active="stageIndex">
+        <template v-for="(item, index) in stageList">
+          <van-step>
+            <div class="flex flex-row items-center bg-white rounded">
+              <div :class="`verticalArrangement px-2 py-3 ${showStageClass(index)}`">
+                {{ showStageText(index) }}
+              </div>
+              <div class="flex-1 pl-3">{{ item.name }} {{ item.plan }}</div>
+            </div>
+          </van-step>
+        </template>
+      </van-steps>
+    </div>
+
+    <div class="px-6 flex flex-col pb-4 mt-2" v-if="stageIndex <= stageList.length && displayButtonOrNot">
+      <van-button type="primary" class="btn-3" v-if="stageIndex < stageList.length - 1"
+        @click="promotionStage()">推荐下阶段({{
+          stageList[+stageIndex + 1]?.name
+        }})</van-button>
+      <van-button type="success" class="btn-3" @click="completionStage(1)">赢单</van-button>
+      <van-button type="warning" class="btn-3" @click="completionStage(2)">输单</van-button>
+      <van-button type="danger" class="btn-3" @click="completionStage(3)">无效</van-button>
+    </div>
+
+    <van-dialog v-model:show="showDisold" :title="`${transitionalData.title}原因`" show-cancel-button @confirm="stageCompletion()">
+      <van-field v-model="transitionalData.reason" rows="4" autosize type="textarea" :placeholder="`请输入${transitionalData.title}原因`"
+        show-word-limit />
+    </van-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch, computed } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import { ACQUISITION_STAGE, GET_BUSINESS_OPPORTUNITY_DETAILS, PROMOTION_STAGE, STAGE_NOTES } from '@hooks/useApi'
+import requests from "@common/requests";
+import useShowToast from '@hooks/useToast'
+import useInfoStore from '@store/useInfoStore'
+import useRouterStore from "@store/useRouterStore.js";
+
+const { toastSuccess, toastFail, toastText, toastLoading } = useShowToast()
+
+const emit = defineEmits();
+const props = defineProps({
+  info: {
+    type: Object,
+    required: true,
+    default: () => ({})
+  }
+})
+
+const infoData = ref({})
+const stageList = ref([])
+const stageIndex = ref(0)
+const stageSetting = ref([])
+const transitionalData = ref({})
+const showDisold = ref(false)
+const displayButtonOrNot = ref(true)
+
+watch(() => props.info, (val) => {
+  getBusinessOpportunityDetails(val.id)
+})
+
+function showStageText(index) {
+  const row = stageList.value[index]
+  const idx = stageIndex.value
+  const flag = displayButtonOrNot.value
+  const idList = stageSetting.value.map(item => item.id)
+
+  if(!flag && index == idx) {
+    return row.name
+  }
+
+  return idx > index ? '已完成' : idx == index ? '进行中' : '未开始'
+}
+
+function showStageClass (index) {
+  const idx = stageIndex.value
+  const flag = displayButtonOrNot.value
+  const row = stageList.value[index]
+  const idList = stageSetting.value.map(item => item.id)
+  if(idx > index) {
+    return 'ended'
+  }
+  if(idx == index && flag) {
+    return 'haveInHand'
+  }
+
+  if(flag && idx < index) {
+    return 'notStartedYet'
+  }
+  
+  if(!flag && idList.includes(row.id)) {
+    return row.name == '赢单' ? 'winningOrders' : row.name == '输单' ? 'singleLoss'  : 'invalid'
+  }
+
+  return ''
+}
+
+function completionStage(type) {
+  const typeObj = { 1: '赢单', 2: '输单', 3: '无效' }
+  const row = stageSetting.value.find(item => item.name === typeObj[type])
+  transitionalData.value = {
+    id: infoData.value.id,
+    stageId: row.id,
+    stageValue: row.name,
+    title: `${typeObj[type]}`,
+    reason: ''
+  }
+
+  if (type != 1) {
+    showDisold.value = true
+  } else {
+    stageCompletion(false)
+  }
+}
+
+function enterReason() {
+  const { id, reason } = transitionalData.value
+  requests.post(PROMOTION_STAGE, {
+    id, reason
+  }).then((res) => {
+    getBusinessOpportunityDetails(id)
+  }).catch((err) => {
+    toastFail(err.msg)
+  })
+}
+
+function stageCompletion(flag = true) {
+  const { id, stageId, stageValue } = transitionalData.value
+  requests.post(PROMOTION_STAGE, {
+    id, stageId, stageValue
+  }).then((res) => {
+    toastSuccess('操作成功')
+    if(flag) {
+      enterReason()
+    } else {
+      emit('chnage');
+      getBusinessOpportunityDetails(id)
+    }
+  }).catch((err) => {
+    toastFail(err.msg)
+  })
+}
+
+function promotionStage() {
+  const item = stageList.value[+stageIndex.value + 1]
+  toastLoading('推进中...', 0)
+  requests.post(PROMOTION_STAGE, {
+    id: infoData.value.id,
+    stageId: item.id,
+    stageValue: item.name
+  }).then((res) => {
+    toastSuccess('推进成功')
+    emit('chnage');
+    getBusinessOpportunityDetails(infoData.value.id)
+  }).catch((err) => {
+    toastFail(err.msg)
+  })
+}
+
+function acquisitionStage(row) {
+  requests.post(ACQUISITION_STAGE, {}).then((res) => {
+    stageIndex.value = (res.data || []).findIndex(item => item.id === row.stageId)
+    stageList.value = (res.data || []).filter(item => !(['赢单', '输单', '无效'].includes(item.name)))
+    stageSetting.value = (res.data || []).filter(item => (['赢单', '输单', '无效'].includes(item.name)))
+    const val = (stageSetting.value || []).filter(item => item.id === row.stageId)
+    displayButtonOrNot.value = true
+    if(val.length > 0) {
+      stageList.value.push(val[0])
+      stageIndex.value = stageList.value.length - 1
+      displayButtonOrNot.value = false
+    }
+  })
+}
+
+function getBusinessOpportunityDetails(id) {
+  requests.post(GET_BUSINESS_OPPORTUNITY_DETAILS, { id }).then((res) => {
+    infoData.value = res.data || {}
+    acquisitionStage(res.data)
+  })
+}
+
+function initializeData() {
+  getBusinessOpportunityDetails(props.info.id)
+}
+
+useLifecycle({
+  init: () => {
+    initializeData()
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+.verticalArrangement {
+  writing-mode: vertical-rl; 
+  border-radius: 6px;
+  background: #D9D9D9;
+  font-size: 12px;
+  line-height: 1.2;
+}
+
+.ended {
+  background: #D9D9D9;
+  color: #858585;
+}
+.haveInHand {
+  background: #075985;
+  color: #fff;
+}
+.notStartedYet {
+  background: #E6E6E6;
+  color: #fff;
+}
+.winningOrders {
+  background: #07C160;
+  color: #fff;
+}
+.singleLoss {
+  background: #FF976A;
+  color: #fff;
+}
+.invalid {
+  background: #EE0A24;
+  color: #fff;
+}
+.btn-3 {
+  margin-bottom: 10px;
+}
+
+::v-deep(.van-steps) {
+  background: none;
+}
+</style>

+ 23 - 3
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue

@@ -1,9 +1,11 @@
 <template>
   <div class="w-full h-full">
     <van-tabs v-model:active="tabActive">
-      <van-tab title="商机阶段" name="商机阶段">商机阶段 1</van-tab>
+      <van-tab title="商机阶段" name="商机阶段">
+        <BusinessOpportunityStage :info="infoData" @chnage="refreshData()" />
+      </van-tab>
       <van-tab title="商机信息" name="商机信息">
-        <BusinessInfo :info="info" />
+        <BusinessInfo :info="infoData" />
       </van-tab>
       <van-tab title="相关产品" name="相关产品">
         <RelatedProducts :infoList="relatedProductsList" />
@@ -23,6 +25,7 @@ import requests from "@common/requests";
 import RelatedProducts from '../product/relatedProducts.vue';
 import BusinessInfo from './businessInfo.vue';
 import RelatedTasks from '../tasks/relatedTasks.vue';
+import BusinessOpportunityStage from './businessOpportunityStage.vue';
 
 const props = defineProps({
   info: {
@@ -34,27 +37,44 @@ const props = defineProps({
 const tabActive = ref('商机信息');
 const relatedProductsList = ref([]);
 const relatedTasksList = ref([]);
+const infoData = ref(props.info);
+const timeout = ref(null);
 
 watch(() => props.info, (newValue) => {
   tabActive.value = '商机信息';
   processingData(newValue.id)
 })
 
+function refreshData() {
+  processingData(infoData.value.id)
+}
+
 function getBusinessOpportunityDetails(id) {
   requests.post(GET_BUSINESS_OPPORTUNITY_DETAILS, { id }).then(({ data }) => {
+    infoData.value = data
     relatedProductsList.value = data.businessItemProducts || []
     relatedTasksList.value = data.taskList || []
   })
 }
 
 function processingData(id) {
-  getBusinessOpportunityDetails(id)
+  clearTimeout(timeout.value);
+  timeout.value = setTimeout(() => {
+    getBusinessOpportunityDetails(id)
+  }, 100);
 }
 
 useLifecycle({
   init: () => {
     tabActive.value = '商机信息';
     processingData(props.info.id)
+  },
+  load: () => {
+    tabActive.value = '商机信息';
+    processingData(props.info.id)
+  },
+  unLoad: () => {
+    clearTimeout(timeout.value);
   }
 });
 </script>

+ 9 - 3
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/relatedBusinessOpportunities.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="flex flex-col h-full overflow-y-auto">
-    <div class="info h-full cellnormall" v-if="infoList.length">
-      <div v-for="(item, index) in infoList">
+    <div class="info h-full cellnormall" v-if="infoListData.length">
+      <div v-for="(item, index) in infoListData">
         <FoldingPanel :title="`相关商机(${item.name})`">
           <template #foldContainer>
             <div class="p-5 bg-white ">
@@ -32,7 +32,7 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import { fixedFieldPriority, fixedFieldTaskStatus } from "@utility/defaultData.js"
 import FoldingPanel from '@components/common/foldingPanel.vue';
@@ -45,6 +45,12 @@ const props = defineProps({
   }
 })
 
+const infoListData = ref(props.infoList);
+
+watch(() => props.infoList, (newValue) => {
+  infoListData.value = newValue;
+})
+
 useLifecycle({
   load: () => {
     // 添加加载逻辑

+ 49 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/contactsInfo.vue

@@ -16,6 +16,8 @@
     </div>
     <div class="bottomButton">
       <van-button type="warning" class="w-full block" @click="showDialogCli()">转移联系人</van-button>
+      <van-button type="default" class="w-full block" v-permission="[routingInformation.jurisdiction.edit]" @click="jumpEdit()">编辑联系人</van-button>
+      <van-button type="danger" class="w-full block" v-permission="[routingInformation.jurisdiction.delete]" @click="deleteRow()">删除联系人</van-button>
     </div>
 
     <!-- 转移弹窗 -->
@@ -38,13 +40,18 @@
 
 <script setup>
 import { ref } from 'vue';
+import { showConfirmDialog } from 'vant';
 import { useLifecycle } from '@hooks/useCommon.js';
 import { TRANSFER_CONTACT_PERSON } from '@hooks/useApi'
 import requests from "@common/requests";
 import useShowToast from '@hooks/useToast'
 import useInfoStore from '@store/useInfoStore'
 import useRouterStore from "@store/useRouterStore.js";
+import { routingInfos } from "@utility/generalVariables"
+import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
+import useFixedData from "@store/useFixedData.js"
 
+const fixedData = useFixedData()
 const router = useRouterStore()
 const userInfo = useInfoStore()
 const { toastSuccess, toastFail, toastText } = useShowToast()
@@ -56,10 +63,52 @@ const props = defineProps({
   }
 })
 
+const routingInformation = routingInfos['contacts']
 const showDialog = ref(false);
 const showSelect = ref(false);
 const dialogSelection = ref({});
 
+function deleteRow() {
+  const { name = '', searchFiled = {}, deteleFiled = '' } = routingInformation
+  const row = props.info
+  const foemVal = { [routingInformation.key == 'tasks' ? 'taskIds' : 'ids']: row.id }
+  showConfirmDialog({
+    title: `删除${name}`,
+    message: `确定删除【${row[searchFiled?.search]}】${name}吗?`,
+  }).then(() => {
+    requests.post(deteleFiled, { ...foemVal }).then((res) => {
+      toastSuccess('删除成功')
+      router.navigateBack({
+        success: () => {
+          router.emit('moduleListDetailParameter', {
+            row: JSON.stringify(routingInformation)
+          })
+        }
+      })
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
+}
+
+function jumpEdit() {
+  const formJson = fixedData.formJson[routingInformation.key] || []
+  const formList = resetListData(formJson?.list)
+  const filedObj = getListFieldKey(formList, props.info)
+  const formVal = { ...filedObj, id: props.info.id }
+
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(routingInformation),
+        filedValue: JSON.stringify(formVal)
+      })
+    }
+  })
+}
+
+
 function confirmTransfer() {
   if (!dialogSelection.value.label) {
     return toastText('请选择要转移的人员')

+ 15 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/detail.vue

@@ -2,7 +2,7 @@
   <div class="w-full h-full">
     <van-tabs v-model:active="tabActive">
       <van-tab title="联系人信息" name="联系人信息">
-        <ContactsInfo :info="info" />
+        <ContactsInfo :info="infoData" />
       </van-tab>
       <van-tab title="相关任务" name="相关任务">
         <RelatedTasks :infoList="relatedTasksList" />
@@ -33,6 +33,8 @@ const props = defineProps({
 const tabActive = ref('联系人信息');
 const relatedBusinessOpportunitiesList = ref([]);
 const relatedTasksList = ref([]);
+const infoData = ref(props.info);
+const timeout = ref(null);
 
 watch(() => props.info, (newValue) => {
   tabActive.value = '联系人信息';
@@ -41,19 +43,30 @@ watch(() => props.info, (newValue) => {
 
 function getDetailedData(id) {
   requests.post(GET_CONTACT_DETAILS, { id }).then(({ data }) => {
+    infoData.value = data || {}
     relatedBusinessOpportunitiesList.value = data.businessOpportunityList || []
     relatedTasksList.value = data.taskList || []
   })
 }
 
 function processingData(id) {
-  getDetailedData(id)
+  clearTimeout(timeout.value);
+  timeout.value = setTimeout(() => {
+    getDetailedData(id)
+  }, 100);
 }
 
 useLifecycle({
   init: () => {
     tabActive.value = '联系人信息';
     processingData(props.info.id || props.info.customId)
+  },
+  load: () => {
+    tabActive.value = '线索信息';
+    processingData(props.info.id || props.info.customId)
+  },
+  unload: () => {
+    clearTimeout(timeout.value)
   }
 });
 </script>

+ 47 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/customerInfo.vue

@@ -20,6 +20,8 @@
         @click="showDialogCli()">转移客户</van-button>
       <van-button type="primary" class="w-full block" v-if="!info.inchargerName"
         @click="claimAndClaim()">认领客户</van-button>
+      <van-button type="default" class="w-full block" v-permission="[routingInformation.jurisdiction.edit]" @click="jumpEdit()">编辑客户</van-button>
+      <van-button type="danger" class="w-full block" v-permission="[routingInformation.jurisdiction.delete]" @click="deleteRow()">删除客户</van-button>
     </div>
 
     <!-- 转移弹窗 -->
@@ -49,7 +51,11 @@ import requests from "@common/requests";
 import useShowToast from '@hooks/useToast'
 import useInfoStore from '@store/useInfoStore'
 import useRouterStore from "@store/useRouterStore.js";
+import { routingInfos } from "@utility/generalVariables"
+import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
+import useFixedData from "@store/useFixedData.js"
 
+const fixedData = useFixedData()
 const router = useRouterStore()
 const userInfo = useInfoStore()
 const { toastSuccess, toastFail, toastText } = useShowToast()
@@ -64,6 +70,47 @@ const props = defineProps({
 const showDialog = ref(false);
 const showSelect = ref(false);
 const dialogSelection = ref({});
+const routingInformation = routingInfos['customer']
+
+function deleteRow() {
+  const { name = '', searchFiled = {}, deteleFiled = '' } = routingInformation
+  const row = props.info
+  const foemVal = { [routingInformation.key == 'tasks' ? 'taskIds' : 'ids']: row.id }
+  showConfirmDialog({
+    title: `删除${name}`,
+    message: `确定删除【${row[searchFiled?.search]}】${name}吗?`,
+  }).then(() => {
+    requests.post(deteleFiled, { ...foemVal }).then((res) => {
+      toastSuccess('删除成功')
+      router.navigateBack({
+        success: () => {
+          router.emit('moduleListDetailParameter', {
+            row: JSON.stringify(routingInformation)
+          })
+        }
+      })
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
+}
+
+function jumpEdit() {
+  const formJson = fixedData.formJson[routingInformation.key] || []
+  const formList = resetListData(formJson?.list)
+  const filedObj = getListFieldKey(formList, props.info)
+  const formVal = { ...filedObj, id: props.info.id }
+
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(routingInformation),
+        filedValue: JSON.stringify(formVal)
+      })
+    }
+  })
+}
 
 function confirmTransfer() {
   if (!dialogSelection.value.label) {

+ 15 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/detail.vue

@@ -2,7 +2,7 @@
   <div class="w-full h-full">
     <van-tabs v-model:active="tabActive" swipe-threshold="3">
       <van-tab title="客户信息" name="客户信息">
-        <CustomerInfo :info="info" />
+        <CustomerInfo :info="infoData" />
       </van-tab>
       <van-tab title="相关任务" name="相关任务">
         <RelatedTasks :infoList="relatedTasksList" />
@@ -43,6 +43,8 @@ const relatedTasksList = ref([]);
 const relatedContactsList = ref([]);
 const relatedBusinessOpportunitiesList = ref([]);
 const relatedSalesOrdersList = ref([]);
+const infoData = ref(props.info)
+const timeout = ref(null);
 
 watch(() => props.info, (newValue) => {
   tabActive.value = '客户信息';
@@ -51,6 +53,7 @@ watch(() => props.info, (newValue) => {
 
 function getDetailedData(id) {
   requests.post(OBTAIN_CUSTOMER_DETAILS, { id }).then(({ data }) => {
+    infoData.value = data || {}
     relatedTasksList.value = data.tasks || []
     relatedContactsList.value = data.contacts || []
     relatedBusinessOpportunitiesList.value = data.businessOpportunitys || []
@@ -59,13 +62,23 @@ function getDetailedData(id) {
 }
 
 function processingData(id) {
-  getDetailedData(id)
+  clearTimeout(timeout.value);
+  timeout.value = setTimeout(() => {
+    getDetailedData(id)
+  }, 100);
 }
 
 useLifecycle({
   init: () => {
     tabActive.value = '客户信息';
     processingData(props.info.id)
+  },
+  load: () => {
+    tabActive.value = '客户信息';
+    processingData(props.info.id)
+  },
+  unload: () => {
+    clearTimeout(timeout.value)
   }
 });
 </script>

+ 26 - 4
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/detail.vue

@@ -1,6 +1,9 @@
 <template>
-  <div class="w-full h-full">
-    任务详情
+  <div class="w-full h-full flex-col">
+    <div class="bg-white info flex-1 overflow-y-auto">
+      <van-cell title="任务名称" :value="''" />
+      <van-cell title="优先级" :value="''" />
+    </div>
   </div>
 </template>
 
@@ -8,13 +11,32 @@
 import { ref } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 
+const props = defineProps({
+  info: {
+    type: Object,
+    required: true,
+    default: () => ({})
+  }
+})
+
+function getTaskDetails() {
+  
+}
+
+function initializeData() {
+
+}
+
 useLifecycle({
   load: () => {
-    // 添加加载逻辑
+    initializeData()
+  },
+  init: () => {
+    initializeData()
   }
 });
 </script>
 
 <style lang='scss' scoped>
-  /* 样式代码 */
+
 </style>

+ 15 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/detail.vue

@@ -2,7 +2,7 @@
   <div class="w-full h-full">
     <van-tabs v-model:active="tabActive">
       <van-tab title="线索信息">
-        <ThreadInfo :info="info" />
+        <ThreadInfo :info="infoData" />
       </van-tab>
       <van-tab title="相关任务" name="相关任务">
         <RelatedTasks :infoList="relatedTasksList" :key="componentKey" />
@@ -29,6 +29,8 @@ const props = defineProps({
 const tabActive = ref('线索信息');
 const componentKey = ref(1);
 const relatedTasksList = ref([]);
+const infoData = ref(props.info);
+const timeout = ref(null);
 
 watch(() => props.info, (newValue) => {
   tabActive.value = '线索信息';
@@ -37,6 +39,7 @@ watch(() => props.info, (newValue) => {
 
 function getDetails(id) {
   requests.post(GET_CLUE_DETAILS, { id }).then(({ data }) => {
+    infoData.value = data || {}
     relatedTasksList.value = data.taskList || []
   }).finally(() => {
     setTimeout(() => {
@@ -46,13 +49,23 @@ function getDetails(id) {
 }
 
 function processingData(id) {
-  getDetails(id)
+  clearTimeout(timeout.value);
+  timeout.value = setTimeout(() => {
+    getDetails(id)
+  }, 100);
 }
 
 useLifecycle({
   init: () => {
     tabActive.value = '线索信息';
     processingData(props.info.id)
+  },
+  load: () => {
+    tabActive.value = '线索信息';
+    processingData(props.info.id)
+  },
+  unload: () => {
+    clearTimeout(timeout.value)
   }
 });
 </script>

+ 47 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/threadInfo.vue

@@ -18,6 +18,8 @@
     <div class="bottomButton">
       <van-button type="warning" class="w-full block" v-if="info.inchargerName"  @click="showDialogCli()">转移线索</van-button>
       <van-button type="primary" class="w-full block" v-if="!info.inchargerName" @click="claimAndClaim()">认领线索</van-button>
+      <van-button type="default" class="w-full block" v-permission="[routingInformation.jurisdiction.edit]" @click="jumpEdit()">编辑线索</van-button>
+      <van-button type="danger" class="w-full block" v-permission="[routingInformation.jurisdiction.delete]" @click="deleteRow()">删除线索</van-button>
     </div>
 
     <!-- 转移弹窗 -->
@@ -47,7 +49,11 @@ import requests from "@common/requests";
 import useShowToast from '@hooks/useToast'
 import useInfoStore from '@store/useInfoStore'
 import useRouterStore from "@store/useRouterStore.js";
+import { routingInfos } from "@utility/generalVariables"
+import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
+import useFixedData from "@store/useFixedData.js"
 
+const fixedData = useFixedData()
 const router = useRouterStore()
 const userInfo = useInfoStore()
 const { toastSuccess, toastFail, toastText } = useShowToast()
@@ -59,10 +65,51 @@ const props = defineProps({
   }
 })
 
+const routingInformation = routingInfos['thread']
 const showDialog = ref(false);
 const showSelect = ref(false);
 const dialogSelection = ref({});
 
+function deleteRow() {
+  const { name = '', searchFiled = {}, deteleFiled = '' } = routingInformation
+  const row = props.info
+  const foemVal = { [routingInformation.key == 'tasks' ? 'taskIds' : 'ids']: row.id }
+  showConfirmDialog({
+    title: `删除${name}`,
+    message: `确定删除【${row[searchFiled?.search]}】${name}吗?`,
+  }).then(() => {
+    requests.post(deteleFiled, { ...foemVal }).then((res) => {
+      toastSuccess('删除成功')
+      router.navigateBack({
+        success: () => {
+          router.emit('moduleListDetailParameter', {
+            row: JSON.stringify(routingInformation)
+          })
+        }
+      })
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
+}
+
+function jumpEdit() {
+  const formJson = fixedData.formJson[routingInformation.key] || []
+  const formList = resetListData(formJson?.list)
+  const filedObj = getListFieldKey(formList, props.info)
+  const formVal = { ...filedObj, id: props.info.id }
+
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(routingInformation),
+        filedValue: JSON.stringify(formVal)
+      })
+    }
+  })
+}
+
 function listReloadData() {
   router.navigateBack({
     success: () => {

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/workbench.vue

@@ -317,7 +317,7 @@ function processForms() {
 
 function getVisitorPlan() {
   requests.post(GET_VISITOR_PLAN, { 
-    calenderDate: dayjs(dateConditions.value).format('YYYY-MM') 
+    calenderDate: dayjs(dateConditions.value).format('YYYY-MM-DD') 
   }).then((res) => {
     visitorProgramList.value = res.data || []
   })

+ 11 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java

@@ -465,6 +465,17 @@ public class ProjectController {
         return projectService.getProjectTask(pageIndex, pageSize, projectId,groupId, request,taskType);
     }
 
+
+    @RequestMapping("/getProjectTaskPlanAndRealCost")
+    public HttpRespMsg getProjectTaskPlanAndRealCost(@RequestParam Integer pageIndex, @RequestParam Integer pageSize, Integer projectId,Integer taskType) {
+        return projectService.getProjectTaskPlanAndRealCost(pageIndex, pageSize, projectId,request,taskType);
+    }
+
+    @RequestMapping("/exportProjectTaskPlanAndRealCost")
+    public HttpRespMsg exportProjectTaskPlanAndRealCost(Integer projectId,Integer taskType) {
+        return projectService.exportProjectTaskPlanAndRealCost(projectId,request,taskType);
+    }
+
     //分页查询项目各个阶段的汇总工时成本
     @RequestMapping("/getProjectStagesCost")
     public HttpRespMsg getProjectStagesCost(@RequestParam Integer pageIndex, @RequestParam Integer pageSize, Integer projectId,String stageNames,String startDate,String endDate) {

+ 14 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskExecutorController.java

@@ -1,10 +1,15 @@
 package com.management.platform.controller;
 
 
+import com.management.platform.entity.TaskExecutor;
+import com.management.platform.service.TaskExecutorService;
+import com.management.platform.util.HttpRespMsg;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
+
 /**
  * <p>
  *  前端控制器
@@ -16,6 +21,15 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RequestMapping("/task-executor")
 public class TaskExecutorController {
+    @Resource
+    private TaskExecutorService taskExecutorService;
 
+    @RequestMapping("modify")
+    public HttpRespMsg modify(TaskExecutor taskExecutor) {
+        HttpRespMsg msg = new HttpRespMsg();
+        taskExecutorService.updateById(taskExecutor);
+        msg.data = taskExecutor;
+        return msg;
+    }
 }
 

+ 5 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserController.java

@@ -1022,5 +1022,10 @@ public class UserController {
         return msg;
     }
 
+    @RequestMapping("/unbindCorpWX")
+    public HttpRespMsg unbindCorpWX(String userId){
+        return userService.unbindCorpWX(userId, request);
+    }
+
 }
 

+ 8 - 8
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserCorpwxTimeController.java

@@ -231,14 +231,14 @@ public class UserCorpwxTimeController {
         list = userCorpwxTimeMapper.getUserDataList(user.getCompanyId(), startDate, endDate, null, user.getId());
         //工作日处理,排除常规周末和法定节假日
         DateTimeFormatter dtf =  DateTimeFormatter.ofPattern("yyyy/MM/dd");
-        list = list.stream().filter(time->{
-            String date = (String)time.get("createDate");
-            if (WorkDayCalculateUtils.isWorkDay(LocalDate.parse(date, dtf))) {
-                return true;
-            } else {
-                return false;
-            }
-        }).collect(Collectors.toList());
+//        list = list.stream().filter(time->{
+//            String date = (String)time.get("createDate");
+//            if (WorkDayCalculateUtils.isWorkDay(LocalDate.parse(date, dtf))) {
+//                return true;
+//            } else {
+//                return false;
+//            }
+//        }).collect(Collectors.toList());
         DateTimeFormatter standFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         HashMap item = new HashMap();
         item.put("list", list);

+ 41 - 32
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/Task.java

@@ -1,9 +1,14 @@
 package com.management.platform.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
+import java.time.LocalDate;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+import java.util.List;
+
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -21,7 +26,7 @@ import java.util.List;
  * </p>
  *
  * @author Seyason
- * @since 2023-12-06
+ * @since 2025-01-04
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -112,18 +117,6 @@ public class Task extends Model<Task> {
     @TableField("project_id")
     private Integer projectId;
 
-    /**
-     * 项目负责人id
-     */
-    @TableField(exist = false)
-    private String projectInchargerId;
-
-    /**
-     * 分组负责人id
-     */
-    @TableField(exist = false)
-    private String groupInchargerId;
-
     /**
      * 当前阶段id
      */
@@ -178,7 +171,6 @@ public class Task extends Model<Task> {
     @TableField("parent_tname")
     private String parentTname;
 
-
     /**
      * 完成日期
      */
@@ -214,22 +206,6 @@ public class Task extends Model<Task> {
     @TableField("meeting_id")
     private String meetingId;
 
-    @TableField(exist = false)
-    private List<TaskExecutor> executorList;
-
-    @TableField(exist = false)
-    private String executorListStr;
-
-    @TableField(exist = false)
-    private List<MilestoneTaskRef> refTaskList;
-    @TableField(exist = false)
-    private Integer finishRefTaskCount;
-    @TableField(exist = false)
-    private String departmentName;
-
-    @TableField(exist = false)
-    private boolean canAddTask;
-
     /**
      * 前置任务ids,多个逗号隔开
      */
@@ -284,6 +260,38 @@ public class Task extends Model<Task> {
 //    @TableField("file_reject_reason")
 //    private String fileRejectReason;
 
+    /**
+     * 任务预估成本
+     */
+    @TableField("plan_cost")
+    private Integer planCost;
+    /**
+     * 项目负责人id
+     */
+    @TableField(exist = false)
+    private String projectInchargerId;
+
+    /**
+     * 分组负责人id
+     */
+    @TableField(exist = false)
+    private String groupInchargerId;
+    @TableField(exist = false)
+    private List<TaskExecutor> executorList;
+
+    @TableField(exist = false)
+    private String executorListStr;
+
+    @TableField(exist = false)
+    private List<MilestoneTaskRef> refTaskList;
+    @TableField(exist = false)
+    private Integer finishRefTaskCount;
+    @TableField(exist = false)
+    private String departmentName;
+
+    @TableField(exist = false)
+    private boolean canAddTask;
+
     @TableField(exist = false)
     private String finalChargeStatusText;
 
@@ -297,6 +305,7 @@ public class Task extends Model<Task> {
      * 任务的外置状态显示 1审核中[待审核+(审核通过)] 2驳回[存在驳回文件]  3审核通过[全部审核通过]
      * */
     private int fileChargeStatus;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

+ 12 - 5
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/TaskExecutor.java

@@ -5,21 +5,20 @@ import com.baomidou.mybatisplus.extension.activerecord.Model;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableField;
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * <p>
  * 
  * </p>
  *
  * @author Seyason
- * @since 2023-12-13
+ * @since 2025-01-04
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -67,6 +66,13 @@ public class TaskExecutor extends Model<TaskExecutor> {
     @TableField("service_id")
     private Integer serviceId;
 
+    /**
+     * 任务预估成本开启的情况下对应的实际研发费用,可手动修改
+     */
+    @TableField("real_cost")
+    private Integer realCost;
+
+
     @TableField(exist = false)
     private String serviceName;
 
@@ -104,4 +110,5 @@ public class TaskExecutor extends Model<TaskExecutor> {
         executor.setExecutorName(task.getExecutorName());
         executor.setExecutorColor(task.getExecutorColor());
     }
+
 }

+ 21 - 16
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/TimeType.java

@@ -1,23 +1,23 @@
 package com.management.platform.entity;
 
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
+import java.math.BigDecimal;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+import java.util.List;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.List;
-
 /**
  * <p>
  * 
  * </p>
  *
  * @author Seyason
- * @since 2024-10-21
+ * @since 2025-01-04
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -297,7 +297,7 @@ public class TimeType extends Model<TimeType> {
     private Integer mainProjectState;
 
     /**
-     * 日报的审核类型, 0-项目审核人审核,1-分组负责人审核,2-先分组负责人审核再项目负责人(PM)审核;3-员工自由选择审批人 4-项目所属BU审核 5-直属审核人或部门负责人审核,6-直属或部门负责人审核->项目日报审核人审核,7-项目和部门并行审核;8-项目设置复审人;9-分组负责人审核->项目日报审核人审核
+     * 日报的审核类型, 0-项目审核人审核,1-分组负责人审核,2-先分组负责人审核再项目负责人(PM)审核;3-员工自由选择审批人 4-项目所属BU审核 5-直属审核人或部门负责人审核,6-直属或部门负责人审核->项目日报审核人审核,7-项目和部门并行审核;8-项目设置复审人;9-分组负责人审核->项目日报审核人审核;10-普通员工到项目经理,项目经理到单独审核人
      */
     @TableField("report_audit_type")
     private Integer reportAuditType;
@@ -608,20 +608,12 @@ public class TimeType extends Model<TimeType> {
     @TableField("only_show_percent")
     private Integer onlyShowPercent;
 
-    @TableField(exist = false)
-    private List<User> userList;
-    @TableField(exist = false)
-    private List<TimeAutoExclude> excludeTimeList;
-    @TableField(exist = false)
-    private Integer saasSyncContact;
-
     /**
      * 是否校验考勤数据的加班时长
      */
     @TableField("verify_card_overtime")
     private Integer verifyCardOvertime;
 
-
     /**
      * 驳回日报原因是否必填
      */
@@ -634,6 +626,19 @@ public class TimeType extends Model<TimeType> {
     @TableField("task_file_charge")
     private Integer taskFileCharge;
 
+    /**
+     * 任务预估成本功能是否开启,0-不启用,1-启用
+     */
+    @TableField("task_plan_cost")
+    private Integer taskPlanCost;
+
+
+    @TableField(exist = false)
+    private List<User> userList;
+    @TableField(exist = false)
+    private List<TimeAutoExclude> excludeTimeList;
+    @TableField(exist = false)
+    private Integer saasSyncContact;
 
     @Override
     protected Serializable pkVal() {

+ 5 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/TaskMapper.java

@@ -72,6 +72,11 @@ public interface TaskMapper extends BaseMapper<Task> {
 
     int getTaskChargePageTotal(@Param("queryBO") QueryTaskChargePage queryBO, @Param("deptIds")List<Integer> branchDepartment);
 
+    List<Map<String,Object>> getTaskPlanRealData(Integer companyId, Integer pageStart, Integer pageSize, Integer projectId,Integer taskType,List<Integer> inchagerIds);
+
+    int getTaskPlanRealCount(Integer companyId, Integer projectId, Integer taskType, List<Integer> inchagerIds);
+
+
     List<TaskFileChargePageVO> getTaskFileChargePage(@Param("queryBO") QueryTaskChargePage queryBO, @Param("deptIds")List<Integer> branchDepartment);
 
     int getTaskFileChargePageTotal(@Param("queryBO") QueryTaskChargePage queryBO, @Param("deptIds")List<Integer> branchDepartment);

+ 2 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/UserMapper.java

@@ -57,4 +57,6 @@ public interface UserMapper extends BaseMapper<User> {
     List<User> getInActiveBewttenStartAndEndList(ArrayList<Integer> deptIds, String startDate, String endDate);
 
     void updateActiveByIds(List<String> array, int isActive);
+
+    void unbindCorpWX(String id);
 }

+ 4 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ProjectService.java

@@ -317,4 +317,8 @@ public interface ProjectService extends IService<Project> {
     HttpRespMsg getProjectEstimatedWorkNew(Integer pageIndex, Integer pageSize, Integer projectId, Integer type,Integer isWarn, HttpServletRequest request);
 
     HttpRespMsg exportProjectEstimatedWorkNew( Integer projectId, Integer type, Integer isWarn, HttpServletRequest request);
+
+    HttpRespMsg getProjectTaskPlanAndRealCost(Integer pageIndex, Integer pageSize, Integer projectId,  HttpServletRequest request, Integer taskType);
+
+    HttpRespMsg exportProjectTaskPlanAndRealCost(Integer projectId, HttpServletRequest request, Integer taskType);
 }

+ 2 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/UserService.java

@@ -106,4 +106,6 @@ public interface UserService extends IService<User> {
     HttpRespMsg deleteUserSalaryById(String id);
 
     HttpRespMsg getSimpleActiveUserListPage(Integer pageIndex, Integer pageSize, Integer departmentId, HttpServletRequest request, String keyword, String cursor, String userIds) throws Exception;
+
+    HttpRespMsg unbindCorpWX(String userId, HttpServletRequest request);
 }

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

@@ -872,6 +872,11 @@ public class LeaveSheetServiceImpl extends ServiceImpl<LeaveSheetMapper, LeaveSh
                 UserRestTimeVO restTimeUserVO = restTimeHourMap.get(tmpUserId);
                 if(null != restTimeUserVO){
                     BigDecimal leftTimeHour = tmpUserTimeVO.getOverTimeHour().subtract(restTimeUserVO.getRestTimeHour());
+                    //特殊处理
+                    if (tmpUserId.equals("8079159320355872768")) {
+                        //火石闪信,从2023年8月1日开始计算加班工时, 老员工(李晶亚)减去21.9天
+                        leftTimeHour = leftTimeHour.subtract(new BigDecimal(21.9*allDayHours));
+                    }
                     if(0 > leftTimeHour.compareTo(new BigDecimal(0))){
                         tmpUserTimeVO.setRestTimeHourStr("0.0");
                         tmpUserTimeVO.setRestTimeDayStr("0.0");
@@ -884,6 +889,7 @@ public class LeaveSheetServiceImpl extends ServiceImpl<LeaveSheetMapper, LeaveSh
                     tmpUserTimeVO.setRestTimeHourStr(tmpUserTimeVO.getOverTimeHour().setScale(1,BigDecimal.ROUND_HALF_UP).toString());
                     tmpUserTimeVO.setRestTimeDayStr(tmpUserTimeVO.getOverTimeHour().divide(new BigDecimal(allDayHours), 1, BigDecimal.ROUND_HALF_UP).toString());
                 }
+
                 resList.add(tmpUserTimeVO);
             }
             httpRespMsg.setData(resList);

+ 94 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java

@@ -1115,6 +1115,100 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
         }
     }
 
+    @Override
+    public HttpRespMsg getProjectTaskPlanAndRealCost(Integer pageIndex, Integer pageSize, Integer projectId, HttpServletRequest request, Integer taskType) {
+        User user = userMapper.selectById(request.getHeader("Token"));
+        Integer companyId = user.getCompanyId();
+        int pageStart = (pageIndex -1) * pageSize;
+        List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", companyId));
+        List<SysRichFunction> functionAllList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "全部项目任务报表");
+        List<SysRichFunction> functionInchargeList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "负责项目任务报表");
+        //判断查看权限
+        List<Integer> inchagerIds=null;
+        if(functionAllList.size()==0){
+            inchagerIds=new ArrayList<>();
+            if(functionInchargeList.size()>0){
+                List<Project> list = projectList.stream().filter(pl ->(pl.getInchargerId()==null?0:pl.getInchargerId()).equals(user.getId())).collect(Collectors.toList());
+                if(list!=null){
+                    List<Integer> collect = list.stream().map(li -> li.getId()).collect(Collectors.toList());
+                    inchagerIds.addAll(collect);
+                }
+            }else {
+                inchagerIds.add(-1);
+            }
+        }
+        int total = taskMapper.getTaskPlanRealCount(companyId, projectId,taskType,inchagerIds);
+        List<Map<String,Object>> projectTask = taskMapper.getTaskPlanRealData(companyId, pageStart, pageSize, projectId,taskType,inchagerIds);
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        Map<String, Object> map = new HashMap<>();
+        map.put("records", projectTask);
+        map.put("total", total);
+        httpRespMsg.data = map;
+        return httpRespMsg;
+    }
+
+    @Override
+    public HttpRespMsg exportProjectTaskPlanAndRealCost(Integer projectId, HttpServletRequest request, Integer taskType) {
+        User user = userMapper.selectById(request.getHeader("Token"));
+        Integer companyId = user.getCompanyId();
+        List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", companyId));
+        List<SysRichFunction> functionAllList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "全部项目任务报表");
+        List<SysRichFunction> functionInchargeList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "负责项目任务报表");
+        //判断查看权限
+        List<Integer> inchagerIds=null;
+        if(functionAllList.size()==0){
+            inchagerIds=new ArrayList<>();
+            if(functionInchargeList.size()>0){
+                List<Project> list = projectList.stream().filter(pl ->(pl.getInchargerId()==null?0:pl.getInchargerId()).equals(user.getId())).collect(Collectors.toList());
+                if(list!=null){
+                    List<Integer> collect = list.stream().map(li -> li.getId()).collect(Collectors.toList());
+                    inchagerIds.addAll(collect);
+                }
+            }else {
+                inchagerIds.add(-1);
+            }
+        }
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id", user.getCompanyId()));
+        CompanyDingding dingding = companyDingdingMapper.selectOne(new LambdaQueryWrapper<CompanyDingding>().eq(CompanyDingding::getCompanyId, user.getCompanyId()));
+        List<Map<String,Object>> projectTask = taskMapper.getTaskPlanRealData(companyId, null, null, projectId,taskType,inchagerIds);
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        List<ProjectVO> list = new ArrayList<>();
+        //String[] statusNames = {"进行中","已完成","已撤销"};
+        String[] statusNames = {MessageUtils.message("excel.onGoing"),MessageUtils.message("excel.complete"),MessageUtils.message("excel.revoke")};
+        //String[] typeList = {"任务","里程碑","风险"};
+        String[] typeList = {MessageUtils.message("excel.task"),MessageUtils.message("excel.milepost"),MessageUtils.message("excel.risk")};
+        List<List<String>> exportList = new ArrayList<>();
+        String[] titles = {"项目编号", "项目名称", "任务名称","预估研发费用(元)", "执行人","计划工时(h)", "实际工时(h)","实际研发费用(元)", "状态"};
+        exportList.add(Lists.list(titles));
+        DecimalFormat df = new DecimalFormat("0.00");
+        for (Map task : projectTask) {
+            List<String> data = new ArrayList<>();
+            data.add(task.get("projectCode") == null?"":task.get("projectCode").toString());
+            data.add(task.get("projectName") == null?"":task.get("projectName").toString());
+            data.add(task.get("name") != null?task.get("name").toString():"");
+            data.add(task.get("planCost") != null?df.format(Integer.parseInt(task.get("planCost").toString())):"0.00");
+            if((wxCorpInfo!=null&&wxCorpInfo.getSaasSyncContact()==1)||(dingding!=null&&dingding.getContactNeedTranslate()==1)){
+                data.add("$userName="+(task.get("executorName") == null?"":task.get("executorName")+"$"));
+            }else {
+                data.add(task.get("executorName") != null?task.get("executorName").toString():"");
+            }
+            data.add(task.get("planHours") != null?task.get("planHours").toString():"0.00");
+            data.add(task.get("realHours").toString());
+            data.add(task.get("realCost") != null?df.format(Integer.parseInt(task.get("realCost").toString())):"");
+            data.add(task.get("taskStatus")==null?"":statusNames[Integer.parseInt(task.get("taskStatus").toString())]);
+            exportList.add(data);
+        }
+        //String fileName = "项目任务报表_"+System.currentTimeMillis();
+        String fileName = "任务研发报表_"+System.currentTimeMillis();
+        try {
+            return excelExportService.exportGeneralExcelByTitleAndList(wxCorpInfo,dingding,fileName, exportList, path);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        httpRespMsg.data =  pathPrefix + fileName+".xlsx";
+        return httpRespMsg;
+    }
+
     public HttpRespMsg getProjectEstimatedWork(Integer pageIndex, Integer pageSize, Integer projectId, HttpServletRequest request) {
         HttpRespMsg httpRespMsg = new HttpRespMsg();
         //通过公司id获取该公司所有的项目列表

+ 71 - 67
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -6902,78 +6902,78 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
             allRangeUserList=allRangeUserList.stream().filter(at-> finalBranchDepartment.contains(at.getDepartmentId())).collect(Collectors.toList());
         }
         for (User curUser: allRangeUserList){
-                for (int i=0;i<=cnt; i++) {
-                    LocalDate date = localStart.plusDays(i);
-                    //入职日期以前 不需要添加 跳出
-                    if(curUser.getInductionDate()!=null&&date.isBefore(curUser.getInductionDate())){
-                        continue;
-                    }
-                    //当前日期在离职日期之后 不需要再添加 跳出
-                    if((curUser.getIsActive()==0&& curUser.getInactiveDate() != null &&date.isAfter(curUser.getInactiveDate()))) {
-                        continue;
-                    }
-                    //去掉非工作日
-                    Boolean workDay = timeTypeService.isWorkDay(companyId, date, timeType);
-                    if (!workDay){
-                        continue;
-                    }
-                    //去掉非工作日
-                    boolean workDay1 = WorkDayCalculateUtils.isWorkDay(date);
-                    if(!workDay1){
-                        continue;
-                    }
-                    //去掉设置了分摊比例的人员
-                    if (setPercentUserIdList.contains(curUser.getId())) {
-                        continue;
-                    }
-                    final String dateStr = dtf.format(date);
-                    if (!list.stream().anyMatch(item->item.get("id").equals(curUser.getId())&&sdf.format((java.sql.Date)item.get("createDate")).equals(dateStr))) {
-                        UserDailyWorkItem noRecord = new UserDailyWorkItem();
-                        noRecord.userId = curUser.getId();
-                        noRecord.departmentId = curUser.getDepartmentId();
-                        noRecord.jobNumber = curUser.getJobNumber();
-                        cpwxIds.add(curUser.getCorpwxUserid());
-                        List<Map> userCorpwxListOn = userCorpwxTimeMapList.stream().filter(mapList -> mapList.get("create_date").toString().equals(dateStr)
-                                && mapList.get("corpwx_userid").equals(curUser.getCorpwxUserid())).collect(Collectors.toList());
-                        noRecord.createDate = dtf.format(date);
-                        //默认为未填写
-                        //noRecord.status = "未填写";
-                        noRecord.status = MessageUtils.message("leave.notFill");
-                        if(!userCorpwxListOn.isEmpty()){
-                            //noRecord.status = "请假";
-                            noRecord.status = MessageUtils.message("leave.leave");
-                        }else{
-                            //检查是否是驳回或者待提交的
-                            if (deniedReportList.stream().anyMatch(deny->deny.getCreatorId().equals(curUser.getId()) && date.isEqual(deny.getCreateDate()))) {
-                                //noRecord.status = "已驳回";
-                                noRecord.status = MessageUtils.message("stages.reject");
-                            }
-                            if (waitingSubmitReportList.stream().anyMatch(deny->deny.getCreatorId().equals(curUser.getId()) && date.isEqual(deny.getCreateDate()))) {
-                                //noRecord.status = "待提交";
-                                noRecord.status = MessageUtils.message("stages.toBeSub");
-                            }
+            for (int i=0;i<=cnt; i++) {
+                LocalDate date = localStart.plusDays(i);
+                //入职日期以前 不需要添加 跳出
+                if(curUser.getInductionDate()!=null&&date.isBefore(curUser.getInductionDate())){
+                    continue;
+                }
+                //当前日期在离职日期之后 不需要再添加 跳出
+                if((curUser.getIsActive()==0&& curUser.getInactiveDate() != null &&date.isAfter(curUser.getInactiveDate()))) {
+                    continue;
+                }
+                //去掉非工作日
+                Boolean workDay = timeTypeService.isWorkDay(companyId, date, timeType);
+                if (!workDay){
+                    continue;
+                }
+                //去掉非工作日
+                boolean workDay1 = WorkDayCalculateUtils.isWorkDay(date);
+                if(!workDay1){
+                    continue;
+                }
+                //去掉设置了分摊比例的人员
+                if (setPercentUserIdList.contains(curUser.getId())) {
+                    continue;
+                }
+                final String dateStr = dtf.format(date);
+                if (!list.stream().anyMatch(item->item.get("id").equals(curUser.getId())&&sdf.format((java.sql.Date)item.get("createDate")).equals(dateStr))) {
+                    UserDailyWorkItem noRecord = new UserDailyWorkItem();
+                    noRecord.userId = curUser.getId();
+                    noRecord.departmentId = curUser.getDepartmentId();
+                    noRecord.jobNumber = curUser.getJobNumber();
+                    cpwxIds.add(curUser.getCorpwxUserid());
+                    List<Map> userCorpwxListOn = userCorpwxTimeMapList.stream().filter(mapList -> mapList.get("create_date").toString().equals(dateStr)
+                            && mapList.get("corpwx_userid").equals(curUser.getCorpwxUserid())).collect(Collectors.toList());
+                    noRecord.createDate = dtf.format(date);
+                    //默认为未填写
+                    //noRecord.status = "未填写";
+                    noRecord.status = MessageUtils.message("leave.notFill");
+                    if(!userCorpwxListOn.isEmpty()){
+                        //noRecord.status = "请假";
+                        noRecord.status = MessageUtils.message("leave.leave");
+                    }else{
+                        //检查是否是驳回或者待提交的
+                        if (deniedReportList.stream().anyMatch(deny->deny.getCreatorId().equals(curUser.getId()) && date.isEqual(deny.getCreateDate()))) {
+                            //noRecord.status = "已驳回";
+                            noRecord.status = MessageUtils.message("stages.reject");
+                        }
+                        if (waitingSubmitReportList.stream().anyMatch(deny->deny.getCreatorId().equals(curUser.getId()) && date.isEqual(deny.getCreateDate()))) {
+                            //noRecord.status = "待提交";
+                            noRecord.status = MessageUtils.message("stages.toBeSub");
                         }
+                    }
 
-                        if (curUser.getDepartmentId() != null && curUser.getDepartmentId() != 0) {
-                            noRecord.department = departmentList.stream().filter(d->d.getDepartmentId().equals(curUser.getDepartmentId())).findFirst().get().getDepartmentName();
-                        } else {
-                            //noRecord.department = "无";
-                            noRecord.department = MessageUtils.message("entry.none");
-                        }
-                        noRecord.name = curUser.getName();
-                        noRecord.corpwxUserId=curUser.getCorpwxUserid();
-                        noRecord.corpwxDeptId=curUser.getCorpwxDeptid();
-                        //请假的
-                        for (LeaveSheet leaveSheet : leaveSheetList) {
-                            if (leaveSheet.getOwnerId().equals(curUser.getId()) &&
-                                    (leaveSheet.getStartDate().isEqual(date) || leaveSheet.getEndDate().isEqual(date)
-                                        || (leaveSheet.getStartDate().isBefore(date) && leaveSheet.getEndDate().isAfter(date)))) {
-                                noRecord.status = MessageUtils.message("leave.leave");
-                            }
+                    if (curUser.getDepartmentId() != null && curUser.getDepartmentId() != 0) {
+                        noRecord.department = departmentList.stream().filter(d->d.getDepartmentId().equals(curUser.getDepartmentId())).findFirst().get().getDepartmentName();
+                    } else {
+                        //noRecord.department = "无";
+                        noRecord.department = MessageUtils.message("entry.none");
+                    }
+                    noRecord.name = curUser.getName();
+                    noRecord.corpwxUserId=curUser.getCorpwxUserid();
+                    noRecord.corpwxDeptId=curUser.getCorpwxDeptid();
+                    //请假的
+                    for (LeaveSheet leaveSheet : leaveSheetList) {
+                        if (leaveSheet.getOwnerId().equals(curUser.getId()) &&
+                                (leaveSheet.getStartDate().isEqual(date) || leaveSheet.getEndDate().isEqual(date)
+                                    || (leaveSheet.getStartDate().isBefore(date) && leaveSheet.getEndDate().isAfter(date)))) {
+                            noRecord.status = MessageUtils.message("leave.leave");
                         }
-                        noReportDataList.add(noRecord);
                     }
+                    noReportDataList.add(noRecord);
                 }
+            }
         }
         //考勤打卡时间
         if (cpwxIds.size() > 0) {
@@ -6987,6 +6987,10 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 }
             }
         }
+        //如果开启也企业微信考勤同步,没有企微考勤打卡记录的就不算未提交
+        if (timeType.getSyncCorpwxTime() == 1) {
+            noReportDataList = noReportDataList.stream().filter(item -> !(item.cardTime == null && item.status.equals(MessageUtils.message("leave.notFill")))).collect(Collectors.toList());
+        }
 
         //排序
         noReportDataList.sort(new Comparator<UserDailyWorkItem>() {

+ 7 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/UserServiceImpl.java

@@ -2523,6 +2523,13 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
         }
     }
 
+    @Override
+    public HttpRespMsg unbindCorpWX(String userId, HttpServletRequest request) {
+        HttpRespMsg respMsg = new HttpRespMsg();
+        userMapper.unbindCorpWX(userId);
+        return respMsg;
+    }
+
     @Override
     public HttpRespMsg getUserListByRole(HttpServletRequest request,String tableName) {
         HttpRespMsg httpRespMsg=new HttpRespMsg();

+ 40 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -1348,12 +1348,13 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                     ct.setAskLeaveTime(0.0);
                     ct.setWorkHours(0.0);
                     ct.setOutdoorTime(0.0);
+
                     if (isCrossDay) {
                         //直接用cardTime作为工作时长
                         ct.setName(name);
                         ct.setWorkHours(DateTimeUtil.getHoursFromDouble(ct.getCardTime()));
                     }
-                    //工作日或者非工作日有打卡,需要校正请假,外出的时长数据
+                    //工作日或者非工作日有打卡/加班,需要校正请假,外出的时长数据
                     else if (timeTypeService.isWorkDay(corpInfo.getCompanyId(), localDate, timeType) || sTime > 0 || eTime > 0) {
                         JSONArray holidayItems = jsonObject.getJSONArray("holiday_infos");
                         //开始时间和结束时间一样,说明下班没有打卡,需要提取请假的最晚时间作为下班打卡时间
@@ -1800,6 +1801,44 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                                 }
                             }
                         }
+                    } else {
+                        //以上不满足,存在非工作日仅有加班无打卡的情况,以加班申请时长为准
+                        JSONObject otInfo = jsonObject.getJSONObject("ot_info");
+                        if (otInfo != null && otInfo.getIntValue("ot_duration") > 0 && otInfo.getIntValue("ot_status") == 1) {
+                            double otTime = DateTimeUtil.getHoursFromSeconds(otInfo.getIntValue("ot_duration"));
+                            ct.setWorkHours(otTime);
+                            ct.setCardTime(0.0);
+                            ct.setAskLeaveTime(0.0);
+                            ct.setOutdoorTime(0.0);
+                            ct.setOtStatus(1);
+                            ct.setOtTime(otTime);
+                            JSONArray holidayItems = jsonObject.getJSONArray("holiday_infos");
+                            for (int t = 0; t < holidayItems.size(); t++) {
+                                JSONObject holiday = holidayItems.getJSONObject(t);
+                                JSONObject spTitle = holiday.getJSONObject("sp_title");
+                                JSONArray data = spTitle.getJSONArray("data");
+                                for (int m = 0; m < data.size(); m++) {
+                                    String leaveText = data.getJSONObject(m).getString("text");
+                                    if (leaveText.startsWith("加班")) {
+                                        //获取对应位置的加班时间段
+                                        String string = holiday.getJSONObject("sp_description").getJSONArray("data").getJSONObject(m).getString("text");
+                                        String[] s = string.split(" |\\~");
+                                        //获取到请假的开始时间和结束时间
+                                        String otStart = s[1];
+                                        String otEnd = s[s.length-1];
+                                        if (otStart.equals("上午")) {
+                                            otStart = baseMorningStart;
+                                        }
+                                        if (otEnd.equals("下午")) {
+                                            otEnd = baseAfternoonEnd;
+                                        }
+                                        ct.setStartTime(otStart);
+                                        ct.setEndTime(otEnd);
+                                        break;
+                                    }
+                                }
+                            }
+                        }
                     }
 
                     List<UserCorpwxTime> itemList = userCorpwxTimeMapper.selectList(new QueryWrapper<UserCorpwxTime>().eq("corpwx_userid", curUserid)

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TaskExecutorMapper.xml

@@ -12,11 +12,12 @@
         <result column="plan_hours" property="planHours" />
         <result column="project_id" property="projectId" />
         <result column="service_id" property="serviceId" />
+        <result column="real_cost" property="realCost" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, task_id, executor_id, executor_name, executor_color, plan_hours, project_id, service_id
+        id, task_id, executor_id, executor_name, executor_color, plan_hours, project_id, service_id, real_cost
     </sql>
 
 </mapper>

+ 138 - 84
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TaskMapper.xml

@@ -32,6 +32,16 @@
         <result column="meeting_id" property="meetingId" />
         <result column="ahead_tid" property="aheadTid" />
         <result column="sap_task_code" property="sapTaskCode" />
+        <result column="charge_one_id" property="chargeOneId" />
+        <result column="charge_one_status" property="chargeOneStatus" />
+        <result column="charge_one_time" property="chargeOneTime" />
+        <result column="charge_two_id" property="chargeTwoId" />
+        <result column="charge_two_status" property="chargeTwoStatus" />
+        <result column="charge_two_time" property="chargeTwoTime" />
+        <result column="final_charge_status" property="finalChargeStatus" />
+        <result column="charge_stage" property="chargeStage" />
+        <result column="file_reject_reason" property="fileRejectReason" />
+        <result column="plan_cost" property="planCost" />
     </resultMap>
     <resultMap id="timeResultMap" type="com.management.platform.entity.TimeTask" >
         <id column="id" property="id" />
@@ -109,7 +119,7 @@
     </resultMap>
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, name, task_desc, creater_id, creater_name, creator_color, executor_id, executor_name, executor_color, task_level, task_status, create_date, end_date, project_id, stages_id, company_id, indate, parent_tid, group_id, seq, plan_hours, task_type, parent_tname, finish_date, start_date, meeting_id, ahead_tid, sap_task_code
+        id, name, task_desc, creater_id, creater_name, creator_color, executor_id, executor_name, executor_color, task_level, task_status, create_date, end_date, project_id, stages_id, company_id, indate, parent_tid, group_id, seq, plan_hours, task_type, parent_tname, finish_date, start_date, meeting_id, ahead_tid, sap_task_code, charge_one_id, charge_one_status, charge_one_time, charge_two_id, charge_two_status, charge_two_time, final_charge_status, charge_stage, file_reject_reason, plan_cost
     </sql>
     <select id="simpleList" resultMap="BaseResultMap">
         select id, name, creater_id, creater_name, creator_color, executor_id, executor_name, executor_color, task_level, task_status, create_date, end_date, project_id, stages_id, company_id, indate, parent_tid, group_id, seq, plan_hours, task_type, parent_tname, finish_date, start_date
@@ -232,7 +242,51 @@
             </foreach>
         </if>
     </select>
-
+    <select id="getTaskPlanRealData" resultType="java.util.Map">
+        SELECT task.id,task.`name`,task_executor.id as teId, DATE_FORMAT(task.`end_date`, '%Y-%m-%d') as endDate, task.`plan_hours` as planHours,task.plan_cost as planCost,
+        (SELECT IFNULL(SUM(working_time),0) FROM report WHERE report.`task_id` = task.id AND report.`state` = 1) AS realHours,
+        task_executor.executor_id as executorId,task_executor.executor_name AS executorName,
+        IF(task_executor.real_cost = 0, task.plan_cost, task_executor.real_cost) AS realCost,
+        task.`task_status` as taskStatus, task.`task_type` as taskType,
+        project.`project_code` as projectCode, project.`project_name` as projectName
+        FROM task
+        LEFT JOIN project ON project.id = task.`project_id`
+        left join task_executor on task_executor.task_id=task.id
+        WHERE project.`company_id` = #{companyId}
+        <if test="projectId != null">
+            and task.project_id = #{projectId}
+        </if>
+        <if test="taskType!=null">
+            and task.task_type=#{taskType}
+        </if>
+        <if test="inchagerIds!=null and inchagerIds.size()>0">
+            and task.project_id in
+            <foreach collection="inchagerIds" open="(" separator="," close=")" item="item">
+                #{item}
+            </foreach>
+        </if>
+        ORDER BY project.is_public desc, project.id ASC, task.stages_id asc, task.seq asc
+        <if test="pageStart != null and pageSize != null">
+            limit #{pageStart}, #{pageSize}
+        </if>
+    </select>
+    <select id="getTaskPlanRealCount" resultType="java.lang.Integer">
+        SELECT count(1) as total FROM task LEFT JOIN project ON project.id = task.`project_id`
+        left join task_executor on task_executor.task_id=task.id
+        WHERE project.`company_id` = #{companyId}
+        <if test="projectId != null">
+            and task.project_id = #{projectId}
+        </if>
+        <if test="taskType!=null">
+            and task.task_type=#{taskType}
+        </if>
+        <if test="inchagerIds!=null and inchagerIds.size()>0">
+            and task.project_id in
+            <foreach collection="inchagerIds" open="(" separator="," close=")" item="item">
+                #{item}
+            </foreach>
+        </if>
+    </select>
     <select id="getTaskWithProjectName" resultMap="RichResultMap">
         SELECT task.id, task.project_id, task.name, task.executor_name, task.start_date, task.`end_date`,task.create_date, task.`creater_id`, task.`creater_name`, task.group_id,
         task.plan_hours, task.task_type, task.task_level, task.task_status, task.`finish_date`, project.`project_name`, stages.stages_name,department.department_name
@@ -306,7 +360,7 @@
             and u.id=#{userId}
         </if>
         <if test="deptId!=null">
-         and d.department_id=#{deptId}
+            and d.department_id=#{deptId}
         </if>
         <if test="list!=null and list.size()>0">
             and  d.department_id in
@@ -363,13 +417,13 @@
     </select>
     <select id="getTaskByUserIdCount" resultType="java.lang.Integer">
         select count(1) from (
-        SELECT t.`name` AS taskName
-        FROM
-        task_executor te
-        LEFT JOIN `user` u ON te.executor_id=u.id
-        LEFT JOIN task t ON te.task_id=t.`id`
-        WHERE t.start_date &lt; #{endDate} AND t.end_date &gt; #{startDate} AND FIND_IN_SET(#{userId},t.`executor_id`) GROUP BY t.`id`
-        )as total
+                                 SELECT t.`name` AS taskName
+                                 FROM
+                                     task_executor te
+                                         LEFT JOIN `user` u ON te.executor_id=u.id
+                                         LEFT JOIN task t ON te.task_id=t.`id`
+                                 WHERE t.start_date &lt; #{endDate} AND t.end_date &gt; #{startDate} AND FIND_IN_SET(#{userId},t.`executor_id`) GROUP BY t.`id`
+                             )as total
     </select>
     <select id="getTaskWithProjectNameWithCharge" resultMap="RichResultMap">
         SELECT task.id, task.project_id, task.name, task.executor_name, task.start_date, task.`end_date`,task.create_date, task.`creater_id`, task.`creater_name`, task.group_id,
@@ -386,11 +440,11 @@
             </foreach>
         </if>
         <if test="userId != null and userId != ''">
-        and task.final_charge_status = 0
-        and case task.charge_stage
-        when 1 then task.charge_one_id = #{userId} and task.charge_one_status = 0
-        when 2 then task.charge_two_id = #{userId} and task.charge_two_status = 0
-        end
+            and task.final_charge_status = 0
+            and case task.charge_stage
+            when 1 then task.charge_one_id = #{userId} and task.charge_one_status = 0
+            when 2 then task.charge_two_id = #{userId} and task.charge_two_status = 0
+            end
         </if>
         ORDER BY task.indate desc
         <if test="pageStart != null and pageSize != null">
@@ -412,79 +466,79 @@
     <select id="getTaskChargePage" resultType="com.management.platform.entity.vo.TaskChargePageVO">
         select tmp1.*,user.name as finalChargeName
         from
-            (
-                select tf.task_id,task.creater_id,task.name as taskName,task.charge_stage
-                     ,task.charge_one_id,task.charge_one_status,task.executor_id,task.group_id
-                     ,task.charge_two_id,task.charge_two_status,p.id as projectId,p.project_name,
-                    case task.charge_stage
-                        when 1 then task.charge_one_id
-                        when 2 then task.charge_two_id
-                        end as finalChargeId
-                from
-                    task_files tf
-                        left join task on tf.task_id = task.id
-                        left join project p on task.project_id = p.id
-                <where>
-                    task.task_status = 0 and task.final_charge_status = 0
-                    and case task.charge_stage
-                    when 1 then (p.incharger_id = task.charge_one_id and task.charge_one_status != 2)
-                    when 2 then (p.incharger_id = task.charge_two_id and task.charge_two_status != 2)
-                    end
-                    <if test="queryBO.projectId != null">
-                        and tf.project_id = #{queryBO.projectId}
-                    </if>
-                    <if test="queryBO.taskName != null and queryBO.taskName != ''">
-                        and task.name like concat('%',#{queryBO.taskName},'%')
-                    </if>
-                    <if test="deptIds!=null and deptIds.size()>0">
-                        and p.dept_id in
-                        <foreach collection="deptIds" open="(" close=")" separator="," item="item">
-                            #{item}
-                        </foreach>
-                    </if>
-                </where>
-            )
-                tmp1 left join user on tmp1.finalChargeId = user.id
+        (
+        select tf.task_id,task.creater_id,task.name as taskName,task.charge_stage
+        ,task.charge_one_id,task.charge_one_status,task.executor_id,task.group_id
+        ,task.charge_two_id,task.charge_two_status,p.id as projectId,p.project_name,
+        case task.charge_stage
+        when 1 then task.charge_one_id
+        when 2 then task.charge_two_id
+        end as finalChargeId
+        from
+        task_files tf
+        left join task on tf.task_id = task.id
+        left join project p on task.project_id = p.id
+        <where>
+            task.task_status = 0 and task.final_charge_status = 0
+            and case task.charge_stage
+            when 1 then (p.incharger_id = task.charge_one_id and task.charge_one_status != 2)
+            when 2 then (p.incharger_id = task.charge_two_id and task.charge_two_status != 2)
+            end
+            <if test="queryBO.projectId != null">
+                and tf.project_id = #{queryBO.projectId}
+            </if>
+            <if test="queryBO.taskName != null and queryBO.taskName != ''">
+                and task.name like concat('%',#{queryBO.taskName},'%')
+            </if>
+            <if test="deptIds!=null and deptIds.size()>0">
+                and p.dept_id in
+                <foreach collection="deptIds" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        )
+        tmp1 left join user on tmp1.finalChargeId = user.id
         where tmp1.finalChargeId = #{queryBO.userId}
         group by tmp1.task_id
         union  all
         select tmp1.*,user.name as finalChargeName
         from
-            (
-                select tf.task_id,task.creater_id,task.name as taskName,task.charge_stage
-                     ,task.charge_one_id,task.charge_one_status,task.executor_id,task.group_id
-                     ,task.charge_two_id,task.charge_two_status,project.id as projectId,project.project_name,
-                    case task.charge_stage
-                        when 1 then task.charge_one_id
-                        when 2 then task.charge_two_id
-                        end as finalChargeId
-                from
-                    task_files tf
-                        left join task on tf.task_id = task.id
-                        left join project on task.project_id = project.id
-                        left join user on task.creater_id = user.id
-                        left join department d on user.department_id = d.department_id
-                <where>
-                    task.task_status = 0 and task.final_charge_status = 0
-                    and case task.charge_stage
-                    when 1 then (d.manager_id = task.charge_one_id and task.charge_one_status != 2)
-                    when 2 then (d.manager_id = task.charge_two_id and task.charge_two_status != 2)
-                    end
-                    <if test="queryBO.projectId != null">
-                        and tf.project_id = #{queryBO.projectId}
-                    </if>
-                    <if test="queryBO.taskName != null and queryBO.taskName != ''">
-                        and task.name like concat('%',#{queryBO.taskName},'%')
-                    </if>
-                    <if test="deptIds!=null and deptIds.size()>0">
-                        and project.dept_id in
-                        <foreach collection="deptIds" open="(" close=")" separator="," item="item">
-                            #{item}
-                        </foreach>
-                    </if>
-                </where>
-            )
-                tmp1 left join user on tmp1.finalChargeId = user.id
+        (
+        select tf.task_id,task.creater_id,task.name as taskName,task.charge_stage
+        ,task.charge_one_id,task.charge_one_status,task.executor_id,task.group_id
+        ,task.charge_two_id,task.charge_two_status,project.id as projectId,project.project_name,
+        case task.charge_stage
+        when 1 then task.charge_one_id
+        when 2 then task.charge_two_id
+        end as finalChargeId
+        from
+        task_files tf
+        left join task on tf.task_id = task.id
+        left join project on task.project_id = project.id
+        left join user on task.creater_id = user.id
+        left join department d on user.department_id = d.department_id
+        <where>
+            task.task_status = 0 and task.final_charge_status = 0
+            and case task.charge_stage
+            when 1 then (d.manager_id = task.charge_one_id and task.charge_one_status != 2)
+            when 2 then (d.manager_id = task.charge_two_id and task.charge_two_status != 2)
+            end
+            <if test="queryBO.projectId != null">
+                and tf.project_id = #{queryBO.projectId}
+            </if>
+            <if test="queryBO.taskName != null and queryBO.taskName != ''">
+                and task.name like concat('%',#{queryBO.taskName},'%')
+            </if>
+            <if test="deptIds!=null and deptIds.size()>0">
+                and project.dept_id in
+                <foreach collection="deptIds" open="(" close=")" separator="," item="item">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        )
+        tmp1 left join user on tmp1.finalChargeId = user.id
         where tmp1.finalChargeId = #{queryBO.userId}
         group by tmp1.task_id
         ORDER BY task_id desc
@@ -572,7 +626,7 @@
         tmp1 left join user on tmp1.finalChargeId = user.id
         where tmp1.finalChargeId = #{queryBO.userId}
         group by tmp1.task_id
-            )tmpAll
+        )tmpAll
     </select>
 
     <resultMap id="taskFileChargeMap" type="com.management.platform.entity.vo.TaskFileChargePageVO">

文件差异内容过多而无法显示
+ 3 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/TimeTypeMapper.xml


+ 4 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/UserMapper.xml

@@ -292,4 +292,8 @@
             #{id}
         </foreach>
     </update>
+    <update id="unbindCorpWX">
+        update user set corpwx_userid=null, corpwx_real_userid=null WHERE id = #{id}
+    </update>
+
 </mapper>

+ 4 - 12
fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue

@@ -41,19 +41,12 @@
                     </el-option>
                 </el-select>
             </el-form-item>
-            <!-- <el-form-item v-if="this.user.companyId==3092" :label="$t('taskdefinition')" prop="serviceId">
-                <el-select filterable style="width:100%;"  v-model="addForm.serviceId" placeholder="请选择任务内容">
-                    <el-option
-                    v-for="item in sapServiceList"
-                    :key="item.serviceCode"
-                    :label="item.serviceName+'-'+item.serviceCode"
-                    :value="item.serviceCode">
-                    </el-option>
-                </el-select>
-            </el-form-item> -->
             <el-form-item  :label="$t('taskdefinition')" prop="name">
                 <el-input v-model="addForm.name" :maxlength="40" :disabled="(this.addForm.id != null && user.id != this.addForm.createrId && currentProject.inchargerId != user.id) && !permissions.projectManagement && !permissions.editAnyTask && !(groupResponsibleId == user.id)" :placeholder="$t('enterthetaskcontent')" clearable></el-input>
             </el-form-item>
+            <el-form-item  :label="$t('planCost')" prop="planCost" v-if="user.timeType.taskPlanCost">
+                <el-input v-model.number="addForm.planCost" :maxlength="40" :disabled="(this.addForm.id != null && user.id != this.addForm.createrId && currentProject.inchargerId != user.id) && !permissions.projectManagement && !permissions.editAnyTask && !(groupResponsibleId == user.id)" :placeholder="$t('planCostHint')" clearable></el-input>
+            </el-form-item>
             <!-- {{timelabel}}{{mileageCup}} -->
             <el-form-item :label="!timelabel ? $t('starttimes') : $t('jie-zhi-shi-jian')" prop="startDate">
                 <el-date-picker v-model="addForm.startDate" type="date" style="width:40%;" value-format="yyyy-MM-dd"  
@@ -789,6 +782,7 @@ export default {
             projectId: [{ required: true, message: this.$t('qingXuanZeSuoShuXiangMu'), trigger: "blur" }],
             groupId: [{ required: true, message: this.$t('qingXuanZeSuoShuRenWuFenZu'), trigger: "blur" }],
             stagesId: [{ required: true, message: this.$t('qingXuanZeSuoShuRenWuLieBiao'), trigger: "blur" }],
+            planCost: [{ required: true, message: this.$t('planCostHint'), trigger: "blur" }],
         },
         formGrouping: {
             name: [{ required: true, message: this.$t('pleaseenteragroupname'), trigger: "blur" }],
@@ -1588,14 +1582,12 @@ export default {
                             });
                             
                             if (this.addForm.parentTid == null) {
-                                console.log('我是对的')
                                 let obj = {
                                     submitInsert: true,
                                     showOrNot: this.showOrNot
                                 }
                                 this.$emit('closeBounced', obj)
                             } else {
-                                console.log('我也是对的')
                                 // this.backToParentTask();
                                 this.$emit('closeBounced', {backToParentTaskSub: true})
                             }

+ 6 - 1
fhKeeper/formulahousekeeper/timesheet/src/i18n/en.json

@@ -2200,5 +2200,10 @@
   "zhuanYiZhi": "Transfer to",
   "cardtimenotfull": "ReportTime and cardTime abnormal list",
   "fillReportTime": "Fill report time",
-  "kaoqingjiabanjiaoyanTip": "Man-hour management type"
+  "kaoqingjiabanjiaoyanTip": "Man-hour management type",
+  "planCost": "Plan Cost",
+  "planCostHint": "please input task plan cost",
+  "taskPlanCostReport": "Task R&D report",
+  "planCostFee": "Plan Cost",
+  "realCostFee": "Real Cost"
 }

+ 6 - 1
fhKeeper/formulahousekeeper/timesheet/src/i18n/zh.json

@@ -2205,5 +2205,10 @@
   "queRenZhuanYi": "确认转移",
   "zhuanYiChengGong": "转移成功",
   "cardtimenotfull": "考勤工时异常列表",
-  "fillReportTime": "填报时长"
+  "fillReportTime": "填报时长",
+  "planCost": "预估成本",
+  "planCostHint": "请输入任务预估成本,单位(元)",
+  "taskPlanCostReport": "任务研发报表",
+  "planCostFee": "预估研发费用(元)",
+  "realCostFee": "实际研发费用(元)"
 }

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

@@ -257,6 +257,7 @@ const StringUtil = {
         arr[i] == '负责部门FTE报表' ? obj.reportFTEPart = true : ''
         arr[i] == '有效工时率表' ? obj.reportEfficent = true : ''
         arr[i] == '项目预估工时表' ? obj.reportProjectEstimated = true : ''
+        arr[i] == '任务研发报表' ? obj.taskPlanCost = true : ''
 
         arr[i] == '新增合同' ? obj.contractNew = true : ''
         arr[i] == '查看全部合同' ? obj.contractView = true : ''

+ 159 - 16
fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue

@@ -49,6 +49,7 @@
                   <el-menu-item index="1-27" v-if="permissions.reportStaffTaskAccomplished" @click="ssl(26)"><p>{{ $t('yuanGongRenWuJinDuBiao') }}</p></el-menu-item>
                   <el-menu-item index="1-28" v-if="permissions.reportProjectEstimated" @click="ssl(27)"><p>{{ $t('xiangMuYuGuGongShiBiao') }}</p></el-menu-item>
                   <el-menu-item index="1-29" v-if="permissions.takCompletedStatus" @click="ssl(28)"><p>{{ $t('yuanGongRenWuWanChengQingKuangBiao') }}</p></el-menu-item>
+                  <el-menu-item index="1-30" v-if="permissions.taskPlanCost" @click="ssl(29)"><p>{{ $t('taskPlanCostReport') }}</p></el-menu-item>
                 </el-submenu>
               </el-menu>
           </el-col>
@@ -125,15 +126,6 @@
             <el-radio-button label="1">图表</el-radio-button>
           </el-radio-group>
         </template>
-          
-        <!-- 按部门/项目筛选 -->
-        <!-- <el-select v-if="ins == 10" v-model="departmentOrProject" placeholder="请选择" size="small" @change="selcts(10)" style="margin-left:10px;width:120px">
-          <el-option label="查看项目审核人" :value="1"></el-option>
-          <el-option label="查看部门审核人" :value="0"></el-option>
-        </el-select> -->
-        <!-- 项目筛选 -->
-        <!-- <el-select v-if="ins != 4 && ins != 8 && ins != 9 && ins != 19 && ins != 10 && ins != 11 && ins != 14 && ins != 15 && ins != 17 && ins != 20 && ins != 21 && ins != 22" v-model="proJuctId" :placeholder="$t('defaultText.pleaseSelectSnItem')" clearable filterable size="small" @change="projectChange()" style="margin-left:10px"> -->
-
         <el-select v-if="ins == 24 && tabPosition == 0 && tabsType == 'all'" v-model="selectStageName" placeholder="请选择阶段" clearable filterable size="small" @change="getList(true)"  style="margin-left:10px; width: 250px">
           <el-option v-for="(item, index) in projectStageList" :key="item.id" :label="item.projectStageName" :value="item.projectStageName"> </el-option>
         </el-select>
@@ -375,6 +367,57 @@
                 </span>
             </el-table>
 
+            <!-- 任务研发报表 -->
+            <el-table v-if="ins == 29" key="29" border :data="list" highlight-current-row v-loading="listLoading" :height="tableHeight" style="width: 100%;" :span-method="objectSpanMethod">
+                <el-table-column  prop="projectCode" :label="$t('Itemno')"  width="120"></el-table-column>
+                <el-table-column  prop="projectName" :label="$t('headerTop.projectName')" >
+                </el-table-column>
+                <el-table-column prop="name" :label="$t('nameofthetask')"  width="300">
+                  <template slot-scope="scope" >
+                    {{scope.row.name}}
+                  </template>
+                </el-table-column>
+                <el-table-column prop="planCost" :label="$t('planCostFee')"  width="150" align="right">
+                  <template slot-scope="scope" >
+                    {{scope.row.planCost == null? 0:scope.row.planCost.toFixed(2)}}
+                  </template>
+                </el-table-column>
+                <el-table-column prop="executorName" :label="$t('zhi-hang-ren')"  width="100">
+                  <template slot-scope="scope" >
+                    <span v-if="user.userNameNeedTranslate == '1'">
+                      <TranslationOpenDataText type='userName' :openid='scope.row.executorName'></TranslationOpenDataText>
+                    </span>
+                    <span v-if="user.userNameNeedTranslate != '1'">
+                      {{scope.row.executorName}}
+                    </span>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="planHours" :label="$t('plantime')+'(h)'"  width="150" align="right">
+                  <template slot-scope="scope">
+                        {{scope.row.planHours == null? 0:scope.row.planHours.toFixed(1)}}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="realHours" :label="$t('shi-ji-gong-shi')+'(h)'"  width="150" align="right">
+                   <template slot-scope="scope">
+                        <font :style="'color:'+(scope.row.realHours > scope.row.planHours?'red':'')">{{scope.row.realHours == null? 0:scope.row.realHours.toFixed(1)}}</font>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="realCost" :label="$t('realCostFee')"  width="150" align="right">
+                   <template slot-scope="scope">
+                        {{scope.row.realCost == null? 0:scope.row.realCost.toFixed(2)}} 
+                        <el-link type="primary" :underline="false" @click="modRealCost(scope.row)" style="margin-left:10px;">
+                          修改
+                      </el-link>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="taskStatus" :label="$t('state.states')" width="80" >
+                    <template slot-scope="scope">
+                        {{taskStatusTxt[scope.row.taskStatus]}}
+                    </template>
+                </el-table-column>
+            </el-table>
+
+
             <!--项目成本报表 -->
             <el-table v-if="ins == 2"  key="2" border :data="list2" highlight-current-row v-loading="listLoading" :height="tableHeight" style="width: 100%;">
                 <el-table-column  prop="projectCode" :label="user.companyId == '7030' ? '项目令号' : $t('Itemno')"  width="120"></el-table-column>
@@ -1690,6 +1733,21 @@
             <el-button type="primary" @click="setForewarning()" :loading="warningTableLoading">{{ $t('queDing') }}</el-button>
           </span>
         </el-dialog>
+        <!-- 修改实际费用弹窗 -->
+        <el-dialog title="修改实际费用" :visible.sync="modRealCostDialog" width="600px" :before-close="handleClose">
+          <div>
+            <el-form ref="modRealCostForm" :model="modRealCostItem" label-width="120px" :rules="rules">
+              <el-form-item label="实际研发费用" prop="realCost">
+                <el-input v-model.number="modRealCostItem.realCost" placeholder="请输入实际研发费用(元)" clearable >
+                </el-input>
+              </el-form-item>
+            </el-form>
+          </div>
+          <span slot="footer" class="dialog-footer">
+            <el-button @click="modRealCostDialog = false">{{ $t('quXiao') }}</el-button>
+            <el-button type="primary" @click="confirmModRealCost()" >{{ $t('queDing') }}</el-button>
+          </span>
+        </el-dialog>
         <!-- 员工任务数据弹窗 -->
         <el-dialog
           :title="$t('yuanGongRenWuShuJu')"
@@ -1823,8 +1881,8 @@ export default {
       listPosition3:0,
       windowHeight: document.documentElement.clientHeight,
       windowWidth: document.documentElement.clientWidth,
-
-
+      modRealCostDialog: false,
+      rules: {realCost: [{ required: true, message: '请输入实际研发费用', trigger: 'blur' }]},
       list2: [],
       list3: [],
       list3HeadList: [],
@@ -2017,7 +2075,9 @@ export default {
       selectStageName: '',
 
       estimatedWorkingHoursType: '按项目查看',
-      isWarn: false
+      isWarn: false,
+      modRealCostItem:{id:null, realCost:0},
+      editRowItem:null
     };
   },
   computed: {},
@@ -2104,7 +2164,6 @@ export default {
       this.projectChange()
     },
     handleClick() {
-      console.log(this.tabsType, '<==== 返回的书')
       this.groupTaskKey++
       if(this.tabsType == 'all') {
         this.$set(this, 'groupConsumptionName', '')
@@ -2153,6 +2212,7 @@ export default {
       if(this.permissions.reportStaffTaskAccomplished) {this.ssl(26);this.defaultActive = '1-27';return} else
       if(this.permissions.reportProjectEstimated) {this.ssl(27);this.reportProjectEstimated = '1-28';return} else
       if(this.permissions.takCompletedStatus) {this.ssl(28);this.takCompletedStatus = '1-29';return} else
+      if(this.permissions.taskPlanCost) {this.ssl(29);this.takCompletedStatus = '1-30';return} else
       {this.allWrong = false}
     },
     rowspan(spanArr,position,spanName,dataItem = [],fields=false){
@@ -2222,7 +2282,38 @@ export default {
           });
       });
     },
-
+    //弹窗修改实际研发成本
+    modRealCost(rowItem) {
+      this.editRowItem = rowItem;
+      this.modRealCostItem.id = rowItem.teId;
+      this.modRealCostItem.realCost = rowItem.realCost;
+      this.modRealCostDialog = true;
+    },
+    confirmModRealCost() {
+      this.$refs.modRealCostForm.validate(valid => {
+        if (valid) {
+          this.http.post('/task-executor/modify', this.modRealCostItem,
+          res => {
+              if (res.code == "ok") {
+                  this.modRealCostDialog = false;
+                  this.editRowItem.realCost = this.modRealCostItem.realCost;
+              } else {
+                  this.$message({
+                    message: res.msg,
+                    type: "error"
+                  });
+              }
+          },
+          error => {
+              this.$message({
+                  message: error,
+                  type: "error"
+              });
+          });
+        }
+      });
+    },
+    
     getUserList() {
       console.log(this.shuzArr[this.ins])
       this.http.post('/user/getUserListByRole', {
@@ -2426,7 +2517,7 @@ export default {
                 this.getGroupWorktimeAll()
             },
             getList(e) {
-              let noUserList = [16, 17, 18, 19, 20, 21, 22, 24, 25, 26,27]
+              let noUserList = [16, 17, 18, 19, 20, 21, 22, 24, 25, 26,27,29]
               if(this.ins == 24) {
                 if(this.tabsType == 'all') {
                   this.rangeDatas = []
@@ -2520,6 +2611,9 @@ export default {
                 if(this.ins == 28) {
                   this.gettaskCompletedData()
                 }
+                if (this.ins == 29) {
+                  this.getTaskPlanAndRealCost();
+                }
             },
       exportExcel() {
         var url = "/project";
@@ -2742,7 +2836,14 @@ export default {
           dept ? sl.deptId = dept : ''
           sl.startDate = this.rangeDatas[0]
           sl.endDate = this.rangeDatas[1]
-        }
+        } else if (this.ins == 29) {
+          fName = this.$t('taskPlanCostReport') + '.xlsx';
+          url += "/exportProjectTaskPlanAndRealCost";
+          if(this.taskTypeId != 'null' && this.taskTypeId != null && this.taskTypeId != '') {
+            sl.taskType = this.taskTypeId
+          }
+          sl.projectId = this.proJuctId
+        } 
         this.exportReportLoading = true
           this.http.post(url, sl,
             res => {
@@ -3915,6 +4016,48 @@ export default {
       if(this.ins == 28) {
         this.gettaskCompletedData()
       }
+      if(this.ins == 29) {
+        this.getTaskPlanAndRealCost()
+      }
+    },
+    getTaskPlanAndRealCost() {
+      this.listLoading = true;
+      const { companyId } = this.user
+      let ginseng = {
+        pageIndex: this.page,
+        pageSize: this.size,
+        projectId: this.proJuctId
+      }
+      if(this.taskTypeId != 'null' && this.taskTypeId != null && this.taskTypeId != '') {
+        ginseng.taskType = this.taskTypeId
+      }
+      this.http.post('/project/getProjectTaskPlanAndRealCost', ginseng,
+        res => {
+            if (res.code == "ok") {
+              this.list = res.data.records;
+              this.listArr1 = []
+              this.listArr2 = []
+              this.listArr3 = []
+              this.listPosition1 = 0
+              this.listPosition2 = 0
+              this.listPosition3 = 0
+              this.rowspan(this.listArr1,this.listPosition1,'project_code')
+              this.rowspan(this.listArr2,this.listPosition2,'project_name')
+              this.total = res.data.total;
+              this.listLoading = false; 
+            } else {
+                this.$message({
+                message: res.msg,
+                type: "error"
+                });
+            }
+        },
+        error => {
+            this.$message({
+                message: error,
+                type: "error"
+            });
+        });
     },
     // 任务重启表
     taskRestart() {

+ 17 - 15
fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue

@@ -386,13 +386,9 @@
             </el-dialog>
 
         <!-- 文件审核 -->
-        <el-dialog title="文件审核" :visible.sync="viewFilesAndReviewThemVisable" width="800px">
-            <el-table :data="viewFilesAndReviewThemRejectList" style="width: 100%" height="500px" :key="viewFilesAndReviewThemkey">
-                <el-table-column :label="$t('headerTop.serialNumber')" prop="documentType" min-width="60" align="center">
-                    <template slot-scope="scope">
-                        {{scope.$index + 1}}
-                    </template>
-                </el-table-column>
+        <el-dialog title="文件审核" :visible.sync="viewFilesAndReviewThemVisable" width="1000px">
+            <el-table :data="viewFilesAndReviewThemRejectList" style="width: 100%" height="500px" @selection-change="handleSelectionChange" :key="viewFilesAndReviewThemkey">
+                <el-table-column type="selection" width="55"></el-table-column>
                 <el-table-column :label="$t('filenames')" prop="documentName" min-width="180"></el-table-column>
                 <el-table-column :label="$t('filesize')" prop="size" min-width="80" align="center"></el-table-column>
                 <el-table-column :label="$t('founder')" prop="creatorName" min-width="60" align="center">
@@ -410,15 +406,15 @@
                         <span>{{scope.row.indate}}</span>
                     </template>
                 </el-table-column>
-                <el-table-column :label="$t('operation')" min-width="90" fixed="right">
+                <el-table-column :label="$t('operation')" min-width="40" fixed="right">
                     <template slot-scope="scope">
                         <el-link :href="scope.row.url" :download="scope.row.documentName" type="primary" style="margin-right:7px">{{ $t('other.download') }}</el-link>
                     </template>
                 </el-table-column>
             </el-table>
             <div slot="footer" class="dialog-footer">
-                <el-button type="primary" size="small" :loading="viewFilesAndReviewThemAdoptLoading" @click="viewFilesAndReviewThemRejectCli(1)">通过</el-button>
-                <el-button type="danger" size="small" @click="viewFilesAndReviewThemReject()">驳回</el-button>
+                <el-button type="primary" size="small" :disabled="tableMultipleSelection.length == 0" :loading="viewFilesAndReviewThemAdoptLoading" @click="viewFilesAndReviewThemRejectCli(1)">通过</el-button>
+                <el-button type="danger" size="small" :disabled="tableMultipleSelection.length == 0" @click="viewFilesAndReviewThemReject()">驳回</el-button>
             </div>
         </el-dialog>
         <el-dialog title="文件驳回" :visible.sync="viewFilesAndReviewThemRejectVisable" width="800px">
@@ -796,10 +792,14 @@ import { error } from 'dingtalk-jsapi';
                 viewFilesAndReviewThemkey: 902,
 
                 tablesKey: 1,
-                tablesTwoKey: 300
+                tablesTwoKey: 300,
+                tableMultipleSelection: []
             };
         },
         methods: {
+            handleSelectionChange(val) {
+                this.tableMultipleSelection = val;
+            },
             showReasonForRejection(item) {
                 const taskId = item.id
                 this.http.post('/task/getFileRejectReason',{
@@ -817,11 +817,11 @@ import { error } from 'dingtalk-jsapi';
                     this.$message.error('请输入驳回原因')
                     return
                 }
+                const taskFileIds = this.tableMultipleSelection.map(item => item.id).join(',')
                 this.viewFilesAndReviewThemRejectValLoading = true
                 const { id, projectId } = this.viewFilesAndReviewThemRejectRow
-                this.http.post('/task/auditFile',{
-                    taskId: id, projectId,
-                    auditStatus,
+                this.http.post(auditStatus == 1 ? '/task-files/approveFile' : '/task-files/rejectFile',{
+                    taskFileIds,
                     reason: auditStatus == 1 ? '' : this.viewFilesAndReviewThemRejectVal
                 },
                 res => {
@@ -860,7 +860,8 @@ import { error } from 'dingtalk-jsapi';
             },
             getViewFilesAndReviewThemList() {
                 this.viewFilesAndReviewThemTableLoading = false
-                this.http.post('/task-files/getTaskFiles',{taskId:this.viewFilesAndReviewThemRejectRow.id},
+                // this.http.post('/task-files/getTaskFiles',{taskId:this.viewFilesAndReviewThemRejectRow.id},
+                this.http.post('/task-files/getUnChargedFilesByTaskId',{taskId:this.viewFilesAndReviewThemRejectRow.id},
                 res => {
                     if (res.code == "ok") {
                         this.viewFilesAndReviewThemkey++
@@ -1518,6 +1519,7 @@ import { error } from 'dingtalk-jsapi';
             viewFilesAndReviewThem(item) {
                 this.viewFilesAndReviewThemRejectRow = item
                 this.viewFilesAndReviewThemVisable = true
+                this.tableMultipleSelection = []
                 this.getViewFilesAndReviewThemList()
             },
             // 下拉框选择

+ 1 - 1
fhKeeper/formulahousekeeper/timesheet/src/views/workReport/daily.vue

@@ -139,7 +139,7 @@
                                     <el-link v-if="user.timeType.enableNewWeeklyfill == 1" type="primary" style="margin-right:10px;" :underline="false" @click="isSubstitude=false;fillInReportCustom()">{{ $t('textLink.fillInAWeek') }}</el-link>
                                     <!-- <el-link type="primary" style="margin-right:10px;" :underline="false" @click="isSubstitude=false;fillInReportCustom()">按周填报123</el-link> -->
                                     <el-link type="primary" style="margin-right:10px;" :underline="false" @click="isSubstitude=false;weekIndex++,fillWeekDialogVisiCustomTwo=true" v-if="user.timeType.enableNewWeeklyfill==2">按周填报</el-link>
-                                    <el-link type="primary" v-if="user.companyId != 5978 && permissions.reportsFillOut && user.timeType.enableNewWeeklyfill != 1" style="margin-right:10px;" :underline="false" @click="isSubstitude=true; fillInReport(-1,(user.companyId == 5814||user.companyId==5693 || user.companyId==4407 || user.companyId == 7812)?2:0)">{{$t('textLink.helpToFillIn')}}</el-link>
+                                    <el-link type="primary" v-if="user.companyId != 5978 && permissions.reportsFillOut && user.timeType.enableNewWeeklyfill != 1" style="margin-right:10px;" :underline="false" @click="isSubstitude=true; fillInReport(-1,(user.companyId == 5814||user.companyId==5693 || user.companyId==4407 || user.companyId == 7812 || user.companyId == 481)?2:0)">{{$t('textLink.helpToFillIn')}}</el-link>
                                     <!-- 苏州景昱,按周填报的模式进行代填日报 -->
                                     <el-link type="primary" v-if="user.companyId == 5978 && permissions.reportsFillOut && user.timeType.enableNewWeeklyfill != 1" style="margin-right:10px;" :underline="false" @click="isSubstitude=true; fillInReportss();">{{$t('textLink.helpToFillIn')}}</el-link>
                                     

+ 21 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/views/my/children/center.vue

@@ -43,6 +43,8 @@
                 </template>
             </van-cell>
             <van-button class="logout" @click="logout" block round type="danger" v-if="!isCorpWX">退出登录</van-button>
+
+            <van-button class="logout" @click="unbindCorpWX" block round type="danger" v-if="isCorpWX && userInfo.corpwxUserid != null && userInfo.userNameNeedTranslate != '1'">解除绑定</van-button>
             <!-- <van-button class="logout" @click="logout" block round type="danger" >退出登录</van-button> -->
         </main>
 
@@ -81,6 +83,25 @@
                 localStorage.removeItem("userInfo");
                 this.$router.push("/login");
             },
+            unbindCorpWX() {
+                this.$dialog.confirm({
+                    title: '',
+                    message: '解除绑定后只能通过账号密码登录,您确定吗?'
+                }).then(() => {
+                    this.$axios.post("/user/unbindCorpWX", {userId: this.userInfo.id})
+                    .then(res => {
+                        if(res.code == "ok") {
+                            this.$store.commit("updateLogin", false);
+                            localStorage.removeItem("userInfo");
+                            this.$router.push("/login");
+                        } else {
+                            this.$toast.clear();
+                            this.$toast.fail(res.msg);
+                        }
+                    }).catch(err=> {this.$toast.clear();});
+                }).catch(() => {
+                });
+            },
             bindWeiXin(){
                 //企业微信
                 if (this.isCorpWX && this.userInfo.corpwxUserid != null) {