Pārlūkot izejas kodu

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

seyason 11 mēneši atpakaļ
vecāks
revīzija
d17fb43107
16 mainītis faili ar 940 papildinājumiem un 181 dzēšanām
  1. 84 30
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue
  2. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue
  3. 10 6
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/api.ts
  4. 163 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/deteleTables.vue
  5. 40 35
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/detail/index.vue
  6. 320 64
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/index.vue
  7. 4 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts
  8. 22 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts
  9. 3 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/CustomController.java
  10. 3 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/ClueMapper.java
  11. 5 4
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/CustomServiceImpl.java
  12. 1 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/ProductServiceImpl.java
  13. 115 12
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java
  14. 15 0
      fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/CustomMapper.xml
  15. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  16. 153 26
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserWithBeisenController.java

+ 84 - 30
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue

@@ -49,11 +49,27 @@ const dateOptions = [
   }
 ];
 
-const defineDate = ref<[Date, Date] | ''>('');
+type PromptType = {
+  permission?: 0 | 1 | 2 | 3;
+  date?: 0 | 1 | 2;
+  sliceDate: [Date, Date];
+};
 
-const bulletinPrompt = reactive({ permission: undefined, date: undefined });
-const summaryPrompt = reactive({ permission: undefined, date: undefined });
-const stagePrompt = reactive({ permission: undefined, date: undefined });
+const bulletinPrompt = reactive<PromptType>({
+  permission: undefined,
+  date: undefined,
+  sliceDate: [new Date(), new Date()]
+});
+const summaryPrompt = reactive<PromptType>({
+  permission: undefined,
+  date: undefined,
+  sliceDate: [new Date(), new Date()]
+});
+const stagePrompt = reactive<PromptType>({
+  permission: undefined,
+  date: undefined,
+  sliceDate: [new Date(), new Date()]
+});
 
 const requestData = reactive<{
   bulletin: BulletinData['data'] | null;
@@ -104,10 +120,10 @@ const queryStage = async (payload?: RequestProps) => {
 
 watchEffect(() => {
   queryBulletin({
-    ...(bulletinPrompt.date === 'defineDate'
+    ...(bulletinPrompt.date === ('ignore' as any)
       ? {
-          startDate: dayjs(defineDate.value[0]).valueOf(),
-          endData: dayjs(defineDate.value[1]).valueOf()
+          startDate: dayjs(bulletinPrompt.sliceDate[0]).format('YYYY-MM-DD'),
+          endData: dayjs(bulletinPrompt.sliceDate[1]).format('YYYY-MM-DD')
         }
       : { dateType: bulletinPrompt.date }),
     queryType: bulletinPrompt.permission
@@ -116,10 +132,10 @@ watchEffect(() => {
 
 watchEffect(() => {
   queryStage({
-    ...(stagePrompt.date === 'defineDate'
+    ...(stagePrompt.date === ('ignore' as any)
       ? {
-          startDate: dayjs(defineDate.value[0]).valueOf(),
-          endData: dayjs(defineDate.value[1]).valueOf()
+          startDate: dayjs(stagePrompt.sliceDate[0]).format('YYYY-MM-DD'),
+          endData: dayjs(stagePrompt.sliceDate[1]).format('YYYY-MM-DD')
         }
       : { dateType: stagePrompt.date }),
     queryType: stagePrompt.permission
@@ -128,22 +144,29 @@ watchEffect(() => {
 
 watchEffect(() => {
   querySummary({
-    ...(summaryPrompt.date === 'defineDate'
+    ...(summaryPrompt.date === ('ignore' as any)
       ? {
-          startDate: dayjs(defineDate.value[0]).valueOf(),
-          endData: dayjs(defineDate.value[1]).valueOf()
+          startDate: dayjs(summaryPrompt.sliceDate[0]).format('YYYY-MM-DD'),
+          endData: dayjs(summaryPrompt.sliceDate[1]).format('YYYY-MM-DD')
         }
       : { dateType: summaryPrompt.date }),
     queryType: summaryPrompt.permission
   });
 });
+
+watchEffect(() => {
+  console.log(
+    bulletinPrompt.date,
+    '-----',
+    bulletinPrompt.sliceDate,
+    '88888888---watchEffect'
+  );
+});
 </script>
 
 <template>
-  <div
-    class="m-5 bg-white min-h-full p-4 rounded relative flex gap-12 items-start"
-  >
-    <section class="flex-[4]">
+  <div class="m-5 bg-white min-h-full p-4 rounded">
+    <section>
       <div class="flex gap-3 mb-4">
         <div class="w-40">
           <el-select
@@ -171,7 +194,20 @@ watchEffect(() => {
               :label="date.label"
               :value="date.value"
             />
-            <el-option v-if="defineDate" label="自定义" value="defineDate" />
+            <el-option label="自定义" value="ignore">
+              <div class="flex gap-2 w-80">
+                <el-date-picker
+                  size="small"
+                  :clearable="false"
+                  type="daterange"
+                  class="w-12"
+                  v-model="bulletinPrompt.sliceDate"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                />
+                <el-button size="small" type="primary">确认</el-button>
+              </div>
+            </el-option>
           </el-select>
         </div>
       </div>
@@ -270,11 +306,20 @@ watchEffect(() => {
                   :label="date.label"
                   :value="date.value"
                 />
-                <el-option
-                  v-if="defineDate"
-                  label="自定义"
-                  value="defineDate"
-                />
+                <el-option label="自定义" value="ignore">
+                  <div class="flex gap-2 w-80">
+                    <el-date-picker
+                      size="small"
+                      :clearable="false"
+                      type="daterange"
+                      class="w-12"
+                      v-model="summaryPrompt.sliceDate"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                    />
+                    <el-button size="small" type="primary">确认</el-button>
+                  </div>
+                </el-option>
               </el-select>
             </div>
           </div>
@@ -366,11 +411,20 @@ watchEffect(() => {
                   :label="date.label"
                   :value="date.value"
                 />
-                <el-option
-                  v-if="defineDate"
-                  label="自定义"
-                  value="defineDate"
-                />
+                <el-option label="自定义" value="ignore">
+                  <div class="flex gap-2 w-80">
+                    <el-date-picker
+                      size="small"
+                      :clearable="false"
+                      type="daterange"
+                      class="w-12"
+                      v-model="stagePrompt.sliceDate"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                    />
+                    <el-button size="small" type="primary">确认</el-button>
+                  </div>
+                </el-option>
               </el-select>
             </div>
           </div>
@@ -380,7 +434,7 @@ watchEffect(() => {
         </div>
       </div>
     </section>
-    <nav class="flex-1 min-w-60 sticky right-0 top-9 max-lg:hidden">
+    <!-- <nav class="flex-1 min-w-60 sticky right-0 top-9 max-lg:hidden">
       <div class="border border-gray-200 rounded w-3/4 text-sm text-gray-500">
         <div class="p-2" v-for="primission in permissionOptions">
           {{ primission.label }}
@@ -399,6 +453,6 @@ watchEffect(() => {
           />
         </div>
       </div>
-    </nav>
+    </nav> -->
   </div>
 </template>

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

@@ -143,7 +143,7 @@
       <div class="p-8">
         <div class="ml-4 mr-4">
           <div class="flex items-center">1、点击下载 <el-link type="primary" @click="downloadTemplate(MODURL, '商机导入模板.xlsx')">商机导入模板.xlsx</el-link></div>
-          <div class="mt-4">2、填写excel文件、商机名称、商机金额、商机阶段</div>
+          <div class="mt-4">2、填写excel文件、商机名称、商机金额、商机阶段必填</div>
         </div>
       </div>
     </el-dialog>

+ 10 - 6
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/api.ts

@@ -3,10 +3,14 @@ export const IMPORTMOD = 'Customer'
 export const PREFIX = '/custom'
 export const GETSYSFILED = '/sys-dict/getListByCode'
 export const GETPERSONNEL = '/user/getSimpleActiveUserList'
+export const GETALLCLUE = '/clue/getAll'
+export const URL_TEMPLALE = `/sys-form/getListByCode${MOD}`
 export const URL_TABLELIST = `${PREFIX}/list`
-
-export const stageStatus = [
-    { id: 1, name: "赢单", progress: "100%" },
-    { id: 2, name: "输单", progress: "0%" },
-    { id: 3, name: "无效", progress: "0%" }
-]
+export const URL_EDITSAVE = `${PREFIX}/insertAndUpdate`
+export const URL_DETELER = `${PREFIX}/deleter`
+export const URL_RECYCLELIST = `${PREFIX}/deleteList`
+export const URL_ROWBACK = `${PREFIX}/rollback`
+export const URL_THOROUGHLYDETELE = `${PREFIX}/reallyDelete`
+export const URL_CLAIM = `${PREFIX}/claim`
+export const URL_FETALL = `${PREFIX}/getAll`
+export const URL_GETDETAIL = `${PREFIX}/getInfo`

+ 163 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/deteleTables.vue

@@ -0,0 +1,163 @@
+<template>
+    <el-dialog v-model="deteleBusinessDialogVisible" width="1000" :before-close="beForeCancel" :show-close="false"
+        top="10vh">
+        <template #header="{ close, titleId, titleClass }">
+            <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                <h4 :id="titleId">客户回收站</h4>
+                <div>
+                    <el-button type="primary" v-loading="allLoading.batchRecoveryLoading"
+                        :disabled="batchTableData.length == 0" @click="batchOperation('恢复')">批量恢复</el-button>
+                    <el-button type="primary" v-loading="allLoading.batchDeteleLoading"
+                        :disabled="batchTableData.length == 0" @click="batchOperation('删除')">批量删除</el-button>
+                    <el-button @click="cancel()">取消</el-button>
+                </div>
+            </div>
+        </template>
+        <div class="h-[60vh] flex flex-col">
+            <div class="flex-1 w-full overflow-hidden">
+                <el-table ref="deteleTableRef" :data="deteleBusinessTable" border v-loading="allLoading.tableLoading"
+                    @selection-change="changeBatch" style="width: 100%;height: 100%;">
+                    <el-table-column type="selection" width="55" />
+                    <el-table-column prop="customName" label="客户名称" width="180"></el-table-column>
+                    <el-table-column prop="customSourceValue" label="客户来源" width="180"></el-table-column>
+                    <el-table-column prop="companyPhone" label="公司电话" width="180"></el-table-column>
+                    <el-table-column prop="email" label="邮箱" width="200"></el-table-column>
+                    <el-table-column prop="customerIndustryValue" label="客户行业" width="180"></el-table-column>
+                    <el-table-column prop="customerLevelValue" label="客户级别" width="180"></el-table-column>
+                    <el-table-column prop="inchargerName" label="负责人" width="190"></el-table-column>
+                    <el-table-column prop="creatorName" label="创建人" width="180"></el-table-column>
+                    <el-table-column prop="newCreateTime" label="创建时间" width="180"></el-table-column>
+                    <el-table-column label="操作" fixed="right" width="120">
+                        <template #default="scope">
+                            <el-button link type="primary" size="large"
+                                @click="operationItem(scope.row.id, scope.row.name, '恢复')">恢复</el-button>
+                            <el-button link type="danger" size="large"
+                                @click="operationItem(scope.row.id, scope.row.name, '删除')">删除</el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+            <div class="flex justify-end pt-3">
+                <el-pagination layout="total, prev, pager, next, sizes" :page-size="tableForm.pageFrom"
+                    @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
+                    :hide-on-single-page="true" />
+            </div>
+        </div>
+    </el-dialog>
+</template>
+<script lang="ts" setup>
+import { post } from '@/utils/request';
+import { ref, reactive, onMounted, watchEffect, watch, inject } from 'vue'
+import { URL_RECYCLELIST, URL_ROWBACK, URL_THOROUGHLYDETELE } from '../api';
+import { ElTable } from 'element-plus';
+import { confirmAction } from '@/utils/tools';
+import { formatDate } from '@/utils/times';
+
+type operationType = '恢复' | '删除'
+
+const emits = defineEmits(['closeVisible']);
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const deteleBusinessTable = ref([])
+const deteleBusinessDialogVisible = ref(false)
+const businessTotalTable = ref(0)
+const batchTableData = ref([])
+const allLoading = reactive({
+    batchRecoveryLoading: false,
+    batchDeteleLoading: false,
+    tableLoading: false
+})
+
+const tableForm = reactive({
+    pageIndex: 1,
+    pageFrom: 10
+})
+
+const deteleTableRef = ref<InstanceType<typeof ElTable>>() // 线索table dom
+
+const props = defineProps<{
+    visibles: boolean
+}>()
+
+watch(() => props.visibles, (newVal) => {
+    deteleBusinessDialogVisible.value = newVal
+    if (newVal) {
+        getTableList()
+    }
+})
+
+function batchOperation(type: operationType) {
+    const value = batchTableData.value.map((item: any) => item.id).join(',')
+    const label = batchTableData.value.map((item: any) => item.name).join(',')
+    operationItem(value, label, type, true)
+}
+
+function operationItem(value: string | number, label: string, type: operationType, batch: boolean = false) {
+    confirmAction(`确定${batch ? '批量' : ''}${type}【${label}】客户吗?`).then(() => {
+        let url = type == '恢复' ? URL_ROWBACK : URL_THOROUGHLYDETELE
+        post(url, { ids: value }).then(res => {
+            if (res.code != 'ok') {
+                globalPopup?.showError(res.msg)
+                return
+            }
+            globalPopup?.showSuccess(`${type}成功`)
+            getTableList()
+            changeBatch(false)
+        }).catch((err) => {
+            globalPopup?.showError(err.message)
+        })
+    })
+}
+
+function changeBatch(flag: boolean = true) {
+    if (flag) {
+        batchTableData.value = deteleTableRef.value && deteleTableRef.value.getSelectionRows()
+    } else {
+        batchTableData.value = []
+        deteleTableRef.value && deteleTableRef.value.clearSelection()
+    }
+}
+
+function getTableList() {
+    allLoading.tableLoading = true
+    post(URL_RECYCLELIST, { ...tableForm }).then((res) => {
+        if (res.code == 'ok') {
+            const { data, total } = res.data
+            deteleBusinessTable.value = data.map((item: any) => {
+                return {
+                    ...item,
+                    expectedTransactionDate: formatDate(new Date(item.expectedTransactionDate))
+                }
+            })
+            businessTotalTable.value = total
+        }
+    }).finally(() => {
+        allLoading.tableLoading = false
+    })
+}
+
+function handleSizeChange(val: number) {
+    tableForm.pageIndex = 1
+    tableForm.pageFrom = val
+    getTableList()
+}
+
+function handleCurrentChange(val: number) {
+    tableForm.pageIndex = val
+    getTableList()
+}
+
+function cancel() {
+    emits('closeVisible', 'deteleCustomerVisible')
+}
+
+function beForeCancel(done: () => void) {
+    emits('closeVisible', 'deteleCustomerVisible')
+    done()
+}
+
+onMounted(() => {
+
+})
+
+</script>
+<style lang="scss" scoped></style>

+ 40 - 35
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/detail/index.vue

@@ -7,13 +7,13 @@
         </el-link>
       </div>
       <div class="mr-8">
-        <el-select v-model="value" placeholder="请选择" style="width: 150px">
+        <el-select v-model="values" placeholder="请选择" style="width: 150px" @change="getDetail()">
           <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </div>
     </div>
     <!-- 内容 -->
-    <div class="flex-1 flex flex-col overflow-y-auto overflow-x-hidden scroll-bar">
+    <div class="flex-1 flex flex-col overflow-y-auto overflow-x-hidden scroll-bar" v-loading="allLoading.customerDetailLoading">
       <div class="w-full h-auto flex justify-between">
         <div class="bg-white shadow-md rounded-md" style="width: 46%;">
           <Information />
@@ -58,7 +58,9 @@ import { ref, reactive, onMounted, inject } from "vue";
 import type { FormInstance, FormRules } from 'element-plus'
 import { backPath } from '@/utils/tools'
 import { Edit, ArrowLeft as IconView } from '@element-plus/icons-vue'
-import { stageStatus } from '../api'
+import { URL_FETALL, URL_GETDETAIL } from '../api'
+import { post, get } from "@/utils/request";
+import { useRoute } from "vue-router";
 
 import Information from '../component/information.vue'
 import Attachment from '../component/attachment.vue'
@@ -67,40 +69,43 @@ import OperationRecord from '../component/operationRecord.vue';
 import RelatedBusiness from '../component/relatedBusiness.vue';
 import RelatedContacts from "../component/relatedContacts.vue";
 import RelatedOrders from "../component/relatedOrders.vue"
+import { number } from "echarts";
+
+const route = useRoute()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const rowId = ref(+(route.query.id || ''))
+const values = ref<any>('')
+const options = ref<optionType[]>([])
+const information = ref<any>({})
+const allLoading = reactive({
+  customerDetailLoading: false
+})
+
+function getDetail() {
+  allLoading.customerDetailLoading = true
+  post(URL_GETDETAIL, { id: values.value }).then((res) => {
+    information.value = res.data
+  }).finally(() => {
+    allLoading.customerDetailLoading = false
+  })
+}
 
-const value = ref('')
-const stageStatusVal = ref('')
-const options = [
-  {
-    value: 'Option1',
-    label: 'Option1',
-  },
-  {
-    value: 'Option2',
-    label: 'Option2',
-  },
-  {
-    value: 'Option3',
-    label: 'Option3',
-  },
-  {
-    value: 'Option4',
-    label: 'Option4',
-  },
-  {
-    value: 'Option5',
-    label: 'Option5',
-  },
-]
-
-
-function handleScroll(event: any) { // 滚表横向滚动
-  if (event.deltaY) {
-    event.preventDefault();
-    const element = event.currentTarget;
-    element.scrollLeft += event.deltaY;
-  }
+function getAllCustomer() {
+  get(URL_FETALL).then(({ data }) => {
+      options.value = data.map((item: any) => {
+        return {
+          value: item.id,
+          label: item.customName
+        }
+      })
+  })
 }
+
+onMounted(() => {
+  values.value = rowId.value || ''
+  getAllCustomer()
+  getDetail()
+})
 </script>
   
 <style lang="scss" scoped>

+ 320 - 64
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/index.vue

@@ -5,15 +5,15 @@
         <div class="flex-1 p-3 overflow-y-auto">
           <el-form :model="customerCriteriaForm" label-width="70px" style="max-width: 600px">
             <el-form-item label="客户名称">
-              <el-input v-model="customerCriteriaForm.clueName" clearable placeholder="请输入"></el-input>
+              <el-input v-model="customerCriteriaForm.customName" clearable placeholder="请输入"></el-input>
             </el-form-item>
             <el-form-item label="客户来源">
-              <el-select v-model="customerCriteriaForm.clueSourceId" placeholder="请选择">
-                <el-option v-for="item in fixedData.CustomSources" :key="item.id" :label="item.name" :value="item.id" />
+              <el-select v-model="customerCriteriaForm.customSourceId" placeholder="请选择">
+                <el-option v-for="item in fixedData.ClueSources" :key="item.id" :label="item.name" :value="item.id" />
               </el-select>
             </el-form-item>
             <el-form-item label="电话号码">
-              <el-input v-model="customerCriteriaForm.phone" clearable placeholder="请输入"></el-input>
+              <el-input v-model="customerCriteriaForm.telPhone" clearable placeholder="请输入"></el-input>
             </el-form-item>
             <el-form-item label="邮箱">
               <el-input v-model="customerCriteriaForm.email" clearable placeholder="请输入"></el-input>
@@ -52,57 +52,138 @@
     <div class="flex-1 p-5 overflow-auto">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
         <div class="flex justify-end pb-3">
-          <el-button type="primary">新建客户</el-button>
-          <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
-          <el-button type="primary" @clicl="batchDelete()">批量删除</el-button>
-          <el-button type="primary">回收站</el-button>
-          <el-button type="primary">导入</el-button>
-          <el-button type="primary">导出</el-button>
+          <el-button type="primary" @click="editCustomer(false)">新建客户</el-button>
+          <el-button type="primary" @click="showVisible('batchTransferVisible')"
+            :disabled="batchTableData.length <= 0">批量转移</el-button>
+          <el-button type="primary" @click="batchDeteleItem()" :disabled="batchTableData.length <= 0">批量删除</el-button>
+          <el-button type="primary" @click="showVisible('deteleCustomerVisible')">回收站</el-button>
+          <el-button type="primary" @click="showVisible('importVisible')">导入</el-button>
+          <el-button type="primary" @click="exportCustomerTableList()" :loading="allLoading.exoprtLoading">导出</el-button>
         </div>
         <div class="flex-1 w-full overflow-hidden">
-          <el-table ref="clueTableRef" :data="clueTable" border v-loading="allLoading.clueTableLading"
-            style="width: 100%;height: 100%;">
+          <el-table ref="customerTableRef" :data="customerTable" border v-loading="allLoading.customerTableLading"
+            style="width: 100%;height: 100%;" @selection-change="changeBatch">
             <el-table-column type="selection" width="55" />
-            <el-table-column prop="clueName" label="客户名称" width="180">
+            <el-table-column prop="customName" label="客户名称" width="180">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click.prevent="toCustomerTableDetail(scope.row)">{{ scope.row.clueName
+                <el-button link type="primary" size="large" @click.prevent="toCustomerTableDetail(scope.row)">{{
+                  scope.row.customName
                 }}</el-button>
               </template>
             </el-table-column>
-            <el-table-column prop="clueSourceId" label="客户来源" width="180"></el-table-column>
-            <el-table-column prop="phone" label="公司电话" width="180"></el-table-column>
-            <el-table-column prop="email" label="邮箱" width="180"></el-table-column>
-            <el-table-column prop="customerIndustryId" label="客户行业" width="180"></el-table-column>
-            <el-table-column prop="customerLevelId" label="客户级别" width="180"></el-table-column>
-            <el-table-column prop="inchargerId" label="负责人" width="190"></el-table-column>
-            <el-table-column prop="createName" label="创建人" width="180"></el-table-column>
-            <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
+            <el-table-column prop="customSourceValue" label="客户来源" width="180"></el-table-column>
+            <el-table-column prop="companyPhone" label="公司电话" width="180"></el-table-column>
+            <el-table-column prop="email" label="邮箱" width="200"></el-table-column>
+            <el-table-column prop="customerIndustryValue" label="客户行业" width="180"></el-table-column>
+            <el-table-column prop="customerLevelValue" label="客户级别" width="180"></el-table-column>
+            <el-table-column prop="inchargerName" label="负责人" width="190"></el-table-column>
+            <el-table-column prop="creatorName" label="创建人" width="180"></el-table-column>
+            <el-table-column prop="newCreateTime" label="创建时间" width="180"></el-table-column>
             <el-table-column label="操作" fixed="right" width="200">
               <template #default="scope">
-                <el-button link type="primary" size="large">编辑</el-button>
-                <el-button link type="primary" size="large">新建客户</el-button>
-                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.$index)">删除</el-button>
+                <el-button link type="primary" size="large" @click="editCustomer(scope.row)">编辑</el-button>
+                <el-button link type="primary" size="large" @click="newTask(scope.row)">新建任务</el-button>
+                <el-button link type="danger" size="large"
+                  @click="customerDeteleItem(scope.row.id, scope.row.customName)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
         </div>
         <div class="flex justify-end pt-3">
-          <el-pagination layout="total, prev, pager, next, sizes" :total="clueTotalTable" :hide-on-single-page="true" />
+          <el-pagination layout="total, prev, pager, next, sizes" :total="customerTotalTable" :hide-on-single-page="true"
+            @size-change="handleSizeChange" @current-change="handleCurrentChange" />
         </div>
       </div>
     </div>
 
-    <!-- 弹窗 -->
-    
+    <!-- 新建客户 -->
+    <el-dialog v-model="allVisible.editCustomerVisible" width="1000" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">{{ allText.editCustomerText }}</h4>
+          <div>
+            <el-button type="primary" :loading="allLoading.editCustomerSaveLoading"
+              @click="editCustomerSave(true)">保存并新建</el-button>
+            <el-button type="primary" :loading="allLoading.editCustomerSaveLoading"
+              @click="editCustomerSave(false)">保存</el-button>
+            <el-button @click="closeVisible('editCustomerVisible')">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
+        <div class="ml-4 mr-4">
+          <GenerateForm ref="customerTemplateRef" :data="customerTemplate" :value="customerTemplateValue"
+            :key="customerTemplateRefKey" v-loading="allLoading.customerTemplateRefLoading" />
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 批量转移 -->
+    <el-dialog v-model="allVisible.batchTransferVisible" width="600" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">{{ allText.transferText }}</h4>
+          <div>
+            <el-button type="primary" v-loading="allLoading.transferLoading" @click="transferBusiness()">转移</el-button>
+            <el-button @click="allVisible.batchTransferVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="scroll-bar m-6">
+        <div class="flex mb-4">
+          <div class="w-20 flex items-center justify-end pr-4">转移至:</div>
+          <el-select v-model="transferPersonnel" placeholder="请选择" class="flex1">
+            <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </div>
+        <div class="pl-3 text-[#e94a4a]">转移后,将看不到此客户</div>
+      </div>
+    </el-dialog>
+
+    <el-dialog v-model="allVisible.importVisible" width="680" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">导入产品</h4>
+          <div class="flex">
+            <el-upload class="upload-demo mr-3" :limit="1" :show-file-list="false" accept=".xlsx"
+              :http-request="importBusiness">
+              <el-button type="primary" :loading="allLoading.importLoading">导入</el-button>
+            </el-upload>
+            <el-button @click="allVisible.importVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="p-8">
+        <div class="ml-4 mr-4">
+          <div class="flex items-center">1、点击下载 <el-link type="primary"
+              @click="downloadTemplate(IMPORTMOD, allText.importText)">{{ allText.importText }}</el-link></div>
+          <div class="mt-4">2、填写excel文件、客户名称必填</div>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 任务 -->
+    <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
+      @close="closeVisible('taskModalVisible')" @submit="submitForm" :title="'新建任务'"
+      :disabled-list="['taskType', 'customId']" />
+
+    <!-- 回收站 -->
+    <DeteleBusiness :visibles="allVisible.deteleCustomerVisible" @closeVisible="closeVisible" />
   </div>
 </template>
 
 <script lang="ts" setup>
 import { ref, reactive, onMounted, inject } from "vue";
-import { MOD, GETSYSFILED, GETPERSONNEL, URL_TABLELIST } from './api.ts'
-import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate } from '@/utils/tools'
-import { post, get } from "@/utils/request";
+import { MOD, GETSYSFILED, GETPERSONNEL, URL_TABLELIST, URL_TEMPLALE, URL_EDITSAVE, URL_DETELER, URL_CLAIM, IMPORTMOD, GETALLCLUE } from './api.ts'
+import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate, getTemplateKey, createTaskFromType, confirmAction, downloadFile, downloadTemplate } from '@/utils/tools'
+import { post, get, uploadFile } from "@/utils/request";
 import { useRouter, useRoute } from "vue-router";
+import { GenerateForm } from '@zmjs/form-design';
+import { createTask } from "@/components/TaskModal/taskFunction";
+import { ElTable, UploadRequestOptions } from "element-plus";
+
+import TaskModal from '@/components/TaskModal/index.vue'
+import DeteleBusiness from './component/deteleTables.vue'
 
 // 定义类型
 interface fixedDataInterface {
@@ -121,9 +202,9 @@ interface personnelInterface {
 }
 
 interface customerCriteriaFormType { // 线索筛选条件类型
-  clueName: string,
-  clueSourceId: string | number,
-  phone: string,
+  customName: string,
+  customSourceId: string | number,
+  telPhone: string,
   email: string,
   customerIndustryId: string | number,
   customerLevelId: string | number,
@@ -135,13 +216,12 @@ interface customerCriteriaFormType { // 线索筛选条件类型
 }
 
 // 定义变量
-const route = useRoute()
 const router = useRouter()
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const customerCriteriaForm = reactive<customerCriteriaFormType>({ // 筛选条件form
-  clueName: '',
-  clueSourceId: '',
-  phone: '',
+  customName: '',
+  customSourceId: '',
+  telPhone: '',
   email: '',
   customerIndustryId: '',
   customerLevelId: '',
@@ -151,60 +231,214 @@ const customerCriteriaForm = reactive<customerCriteriaFormType>({ // 筛选条
   pageIndex: 1,
   pageFrom: 10
 })
-const clueTableTotal = ref(0)
+const customerTable = ref([]) // 线索table数据
+const customerTotalTable = ref(0) // 线索 table 数据总数
 const allLoading = reactive({
-  clueTableLading: false,
+  customerTableLading: false,
+  editCustomerSaveLoading: false,
+  customerTemplateRefLoading: false,
+  transferLoading: false,
+  exoprtLoading: false,
+  importLoading: false
+})
+const allVisible = reactive({
+  editCustomerVisible: false,
+  taskModalVisible: false,
+  deteleCustomerVisible: false,
+  batchTransferVisible: false,
+  importVisible: false
+})
+const allText = reactive({
+  editCustomerText: '新建客户',
+  transferText: '批量转移',
+  importText: '客户导入模板.xlsx',
+  exportText: '客户表导出.xlsx'
 })
 const fixedData = reactive({
-  CustomSources: [] as fixedDataInterface[],
+  ClueSources: [] as fixedDataInterface[],
   CustomIndustry: [] as fixedDataInterface[],
   CustomLevel: [] as fixedDataInterface[],
   Personnel: [] as personnelInterface[]
 })
-const clueTable = ref([{ clueName: '客户名称', clueSourceId: '客户来源', id: 123456789 }]) // 线索table数据
-const clueTotalTable = ref(0) // 线索 table 数据总数
+
+const customerTemplate = ref({
+  list: [],
+  config: {}
+})
+const customerTemplateRef = ref<typeof GenerateForm>() // 自定义表单dom
+const customerTemplateValue = ref({})
+const customerTemplateRefKey = ref(1)
+const taskModalForm = ref({})
+const taskLoading = ref<saveLoadingType>('1')
+const batchTableData = ref<any>([])
+const customerTableRef = ref<InstanceType<typeof ElTable>>()
+const transferPersonnel = ref('')
+
 
 // 定义方法
-function searchTable() {
-  getClueTable()
+async function importBusiness(param: UploadRequestOptions) {
+  allLoading.importLoading = true
+  const formData = new FormData();
+  formData.append('multipartFile', param.file)
+  const res = await uploadFile('接口名称', formData).finally(() => {
+    allLoading.importLoading = false
+  })
+  if (res.code == 'ok') {
+    globalPopup?.showSuccess('导入成功' || '')
+    getCustomerTable()
+    return
+  }
+  globalPopup?.showError(res.msg || '')
 }
 
-function resetTable() {
-  let newResetForm = resetFromValue(customerCriteriaForm, { startTime: getFirstDayOfMonth(new Date()), endTime: formatDate(new Date()), pageIndex: 1, pageFrom: 10 })
-  Object.assign(customerCriteriaForm, newResetForm)
-  getClueTable()
+function exportCustomerTableList() {
+  allLoading.exoprtLoading = true
+  let valueForm = getFromValue(customerCriteriaForm)
+  post('接口名称', { ...valueForm }).then((res) => {
+    downloadFile(res.data, allText.exportText)
+  }).finally(() => {
+    allLoading.exoprtLoading = false
+  })
+}
+
+function transferBusiness() {
+  const ids = batchTableData.value.map((item: any) => item.id).join(',')
+  allLoading.transferLoading = true
+  post(URL_CLAIM, { ids, inchargerId: transferPersonnel.value }).then(() => {
+    transferPersonnel.value = ''
+    globalPopup?.showSuccess('转移成功')
+    closeVisible('batchTransferVisible')
+    getCustomerTable()
+  }).finally(() => {
+    allLoading.transferLoading = false
+  })
+}
+
+function batchDeteleItem() {
+  const value = batchTableData.value.map((item: any) => item.id).join(',')
+  const label = batchTableData.value.map((item: any) => item.customName).join(',')
+  customerDeteleItem(value, label, true)
 }
 
-function deleteRow(_row: any) {
-  console.log('点击了删除')
+function customerDeteleItem(value: string | number, label: string, batch: boolean = false) {
+  confirmAction(`确定${batch ? '批量' : ''}删除【${label}】客户吗?`).then(() => {
+    post(URL_DETELER, { ids: value }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      changeBatch(false)
+      getCustomerTable()
+    }).catch((err) => {
+      globalPopup?.showError(err.message)
+    })
+  })
 }
 
-function batchTransfer() {
-  console.log('点击了批量转移')
+function submitForm(submitData: any, isClose: boolean) {
+  taskLoading.value = '2'
+  createTask(submitData, isClose).then((res) => {
+    const { saveLoading, isClose } = res
+    taskLoading.value = saveLoading
+    allVisible.taskModalVisible = isClose
+    globalPopup?.showSuccess('新增成功')
+  }).catch((err) => {
+    const { saveLoading, isClose, message } = err
+    taskLoading.value = saveLoading
+    allVisible.taskModalVisible = isClose
+    globalPopup?.showError(message)
+  })
 }
 
-function batchDelete() {
-  console.log('批量删除')
+function newTask(item: any) {
+  const { id } = item
+  taskModalForm.value = { ...createTaskFromType(0), customId: id, }
+  showVisible('taskModalVisible')
+}
+
+function editCustomerSave(flag: boolean) {
+  customerTemplateRef.value?.getData().then((res: any) => {
+    allLoading.editCustomerSaveLoading = true
+    post(URL_EDITSAVE, { ...res }).then((_res) => {
+      allVisible.editCustomerVisible = flag
+      globalPopup?.showSuccess('保存成功')
+      if (flag) {
+        customerTemplateRef.value?.reset()
+      }
+      getCustomerTable()
+    }).finally(() => {
+      allLoading.editCustomerSaveLoading = false
+    })
+  }).catch((_err: any) => {
+    console.log(_err)
+    globalPopup?.showError('请填写完整')
+  })
+}
+
+function editCustomer(row: any) { // row 有数据代表编辑
+  if (row) {
+    const { id, companyPhone, customName, inchargerId, createTime, customSourceId, customerIndustryId, customerLevelId, email } = row
+    const formVal = {
+      id, customName, inchargerId, customerIndustryId, customerLevelId, email, customSourceId,
+      createTime: formatDate(new Date(createTime)),
+      telPhone: companyPhone,
+    }
+    allLoading.customerTemplateRefLoading = true
+    setTimeout(() => {
+      customerTemplateRefKey.value++
+      allLoading.customerTemplateRefLoading = false
+    }, 1000);
+    customerTemplateValue.value = formVal
+    allText.editCustomerText = '编辑客户'
+  } else { // 没有数据代表新建
+    allText.editCustomerText = '新增客户'
+  }
+  showVisible('editCustomerVisible')
+}
+
+function changeBatch(flag: boolean = true) {
+  if (flag) {
+    batchTableData.value = customerTableRef.value && customerTableRef.value.getSelectionRows()
+  } else {
+    batchTableData.value = []
+    customerTableRef.value && customerTableRef.value.clearSelection()
+  }
 }
 
 function toCustomerTableDetail(_row: any) {
-  console.log('点击跳转详情')
-  router.push({path: `${MOD}/detail`, query: {id: _row.id}})
+  router.push({ path: `${MOD}/detail`, query: { id: _row.id } })
+}
+
+function searchTable() {
+  getCustomerTable()
 }
 
-function getClueTable() {
+function resetTable() {
+  let newResetForm = resetFromValue(customerCriteriaForm, { startTime: getFirstDayOfMonth(new Date()), endTime: formatDate(new Date()), pageIndex: 1, pageFrom: 10 })
+  Object.assign(customerCriteriaForm, newResetForm)
+  getCustomerTable()
+}
+
+function getCustomerTable() {
   let valueForm = getFromValue(customerCriteriaForm)
-  post(URL_TABLELIST, { ...valueForm}).then((res) => {
+  allLoading.customerTableLading = true
+  post(URL_TABLELIST, { ...valueForm }).then((res) => {
     const { data, total } = res.data
-    clueTable.value = data
-    clueTableTotal.value = total
+    customerTable.value = (data || []).map((item: any) => {
+      return {
+        ...item,
+        newCreateTime: formatDate(new Date(item.createTime)),
+      }
+    })
+    customerTotalTable.value = total
   }).finally(() => {
-
+    allLoading.customerTableLading = false
   })
 }
 
 async function getSystemField() {
-  const systemField = getAllListByCode(['客户来源', '客户行业', '客户级别'])
+  const systemField = getAllListByCode(['线索来源', '客户行业', '客户级别'])
   for (let i in systemField) {
     const { data } = await get(`${GETSYSFILED}?code=${systemField[i]}`)
     for (let key of Object.keys(fixedData)) {
@@ -221,11 +455,33 @@ async function getSystemField() {
       id, name, phone, jobNumber
     }
   })
+
+  const res = await get(URL_TEMPLALE)
+  customerTemplate.value = JSON.parse(res.data[0].config)
+}
+
+function showVisible(type: keyof typeof allVisible) {
+  allVisible[type] = true
+}
+
+function closeVisible(type: keyof typeof allVisible) {
+  allVisible[type] = false
+}
+
+function handleSizeChange(val: number) {
+  customerCriteriaForm.pageIndex = 1
+  customerCriteriaForm.pageFrom = val
+  getCustomerTable()
+}
+
+function handleCurrentChange(val: number) {
+  customerCriteriaForm.pageIndex = val
+  getCustomerTable()
 }
 
 onMounted(() => {
   getSystemField()
-  getClueTable()
+  getCustomerTable()
 })
 </script>
 

+ 4 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts

@@ -21,6 +21,10 @@ type ListByCodeType = (
   | "订单类型"
 )[];
 
+type templateKey = { // 自定义模板键值
+  [key: string]: any;
+}
+
 type saveLoadingType = "1" | "2" | "3" | "4"; //1是没有保存, 2是正在保存, 3是保存成功, 4是保存失败
 
 type TASK_VALUE_TYPE = 0 | 1 | 2 | 3; //0是客户, 1是商机, 2是销售订单 ,3是线索

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

@@ -215,3 +215,25 @@ export function downloadTemplate(mod: any, fileName: string) {
     downloadFile(res.data, fileName)
   });
 }
+
+/**
+ * 取出自定义模板需要的 key
+ * @param list 自定义模板数据(list)
+ * @returns 
+ */
+export function getTemplateKey(list: Array<any>) {
+  const parsedList = list;
+  const models: Array<string> = [];
+  parsedList.forEach((item: any) => {
+    if (item.type === 'grid') {
+      item.columns.forEach((column: any) => {
+        column.list.forEach((subItem: any) => {
+          models.push(subItem.model);
+        });
+      });
+    } else {
+      models.push(item.model);
+    }
+  });
+  return models;
+}

+ 3 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/CustomController.java

@@ -6,6 +6,7 @@ import com.management.platform.entity.Clue;
 import com.management.platform.entity.Custom;
 import com.management.platform.entity.UploadFile;
 import com.management.platform.entity.User;
+import com.management.platform.mapper.ClueMapper;
 import com.management.platform.mapper.CustomMapper;
 import com.management.platform.mapper.UserMapper;
 import com.management.platform.service.CustomService;
@@ -40,6 +41,8 @@ public class CustomController {
     @Autowired
     private CustomMapper customMapper;
     @Autowired
+    private ClueMapper clueMapper;
+    @Autowired
     private UserMapper userMapper;
 
     @RequestMapping("list")

+ 3 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/ClueMapper.java

@@ -2,9 +2,11 @@ package com.management.platform.mapper;
 
 import com.management.platform.entity.Clue;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.management.platform.util.HttpRespMsg;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.Map;
 
@@ -38,4 +40,5 @@ public interface ClueMapper extends BaseMapper<Clue> {
     Clue selectById2Info(@Param("id") Integer id);
 
     Map<String, Object> getDataSummary(Integer companyId, String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
+
 }

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

@@ -95,11 +95,12 @@ public class CustomServiceImpl extends ServiceImpl<CustomMapper, Custom> impleme
                 return msg;
             }
             if (customMapper.selectCount(new QueryWrapper<Custom>().eq("tel_phone", custom.getTelPhone())) > 0) {
-                msg.setError("电话号码重复了");
+//                msg.setError("电话号码重复了");
                 return msg;
             }
             custom.setCreateTime(new Date());
             custom.setCreatorId(user.getId());
+            custom.setIsDelete(0);
             ActionLog actionLog = new ActionLog();
             actionLog.setCode("custom");
             actionLog.setCreatTime(new Date());
@@ -243,12 +244,12 @@ public class CustomServiceImpl extends ServiceImpl<CustomMapper, Custom> impleme
         boolean isAll = sysFunctionService.hasPriviledge(user.getRoleId(), "查看全部客户");
         boolean isNotAll = sysFunctionService.hasPriviledge(user.getRoleId(), "查看负责部门客户");
         List<Custom> list = new ArrayList<>();
-        int i = 0;
-        if (!isAll) {
+        int i = 1;
+        if (isAll) {
             //查看全部线索
             list = customMapper.getList(custom);
             i = customMapper.getTotal(custom);
-        } else if (!isNotAll) {
+        } else if (isNotAll) {
             //查看负责部门线索 找出所处部门下所有的负责人
             list = customMapper.getList1(custom);
             i = customMapper.getTotal1(custom);

+ 1 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/ProductServiceImpl.java

@@ -287,7 +287,6 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
                             default:cell.setCellType(CellType.STRING);
                         }
                     }
-//                    Class<?> productClass = Class.forName("com.management.platform.entity.Product");
                     //校验当前列是否为必填
                     JSONObject options = item.getJSONObject("options");
                     JSONObject rules = options.getJSONObject("rules");
@@ -320,7 +319,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
                             if (first.isPresent()) {
                                 product.setInchargerId(first.get().getId());
                             } else {
-                                msg.setError("["+userName+"]在系统中不存在");
+                                msg.setError("负责人["+userName+"]在系统中不存在");
                                 return msg;
                             }
                         }

+ 115 - 12
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java

@@ -34,6 +34,7 @@ import java.math.RoundingMode;
 import java.text.NumberFormat;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.Year;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
@@ -208,11 +209,12 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
         File file = new File(fileName == null ? "file" : fileName);
         User user = userMapper.selectById(request.getHeader("token"));
         Integer companyId = user.getCompanyId();
+        DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        DateTimeFormatter df1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         WxCorpInfo wxCorpInfo = wxCorpInfoService.getOne(new LambdaQueryWrapper<WxCorpInfo>().eq(WxCorpInfo::getCompanyId, companyId));
         List<SalesOrder> orderList = salesOrderMapper.selectList(new LambdaQueryWrapper<SalesOrder>().eq(SalesOrder::getCompanyId, companyId));
         List<User> userList = userMapper.selectList(new LambdaQueryWrapper<User>().eq(User::getCompanyId, companyId));
-        List<SysDict> sysDictOfProductType = sysDictMapper.selectList(new LambdaQueryWrapper<SysDict>().eq(SysDict::getCompanyId, companyId).eq(SysDict::getCode, "ProductType"));
-        List<SysDict> sysDictOfProductUnit = sysDictMapper.selectList(new LambdaQueryWrapper<SysDict>().eq(SysDict::getCompanyId, companyId).eq(SysDict::getCode, "ProductUnit"));
+        List<SysDict> sysDictOfOrderType = sysDictMapper.selectList(new LambdaQueryWrapper<SysDict>().eq(SysDict::getCompanyId, companyId).eq(SysDict::getCode, "OrderType"));
         InputStream inputStream = null;
         OutputStream outputStream = null;
         try {
@@ -270,6 +272,16 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
                             userNameList.add(cell.getStringCellValue());
                         }
                     }
+                    if(modelName.equals("customSigner")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            userNameList.add(cell.getStringCellValue());
+                        }
+                    }
+                    if(modelName.equals("companySigner")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            userNameList.add(cell.getStringCellValue());
+                        }
+                    }
 
                 }
             }
@@ -310,9 +322,6 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
                             default:cell.setCellType(CellType.STRING);
                         }
                     }
-//                    Class<?> productClass = Class.forName("com.management.platform.entity.Product");
-                    Class<Product> productClass = Product.class;
-                    Method method = productClass.getMethod(setter, String.class);
                     //校验当前列是否为必填
                     JSONObject options = item.getJSONObject("options");
                     JSONObject rules = options.getJSONObject("rules");
@@ -325,14 +334,70 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
                     }
                     if(modelName.equals("orderCode")){
                         if(!StringUtils.isEmpty(cell.getStringCellValue())){
-                            //系统中同公司已存在的产品编码 更新
+                            //系统中同公司已存在的订单编码 更新
                             Optional<SalesOrder> first = orderList.stream().filter(p ->p.getOrderCode()!=null&& p.getOrderCode().equals(cell.getStringCellValue())).findFirst();
                             if(first.isPresent()){
                                 order.setId(first.get().getId());
                             }
+                            order.setOrderCode(cell.getStringCellValue());
                         }
-                    }
-                    if(modelName.equals("inchargerId")){
+                    }else if(modelName.equals("customId")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            //系统中同公司已存在的订单编码 更新
+                            Custom one = customService.getOne(new LambdaQueryWrapper<Custom>().eq(Custom::getCompanyId, companyId).eq(Custom::getCustomName, cell.getStringCellValue()));
+                            if(one!=null){
+                                order.setCustomId(one.getId());
+                            }else {
+                                msg.setError("客户["+cell.getStringCellValue()+"]在系统中不存在");
+                                return msg;
+                            }
+                        }
+                    } else if(modelName.equals("businessOpportunityId")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            //系统中同公司已存在的订单编码 更新
+                            BusinessOpportunity one = businessOpportunityService.getOne(new LambdaQueryWrapper<BusinessOpportunity>().eq(BusinessOpportunity::getCompanyId, companyId).eq(BusinessOpportunity::getName, cell.getStringCellValue()));
+                            if(one!=null){
+                                order.setBusinessOpportunityId(one.getId());
+                            }else {
+                                msg.setError("商机["+cell.getStringCellValue()+"]在系统中不存在");
+                                return msg;
+                            }
+                        }
+                    } else if(modelName.equals("price")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setPrice(new BigDecimal(cell.getStringCellValue()));
+                        }
+                    } else if(modelName.equals("receivedPayment")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setReceivedPayment(new BigDecimal(cell.getStringCellValue()));
+                        }
+                    } else if(modelName.equals("unReceivedPayment")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setUnReceivedPayment(new BigDecimal(cell.getStringCellValue()));
+                        }
+                    }else if(modelName.equals("type")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            Optional<SysDict> first = sysDictOfOrderType.stream().filter(s -> s.getName().equals(cell.getStringCellValue())).findFirst();
+                            if(first.isPresent()){
+                                order.setType(first.get().getId());
+                            }else {
+                                msg.setError("订单类型["+cell.getStringCellValue()+"]不存在,请在系统字典中增加");
+                                return msg;
+                            }
+                        }
+                    }else if(modelName.equals("placeTime")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setPlaceTime(LocalDateTime.parse(cell.getStringCellValue(),df1));
+                        }
+                    }else if(modelName.equals("orderStartDate")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setOrderStartDate(LocalDate.parse(cell.getStringCellValue(),df));
+                        }
+                    }else if(modelName.equals("orderEndDate")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            order.setOrderEndDate(LocalDate.parse(cell.getStringCellValue(),df));
+                        }
+                    }else if(modelName.equals("inchargerId")){
                         if(!StringUtils.isEmpty(cell.getStringCellValue())){
                             String userName = cell.getStringCellValue();
                             Optional<User> first;
@@ -345,12 +410,50 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
                             if (first.isPresent()) {
                                 order.setInchargerId(first.get().getId());
                             } else {
-                                throw new Exception("["+userName+"]在系统中不存在");
+                                msg.setError("负责人["+userName+"]在系统中不存在");
+                                return msg;
                             }
                         }
-                    }
-                    if(!StringUtils.isEmpty(cell.getStringCellValue())){
-                        method.invoke(order,cell.getStringCellValue());
+                    }else if(modelName.equals("customSigner")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            String userName = cell.getStringCellValue();
+                            Optional<User> first;
+                            if(wxCorpInfo!=null&&wxCorpInfo.getSaasSyncContact()==1){
+                                Optional<User> optional = targetUserList.stream().filter(tl -> tl.getName().equals(userName)).findFirst();
+                                first= userList.stream().filter(u ->(u.getJobNumber()!=null&&u.getJobNumber().equals(userName))||(optional.isPresent()&&u.getCorpwxUserid()!=null&&u.getCorpwxUserid().equals(optional.get().getCorpwxUserid()))).findFirst();
+                            }else {
+                                first= userList.stream().filter(u -> u.getName().equals(userName)||(u.getJobNumber()!=null&&u.getJobNumber().equals(userName))).findFirst();
+                            }
+                            if (first.isPresent()) {
+                                order.setCustomSigner(first.get().getId());
+                            } else {
+                                msg.setError("客户签约人["+userName+"]在系统中不存在");
+                                return msg;
+                            }
+                        }
+                    }else if(modelName.equals("companySigner")){
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            String userName = cell.getStringCellValue();
+                            Optional<User> first;
+                            if(wxCorpInfo!=null&&wxCorpInfo.getSaasSyncContact()==1){
+                                Optional<User> optional = targetUserList.stream().filter(tl -> tl.getName().equals(userName)).findFirst();
+                                first= userList.stream().filter(u ->(u.getJobNumber()!=null&&u.getJobNumber().equals(userName))||(optional.isPresent()&&u.getCorpwxUserid()!=null&&u.getCorpwxUserid().equals(optional.get().getCorpwxUserid()))).findFirst();
+                            }else {
+                                first= userList.stream().filter(u -> u.getName().equals(userName)||(u.getJobNumber()!=null&&u.getJobNumber().equals(userName))).findFirst();
+                            }
+                            if (first.isPresent()) {
+                                order.setCompanySigner(first.get().getId());
+                            } else {
+                                msg.setError("公司签约人["+userName+"]在系统中不存在");
+                                return msg;
+                            }
+                        }
+                    }else{
+                        if(!StringUtils.isEmpty(cell.getStringCellValue())){
+                            Class<SalesOrder> orderClass = SalesOrder.class;
+                            Method method = orderClass.getMethod(setter, String.class);
+                            method.invoke(order,cell.getStringCellValue());
+                        }
                     }
                 }
                 importOrderList.add(order);

+ 15 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/CustomMapper.xml

@@ -48,10 +48,15 @@
         email,
         company_phone,
         create_time,
+        customer_level_id,
         (select name from sys_dict where customer_level_id = id and code = 'CustomLevel') customerLevelValue,
+        customer_industry_id,
         (select name from sys_dict where customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
+        custom_source_id,
         (select name from sys_dict where custom_source_id = id and code = 'CustomSources') customSourceValue,
+        incharger_id,
         (select `name` from `user` where id = incharger_id) inchargerName,
+        creator_id,
         (select `name` from `user` where id = creator_id) creatorName
         from custom
         where company_id = #{companyId}
@@ -118,10 +123,15 @@
         email,
         company_phone,
         create_time,
+        customer_level_id,
         (select name from sys_dict where customer_level_id = id and code = 'CustomLevel') customerLevelValue,
+        customer_industry_id,
         (select name from sys_dict where customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
+        custom_source_id,
         (select name from sys_dict where custom_source_id = id and code = 'CustomSources') customSourceValue,
+        incharger_id,
         (select `name` from `user` where id = incharger_id) inchargerName,
+        creator_id,
         (select `name` from `user` where id = creator_id) creatorName
         from custom
         where company_id = #{companyId}
@@ -194,10 +204,15 @@
         email,
         company_phone,
         create_time,
+        customer_level_id,
         (select name from sys_dict where customer_level_id = id and code = 'CustomLevel') customerLevelValue,
+        customer_industry_id,
         (select name from sys_dict where customer_industry_id = id and code = 'CustomIndustry') customerIndustryValue,
+        custom_source_id,
         (select name from sys_dict where custom_source_id = id and code = 'CustomSources') customSourceValue,
+        incharger_id,
         (select `name` from `user` where id = incharger_id) inchargerName,
+        creator_id,
         (select `name` from `user` where id = creator_id) creatorName
         from custom
         where company_id = #{companyId}

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

@@ -2573,7 +2573,7 @@ public class ReportController {
         return reportService.pushProjectReportToSap(pushDate,reportId);
     }
 
-    //todo:推送工时管家工时考勤数据到SAP
+    //todo:取消推送工时管家工时考勤数据到SAP
     @RequestMapping("/cancelHasPushForSap")
     public HttpRespMsg cancelHasPushForSap(String uuids){
         return reportService.cancelHasPushForSap(uuids);

+ 153 - 26
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserWithBeisenController.java

@@ -448,7 +448,8 @@ public class UserWithBeisenController {
         DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd");
         DateTimeFormatter df1=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
         DateTimeFormatter df2=DateTimeFormatter.ofPattern("HH:mm");
-        DateTimeFormatter df3=DateTimeFormatter.ofPattern("HH:mm:ss");
+        DateTimeFormatter df3=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+        DateTimeFormatter df4=DateTimeFormatter.ofPattern("HH:mm:ss");
         User user = userMapper.selectById(request.getHeader("token"));
         Integer companyId = user.getCompanyId();
         TimeType timeType = timeTypeMapper.selectById(companyId);
@@ -481,44 +482,170 @@ public class UserWithBeisenController {
             boolean workDay = WorkDayCalculateUtils.isWorkDay(LocalDate.parse(createDate,df));
             //todo:针对景昱 工作日默认以8小时工作制度加上加班时长 非工作日以加班时长为准
             Double workTime;
-            Duration between = Duration.between(LocalTime.parse(firstCard, df3), LocalTime.parse(lastCard, df3));
-            if(between.toHours()>8){
-                workTime=8.0;
-            }else if(between.toHours()<0) {
-                workTime=0.0;
+            Duration between = Duration.between(LocalTime.parse(firstCard, df4), LocalTime.parse(lastCard, df4));
+            LocalTime min = LocalTime.parse(firstCard, df4);
+            LocalTime max = LocalTime.parse(lastCard, df4);
+            if(timeType.getCompanyId()==5978){
+                if(workDay){
+                    workTime=8.0;
+                }else {
+                    workTime=0.0;
+                }
             }else {
-                BigDecimal decimal = new BigDecimal(between.toMinutes());
-                decimal=decimal.divide(new BigDecimal(60),1,RoundingMode.HALF_UP);
-                workTime=decimal.doubleValue();
+                if(between.toHours()<0){
+                    workTime=0.0;
+                }else {
+                    BigDecimal decimal = new BigDecimal(between.toMinutes());
+                    decimal=decimal.divide(new BigDecimal(60),1,RoundingMode.HALF_UP);
+                    workTime=decimal.doubleValue();
+                }
             }
             Stream<JSONObject> overTimeStream = allOverTimeList.stream().map(elment -> (JSONObject) elment);
             Stream<JSONObject> vacationStream = vacationList.stream().map(elment -> (JSONObject) elment);
             Optional<UserWithBeisen> beisen = userWithBeisenList.stream().filter(u -> u.getJobNumber() != null && u.getJobNumber().equals(first.get().getJobNumber())).findFirst();
             if(beisen.isPresent()){
+                //todo:之前的逻辑
+//                        List<JSONObject> overTimeList = overTimeStream.filter(a -> a.getString("StaffId").equals(beisen.get().getUserId())
+//                                && (a.getIntValue("ApproveStatus") == 2||a.getIntValue("ApproveStatus") == 1)
+//                                &&LocalDateTime.parse(a.getString("StartDate"),df1).toLocalDate().isEqual(localDate)).collect(Collectors.toList());
                 List<JSONObject> overTimeList = overTimeStream.filter(a -> a.getString("StaffId").equals(beisen.get().getUserId())
                         && (a.getIntValue("ApproveStatus") == 2||a.getIntValue("ApproveStatus") == 1)).collect(Collectors.toList());
-                if(overTimeList.size()>0){
-                    double actualOverTimeDuration = overTimeList.stream().mapToDouble(i -> i.getDouble("OverTimeDuration")).sum();
-                    if(workDay){
-                        workTime= workTime+actualOverTimeDuration;
+                //加班数据可能存在结束日期是当前日期的情况的情况
+                BigDecimal overTimeBigDecimal = new BigDecimal(0);
+                LocalDate localDate = LocalDate.parse(createDate, df);
+                for (JSONObject o : overTimeList) {
+                    //存在开始日期为当前日期的数据以及结束日期为当天日期的数据 分开计算
+                    if(LocalDateTime.parse(o.getString("StartDate"),df1).toLocalDate().isEqual(localDate)){
+                        //存在开始日期为当天的数据
+                        if(LocalDateTime.parse(o.getString("StartDate"),df1).toLocalDate().isEqual(LocalDateTime.parse(o.getString("StopDate"),df1).toLocalDate())){
+                            //开始日期和结束日期是相同的情况 说明是加班区间只存在于当天的情况
+                            if(max.isAfter(LocalTime.parse("19:00:00",df4))){
+                                //判断打卡时间是不是大于19:00 大于才算加班 通过打卡计算加班时长 与加班单作比较 取小
+                                Duration timeDurantion = Duration.between(LocalTime.parse("19:00:00", df4), max);
+                                //如果结束时间大于13:00
+                                if(max.isAfter(LocalTime.parse("13:00:00",df4))){
+                                    timeDurantion.minusHours(1);
+                                }
+                                //如果结束时间大于18:00
+                                if(max.isAfter(LocalTime.parse("18:00:00",df4))){
+                                    timeDurantion.minusMinutes(30);
+                                }
+                                long l = timeDurantion.toMinutes() / 60;
+                                if(l<o.getDouble("OverTimeDuration")){
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l));
+                                }else {
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(o.getDouble("OverTimeDuration")));
+                                }
+                            }
+                        }else {
+                            //开始日期和结束日期是不相同的情况 说明是加班区间存在加班到第二天的情况
+                            LocalDateTime start = LocalDateTime.parse(o.getString("StartDate"), df1);
+                            LocalDateTime stop = start.toLocalDate().atTime(LocalTime.MAX);
+                            Duration duration = Duration.between(start, stop);
+                            if(stop.toLocalTime().isAfter(LocalTime.parse("13:00:00",df4))){
+                                duration.minusHours(1);
+                            }
+                            //如果结束时间大于18:00
+                            if(stop.toLocalTime().isAfter(LocalTime.parse("18:00:00",df4))){
+                                duration.minusMinutes(30);
+                            }
+                            long l = duration.toMinutes() / 60;
+                            if(max.isAfter(LocalTime.parse("19:00:00",df4))){
+                                //判断打卡时间是不是大于19:00 通过打卡计算加班时长 与加班单作比较 取小
+                                Duration timeDurantion = Duration.between(LocalTime.parse("19:00:00", df4), max);
+                                long l1 = timeDurantion.toMinutes() / 60;
+                                if(l1<l){
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l1));
+                                }else {
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l));
+                                }
+                            }
+                        }
+                    }else if(LocalDateTime.parse(o.getString("StopDate"),df1).toLocalDate().isEqual(localDate)){
+                        //存在结束日期为当天的数据
+                        if(LocalDateTime.parse(o.getString("StartDate"),df1).toLocalDate().isEqual(LocalDateTime.parse(o.getString("StopDate"),df1).toLocalDate())){
+                            //开始日期和结束日期是相同的情况 说明是加班区间只存在于当天的情况
+                            if(max.isAfter(LocalTime.parse("19:00:00",df4))){
+                                //判断打卡时间是不是大于19:00 通过打卡计算加班时长 与加班单作比较 取小
+                                Duration timeDurantion = Duration.between(LocalTime.parse("19:00:00", df4), max);
+                                //如果结束时间大于13:00
+                                if(max.isAfter(LocalTime.parse("13:00:00",df4))){
+                                    timeDurantion.minusHours(1);
+                                }
+                                //如果结束时间大于18:00
+                                if(max.isAfter(LocalTime.parse("18:00:00",df4))){
+                                    timeDurantion.minusMinutes(30);
+                                }
+                                long l = timeDurantion.toMinutes() / 60;
+                                if(l<o.getDouble("OverTimeDuration")){
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l));
+                                }else {
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(o.getDouble("OverTimeDuration")));
+                                }
+                            }
+                        }else {
+                            //开始日期和结束日期是不相同的情况 说明是加班区间存在加班到第二天的情况
+                            LocalDateTime stop = LocalDateTime.parse(o.getString("StopDate"), df1);
+                            LocalDateTime start = stop.toLocalDate().atTime(LocalTime.MIN);
+                            Duration duration = Duration.between(start, stop);
+                            //如果结束时间大于13:00
+                            if(stop.toLocalTime().isAfter(LocalTime.parse("13:00:00",df4))){
+                                duration.minusHours(1);
+                            }
+                            //如果结束时间大于18:00
+                            if(stop.toLocalTime().isAfter(LocalTime.parse("18:00:00",df4))){
+                                duration.minusMinutes(30);
+                            }
+                            long l = duration.toMinutes() / 60;
+                            if(max.isAfter(LocalTime.parse("19:00:00",df4))){
+                                //判断打卡时间是不是大于19:00 通过打卡计算加班时长 与加班单作比较 取小
+                                Duration timeDurantion = Duration.between(LocalTime.parse("19:00:00", df4), max);
+                                long l1 = timeDurantion.toMinutes() / 60;
+                                if(l1<l){
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l1));
+                                }else {
+                                    overTimeBigDecimal=overTimeBigDecimal.add(new BigDecimal(l));
+                                }
+                            }
+                        }
+                    }
+                }
+                if(workDay){
+                    //工作日计算按照前面的逻辑计算加班时长计算
+                    workTime= workTime+overTimeBigDecimal.doubleValue();
+                }else {
+                    //非工作日加班 根据实际打卡时长 比较 加班单时长
+                    if((between.toMinutes()/60)>overTimeBigDecimal.doubleValue()){
+                        workTime= workTime+overTimeBigDecimal.doubleValue();
                     }else {
-                        workTime= actualOverTimeDuration;
+                        workTime= workTime+(between.toMinutes()/60);
                     }
                 }
-                //计算休假
-                List<JSONObject> targetVacation = vacationStream.filter(a -> a.getString("StaffId").equals(beisen.get().getUserId())
-                        && (a.getString("ApproveStatus").equals("通过") || a.getString("ApproveStatus").equals("审批中"))).collect(Collectors.toList());
-                if(targetVacation.size()>0){
-                    double vacationDuration = targetVacation.stream().mapToDouble(i -> i.getDouble("VacationDuration")).sum();
+                //处理修改
+                List<JSONObject> lastVacationList = vacationStream.filter(a ->{
+                    LocalDate vacationStartDate = LocalDateTime.parse(a.getString("VacationStartDateTime"), df3).toLocalDate();
+                    LocalDate vacationStopDate = LocalDateTime.parse(a.getString("VacationStopDateTime"), df3).toLocalDate();
+                    boolean b=false;
+                    if((localDate.isAfter(vacationStartDate)||localDate.isEqual(vacationStartDate))
+                            &&(localDate.isBefore(vacationStopDate)||localDate.isEqual(vacationStopDate))){
+                        b=true;
+                    }
+                    if(a.getString("StaffId").equals(beisen.get().getUserId())
+                            && b
+                            &&  (a.getString("ApproveStatus").equals("通过") || a.getString("ApproveStatus").equals("审批中"))){
+                        return true;
+                    }
+                    return false;
+                }).collect(Collectors.toList());
+                if(lastVacationList.size()>0){
+                    double vacationDuration = lastVacationList.stream().mapToDouble(i -> i.getDouble("VacationDuration")).sum();
                     BigDecimal decimal = new BigDecimal(vacationDuration);
                     decimal=decimal.divide(new BigDecimal(60),1,RoundingMode.HALF_UP);
-                    //考勤打卡区间大于8小时 但是存在休假数据
-                    if(between.toHours()>8){
-                        if(decimal.doubleValue()>8){
-                            workTime= workTime-8;
-                        }else {
-                            workTime= workTime-decimal.doubleValue();
-                        }
+//                            //可能存在休假多天 只减去一天
+                    if(decimal.doubleValue()>=8){
+                        workTime= workTime-8;
+                    }else {
+                        workTime= workTime-decimal.doubleValue();
                     }
                 }
             }