Browse Source

Merge remote-tracking branch 'origin/master'

yusm 2 months ago
parent
commit
eaeb14db2f

+ 179 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/treeSelect/selectTree.vue

@@ -0,0 +1,179 @@
+<template>
+  <van-popup v-model:show="showPicker" round position="bottom"  @click-overlay="onClickOverlay" >
+    <div class="tree-box">
+      <div class="tree-container">
+        <div class="tree-data">
+          <TreeSelect
+            ref="treeSelectRef"
+            :list="data.list"
+            :listObj="data.listObj"
+            :multiple="multiple"
+            :configurationItem="configurationItem"
+            @confirm="onConfirm"
+          ></TreeSelect>
+        </div>
+      </div>
+    </div>
+
+    <div class="tree-confirm">
+      <van-button type="primary" block @click="handleConfirm">确定</van-button>
+    </div>
+  </van-popup>
+</template>
+ 
+<script setup>
+import { reactive, watch, ref, nextTick, onMounted } from "vue";
+import TreeSelect from "./tree.vue";
+import { showLoadingToast, closeToast } from "vant";
+
+const emits = defineEmits(["changeModelValue", "update:show", "confirm"]);
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false,
+  },
+  // 绑定值
+  modelValue: {
+    type: Array,
+     default() {
+      return [];
+    },
+  },
+  multiple: {
+    type: Boolean,
+    default: false,
+  },
+  listData: {
+    type: Array,
+    default() {
+      return [];
+    },
+  },
+  // 配置项 默认为 label, value, children
+  configurationItem: {
+    type: Object,
+    default() {
+      return {
+        label: "label",
+        value: "value",
+        children: "children",
+      };
+    },
+  }
+});
+
+watch(
+  () => props.show,
+  () => {
+    showPicker.value = props.show;
+    initData(props.listData);
+  }
+);
+
+const showPicker = ref(props.show);
+const data = reactive({
+  list: props.listData, // 树数组
+  listObj: {}, // 数组对象
+  selectList: [], // 选中的数据
+  canCheckList: [], // 能够选择的数据集合
+  canCheckListFixed: [], // 固定的能够选择的数据集合
+});
+
+const treeSelectRef = ref(null);
+
+const init = (type) => {
+  data.canCheckList = [];
+  data.canCheckListFixed = [];
+};
+const initData = (options) => {
+  if (options && options.length) {
+    options[0].first=true
+    data.list = options;
+    init();
+    data.listObj = setListObj(options);
+  }
+};
+
+// 将树形数据转为扁平对象
+const setListObj = (list) => {
+  let listObj = {};
+  list.forEach((itm) => {
+    if(props.modelValue&&props.modelValue.indexOf(itm.id)!==-1){
+       itm.checked=true
+    }
+    data.canCheckList.push(itm);
+    data.canCheckListFixed.push(itm);
+    listObj[itm.id] = itm;
+    if (itm.children && itm.children.length) {
+      listObj = {
+        ...listObj,
+        ...setListObj(itm.children),
+      };
+    }
+  });
+
+  return listObj;
+};
+
+const onClickOverlay = () => {
+  emits("update:show", false);
+};
+// 确认
+const handleConfirm = () => {
+  emits("changeModelValue", data.selectList);
+  emits("update:show", false);
+};
+
+const onConfirm = (e) => {
+  const showSelectList = filterData(e);
+  console.log(showSelectList, '<==== showSelectList')
+  console.log(props.configurationItem.value)
+  data.selectList = showSelectList.map((itm) => itm[props.configurationItem.value]);
+  console.log(data.selectList, '<===== showSelectList')
+};
+
+// 过滤数据
+const filterData = (selectList) => {
+  // 过滤出展示中,且打勾的数据
+  const showSelectList = selectList.filter((itm) => {
+    return !itm.isHide && !itm.isShowChildren;
+  });
+  return showSelectList;
+};
+
+const sendWordShow = ref(false);
+
+defineExpose({
+  init,
+  setListObj,
+});
+</script>
+ 
+<style lang="scss" scoped>
+.tree-box {
+  --van-search-content-background-color: #eeeeee;
+  --van-search-content-background: #eeeeee;
+}
+
+.tree-container {
+  width: 100%;
+  padding: 32px 32px 0;
+}
+
+.tree-data {
+  height: 60vh;
+  overflow-y: auto;
+}
+
+.tree-btns {
+  width: 100%;
+  margin-bottom: 24px;
+  display: flex;
+  align-items: center;
+}
+
+.tree-confirm {
+  width: 100%;
+  padding: 12px 32px;
+}
+</style>

+ 245 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/treeSelect/tree.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="list">
+    <div class="item" v-for="item in props.list" :key="item[configurationItem.value]" v-show="!item.isHide">
+      <div class="title">
+        <div class="checkbox-box">
+          <van-checkbox  icon-size="16px" shape="square" @click.stop="checkChange(item)" v-model="item.checked">
+            <span style="font-size: 15px;">
+              {{ item[configurationItem.label]}}
+            </span>
+          </van-checkbox>
+        </div>
+        <div @click.stop="itemClick(item)" :class="item.first?'arrow':'arrowlast'">
+            <van-icon v-if="item[configurationItem.children] && item[configurationItem.children].length" :name="item.isShowChildren ? 'arrow-up' : 'arrow-down'" />
+        </div>
+        
+      </div>
+      <div class="tree" v-show="item.first||item.isShowChildren">
+        <tree  
+         :isLink="data.isLink"
+          v-if="item.children && item.children.length" 
+          :list="item.children" 
+          :listObj="props.listObj"
+          :isFirstFloor="false" 
+          :multiple="data.multiple" 
+          @confirm="onConfirm" 
+          :defaultId="defaultId">
+        </tree>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import { reactive, watch } from 'vue'
+import tree from './tree.vue'
+const emits = defineEmits(["change","confirm"])
+const props = defineProps({
+  // 是否是第一层
+  isFirstFloor: {
+    type: Boolean,
+    default() {
+      return true;
+    },
+  },
+  // 树形结构
+  list: {
+    type: Array,
+    default() {
+      return [];
+    },
+  },
+  multiple: {
+    type: Boolean,
+    default() {
+      return false;
+    },
+  },
+  // 树形扁平化数据
+  listObj: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  // 配置项 默认为 label, value, children
+  configurationItem: {
+    type: Object,
+    default() {
+      return {
+        label: "label",
+        value: "value",
+        children: "children",
+      };
+    },
+  },
+  // 单选默认值
+  defaultId : String
+})
+
+ 
+const data = reactive({
+  firstLoad: true,
+  checkboxValue1: [],
+  showList: [],
+  isLink: true,
+  multiple: true,
+  isOutData: true, // 需要将数据抛出
+})
+ 
+watch(() => props.list, () => {
+  if (data.firstLoad) {
+    outDataBuffer();
+    data.firstLoad = false;
+  }
+  // 判断 是第一层树 且 不是进行显示隐藏操作时,进行数据的抛出
+  if (props.isFirstFloor && data.isOutData) {
+    if(data.multiple){
+      outCheckedData();
+    }
+  }
+}, { deep: true })
+ 
+// 展开
+const itemClick = (item) => {
+  outDataBuffer();
+  item.isShowChildren = !item.isShowChildren
+  
+}
+// 数据抛出缓冲(在list数据变化时,不想抛出选择的数据时,调用该方法)
+const outDataBuffer = () => {
+  data.isOutData = false;
+  setTimeout(() => {
+    data.isOutData = true;
+  }, 500);
+}
+// 获取选中对象
+const getCheckData = (list) => {
+  let deptList = [];
+  list.forEach((itm) => {
+    if (itm.checked) {
+      deptList.push(itm);
+    }
+    if (itm.children && itm.children.length) {
+      deptList = deptList.concat(getCheckData(itm.children));
+    }
+  });
+  return deptList;
+}
+// 单项checked改变
+const checkChange = (item) => {
+
+  // 多选
+  if (data.multiple) {
+    // item.checked = !item.checked
+    if (data.isLink) {
+      // 展开所有可以展开的节点
+      if (item.checked) {
+          expandAll(item);
+      }
+  
+      // 勾选子级
+      if (item.children && item.children.length) {
+        checkChidren(item.children, item.checked);
+        outCheckedData();
+      }
+    }
+    return
+  }
+ 
+  // 单选
+  if(item.children && item.children.length) return
+  toggleAllSelectData(props.list)
+  outCheckedData();
+
+}
+ 
+// 获取全部可选择数据,进行全选/取消
+const toggleAllSelectData = (list) => {
+  list.forEach((itm) => {
+    itm.checked = false
+    if (itm.children && itm.children.length) {
+      toggleAllSelectData(itm.children)
+    }
+  });
+}
+ 
+// 展开所有可以展开的节点
+const expandAll = (item) => {
+  if (item.children?.length) {
+    item.isShowChildren = true
+    item.children.forEach(itm => {
+      expandAll(itm);
+    })
+  }
+}
+
+
+
+// 根据父级统一取消勾选或勾选
+const checkChidren = (list, isChecked) => {
+  list.forEach((itm) => {
+    itm.checked = isChecked
+    if (itm.children && itm.children.length) {
+      checkChidren(itm.children, isChecked);
+    }
+  });
+}
+// 抛出选中的数据
+const outCheckedData = () => {
+  const checkedList = getCheckData(props.list);
+  emits("change", checkedList);
+  onConfirm(checkedList)
+}
+ 
+const onConfirm = (e) => {
+  emits("confirm", e);
+}
+ 
+defineExpose({
+  itemClick,
+  outDataBuffer,
+  getCheckData,
+  checkChange,
+  expandAll,
+  // checkParent,
+  checkChidren,
+  outCheckedData,
+
+})
+</script>
+ 
+<style lang="scss" scoped>
+ 
+.list {
+  .item {
+    margin-bottom: 10px;
+ 
+    .title {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 10px;
+ 
+      .checkbox-box {
+        display: flex;
+        align-items: center;
+        cursor: pointer;
+        padding: 10px 0;
+      }
+ 
+      .arrow{
+        width: 80px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+ 
+    .tree {
+      margin-left: 50px;
+    }
+  }
+    .arrow{
+    display: none !important;
+   }
+}
+</style>

+ 207 - 15
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/addEditor.vue

@@ -1,6 +1,12 @@
 <template>
   <div class="w-full h-full flex flex-col">
     <div class="flex-1 overflow-y-auto">
+      <van-field v-model="value" label="合同所属部门" placeholder="请选择" input-align="right" readonly
+        @click="showDepartmentSelection">
+        <template #input v-if="departmentRow.departmentName != ''">
+          <TranslationComponent type="departmentName" :openId="departmentRow.departmentName" />
+        </template>
+      </van-field>
       <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
       <template v-for="(item, index) in paymentPlanList">
         <FoldingPanel :title="`回款计划(${index + 1})`" class="mb-4">
@@ -19,9 +25,10 @@
                   </div>
                 </template>
               </van-cell>
-              <van-cell title="回款日期" @click="showDatePicker(index)">
+              <van-cell title="回款日期" @click="showDatePicker(index, 'payDate')">
                 <template #default>
-                  <van-field v-model="item.payDate" input-align="right" placeholder="请选择日期" class="resetStyles" readonly />
+                  <van-field v-model="item.payDate" input-align="right" placeholder="请选择日期" class="resetStyles"
+                    readonly />
                 </template>
               </van-cell>
               <van-cell title="回款金额">
@@ -30,6 +37,37 @@
                     class="resetStyles" />
                 </template>
               </van-cell>
+              <van-cell title="是否已开票">
+                <template #default>
+                  <div class="flex justify-end">
+                    <van-checkbox v-model="item.isBilled">已开票</van-checkbox>
+                  </div>
+                </template>
+              </van-cell>
+              <van-cell title="开票日期" @click="showDatePicker(index, 'billDate')">
+                <template #default>
+                  <van-field v-model="item.billDate" input-align="right" placeholder="请选择日期" class="resetStyles"
+                    readonly />
+                </template>
+              </van-cell>
+              <van-cell title="开票种类增值税" @click="showSelectTaxAmount(index)">
+                <template #default >
+                  <van-field v-model="item.invoiceTypeName" type="text" input-align="right" placeholder="请选择"
+                    class="resetStyles" readonly="" />
+                </template>
+              </van-cell>
+              <van-cell title="税率%">
+                <template #default>
+                  <van-field v-model="item.taxRate" type="digit" input-align="right" placeholder="请输入"
+                    class="resetStyles" @change="inputNumberChange(index)" />
+                </template>
+              </van-cell>
+              <van-cell title="税额">
+                <template #default>
+                  <van-field v-model="item.taxAmount" type="digit" input-align="right" placeholder="请输入"
+                    class="resetStyles" />
+                </template>
+              </van-cell>
             </div>
           </template>
         </FoldingPanel>
@@ -45,7 +83,48 @@
     <van-popup v-model:show="showPicker" destroy-on-close position="bottom" :style="{ height: '50%' }">
       <van-date-picker v-model="pickerValue" @confirm="showPickerConfirm" @cancel="showPicker = false" />
     </van-popup>
+
+    <!-- 部门选择 -->
+    <!-- <treeSelect
+      ref="treeSelectRef"
+      v-model:show="departmentShow"
+      :modelValue="departmentShow.departmentId"
+      :listData="options"
+      :multiple='false'
+      placeholder="请选择"
+      @changeModelValue="changeModelValue"
+      :configurationItem="{ label: 'label', value: 'id', children: 'children' }"
+    ></treeSelect> -->
+
+    <van-popup v-model:show="departmentShow" destroy-on-close position="bottom" :style="{ height: '85%' }">
+      <div class="selectDepartment">
+        <div class="w-full absolute top-12 z-10">
+          <van-tabs v-model:active="departmentRow.tabIndex" shrink scrollspy @click-tab="tabClick">
+            <van-tab v-for="item in tableSelectSelectionShowList">
+              <template #title>
+                <TranslationComponent type="departmentName" :openId="item.label" />
+              </template>
+            </van-tab>
+          </van-tabs>
+        </div>
+        <div class="soDept">
+          <van-cascader v-model="departmentRow.departmentId" ref="cascaderDeptRef" title="合同所属部门" :options="deptOptions"
+            @change="changeModelValue" @close="departmentShow = false" :field-names="fieldNames"
+            @finish="finishModelValue">
+            <template #option="{ option }">
+              <div>
+                <TranslationComponent type="departmentName" :openId="option.label" />
+              </div>
+            </template>
+          </van-cascader>
+        </div>
+      </div>
+    </van-popup>
   </div>
+
+  <van-popup v-model:show="showSelectTaxAmountShow" destroy-on-close position="bottom" :style="{ height: '50%' }">
+    <van-picker :columns="columns" @confirm="onConfirm" />
+  </van-popup>
 </template>
 
 <script setup>
@@ -61,6 +140,8 @@ import dayjs from 'dayjs';
 import commonUtil from "@utility/commonUtil"
 import { number } from 'echarts';
 
+import TreeSelect from "@components/common/treeSelect/selectTree.vue"
+
 const router = useRouterStore()
 const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 const props = defineProps({
@@ -74,6 +155,28 @@ const paymentPlanList = ref([{}])
 const showPicker = ref(false)
 const pickerValue = ref([])
 const rowIndex = ref(0)
+const departmentRow = ref({
+  departmentName: '',
+  departmentId: '',
+  sureDepartmentId: '',
+  tableSelect: ''
+})
+const departmentShow = ref(false)
+const deptOptions = ref([])
+const tableSelectSelectionShowList = ref([])
+const fieldNames = {
+  text: 'label',
+  value: 'id',
+  children: 'children',
+};
+const cascaderDeptRef = ref(null)
+const dataType = ref('payDate')
+const showSelectTaxAmountShow = ref(false)
+const selectIndex = ref(0)
+const columns = [
+  { text: '增值税专用发票', value: 0 },
+  { text: '增值税普通发票', value: 1 },
+]
 
 watch(() => formVal.value, (newValue) => {
   console.log(newValue, '<==== 看看')
@@ -81,10 +184,71 @@ watch(() => formVal.value, (newValue) => {
     paymentPlanList.value = [{}]
     return
   }
-
+  departmentRow.value = {
+    departmentName: newValue.departmentName,
+    departmentId: newValue.departmentId,
+    sureDepartmentId: newValue.departmentId,
+    tableSelect: ''
+  }
   getPaymentCollectionList(newValue.id)
 })
 
+function inputNumberChange(index) {
+  const { amount = 0, taxRate = 0, taxAmount = 0 } = paymentPlanList.value[index]
+  if(!amount && !amount) {
+    return
+  }
+
+  paymentPlanList.value[index].taxAmount = +((amount * taxRate / 100).toFixed(2))
+}
+
+function onConfirm(val) {
+  const selectedOptions = val.selectedOptions[0]
+  paymentPlanList.value[selectIndex.value].invoiceType = selectedOptions.value
+  paymentPlanList.value[selectIndex.value].invoiceTypeName = selectedOptions.text
+  showSelectTaxAmountShow.value = false
+}
+function showSelectTaxAmount(index) {
+  selectIndex.value = index
+  showSelectTaxAmountShow.value = true
+}
+
+function finishModelValue({ value, selectedOptions }) {
+  const row = selectedOptions.find(item => item.id == value)
+  departmentRow.value.sureDepartmentId = row.id
+  departmentRow.value.departmentName = row.label
+  departmentShow.value = false
+}
+
+function tabClick(name) {
+  const elements = cascaderDeptRef.value.$el.querySelectorAll('[role="tab"]');
+  elements[name.name].click()
+  tableSelectSelectionShowList.value.splice(name.name + 1, tableSelectSelectionShowList.value.length)
+}
+
+function changeModelValue({ value, selectedOptions, tabIndex }) {
+  const row = selectedOptions.find(item => item.id == value)
+  tableSelectSelectionShowList.value.push({
+    label: row.label,
+    index: tabIndex,
+    id: row.id
+  })
+  setTimeout(() => {
+    departmentRow.value.tabIndex = tableSelectSelectionShowList.value.length - 1
+  }, 200)
+}
+
+function showDepartmentSelection() {
+  tableSelectSelectionShowList.value = []
+  departmentRow.value.departmentId = ''
+  departmentShow.value = true
+}
+
+function obtainDepartmentalData() {
+  requests.post('/department/list', {}).then((res) => {
+    deptOptions.value = res.data
+  })
+}
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
@@ -93,33 +257,44 @@ function onSubmit() {
     }
     const determineArray = paymentPlanList.value.filter(item => item.isPayed || (item.payDate || item.amount))
     let totalNum = 0
-    for(let i in determineArray) {
+    for (let i in determineArray) {
       const row = determineArray[i]
-      if(!row.payDate) {
+      if (!row.payDate) {
         toastText('回款日期不能为空')
         return
       }
-      if(!row.amount || row.amount == 0) {
+      if (!row.amount || row.amount == 0) {
         toastText('回款金额不能为空和0')
         return
       }
       totalNum += Number(determineArray[i].amount)
-      if(formVal.value.id) {
+      if (formVal.value.id) {
         determineArray[i].contractId = formVal.value.id
       }
     }
 
-    if(totalNum > 0 && !res.data.amounts) {
+    if (totalNum > 0 && !res.data.amounts) {
       toastText('请输入合同金额')
       return
     }
-    if(totalNum > res.data.amounts) {
+    if (totalNum > res.data.amounts) {
       toastText('回款金额不能大于合同金额')
       return
     }
 
     toastLoading('保存中', 0)
-    requests.post(props.formValue?.id ? CONTRACT_EDITING : CONTRACT_ADDITION_EDITING, { ...commonUtil.getFromValue({ ...props.formValue, ...res.data, paymentListStr: JSON.stringify(determineArray || []) }) }).then(() => {
+    requests.post(props.formValue?.id ? CONTRACT_EDITING : CONTRACT_ADDITION_EDITING, { ...commonUtil.getFromValue({ 
+      ...props.formValue, 
+      ...res.data, 
+      paymentListStr: JSON.stringify((determineArray || []).map(item => {
+        delete item.invoiceTypeName
+        return {
+          ...item,
+          isBilled: item.isBilled ? 1 : 0
+        }
+      }))}),
+      departmentId: departmentRow.value.sureDepartmentId
+    }).then(() => {
       toastSuccess('保存成功')
       setTimeout(() => {
         router.navigateBack({
@@ -135,23 +310,30 @@ function onSubmit() {
 }
 
 function addPaymentCollection(index) {
-  paymentPlanList.value.splice(index + 1, 0, { isPayed: false, payDate: dayjs(new Date()).format('YYYY-MM-DD'), amount: null })
+  paymentPlanList.value.splice(index + 1, 0, { isPayed: false, payDate: dayjs(new Date()).format('YYYY-MM-DD'), amount: null, isBilled: true })
 }
 
 function deletePaymentCollection(index) {
   paymentPlanList.value.splice(index, 1)
 }
 
-function showDatePicker(index) {
-  const { payDate } = paymentPlanList.value[index];
+function showDatePicker(index, type) {
+  const { payDate, billDate } = paymentPlanList.value[index];
+  dataType.value = type
   rowIndex.value = index;
-  const currentDate = dayjs(payDate ? new Date(payDate) : new Date()).format('YYYY-MM-DD').split('-');
+  const dates = type == 'payDate' ? payDate : billDate
+  const currentDate = dayjs(dates ? new Date(dates) : new Date()).format('YYYY-MM-DD').split('-');
   pickerValue.value = currentDate;
   showPicker.value = true;
 }
 
 function showPickerConfirm({ selectedValues }) {
-  paymentPlanList.value[rowIndex.value].payDate = selectedValues.join("-");
+  if (dataType.value == 'payDate') {
+    paymentPlanList.value[rowIndex.value].payDate = selectedValues.join("-");
+  }
+  if (dataType.value == 'billDate') {
+    paymentPlanList.value[rowIndex.value].billDate = selectedValues.join("-");
+  }
   showPicker.value = false;
 }
 
@@ -165,10 +347,12 @@ useLifecycle({
   load: () => {
     formVal.value = props.formValue
     paymentPlanList.value = [{}]
+    obtainDepartmentalData()
   },
   init: () => {
     formVal.value = props.formValue
     paymentPlanList.value = [{}]
+    obtainDepartmentalData()
   }
 });
 
@@ -182,4 +366,12 @@ onActivated(() => {
 .resetStyles {
   padding: 0;
 }
+
+.soDept {
+  position: relative;
+
+  :deep(.van-tabs__nav--complete) {
+    display: none;
+  }
+}
 </style>

+ 15 - 4
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/detail.vue

@@ -1,6 +1,13 @@
 <template>
   <div class="flex flex-col h-full">
     <div class="bg-white info flex-1 overflow-y-auto cellnormall">
+      <van-cell title="合同所属部门">
+        <template #default>
+          <span>
+            <TranslationComponent type="departmentName" :openId="infoData.departmentName" />
+          </span>
+        </template>
+      </van-cell>
       <van-cell title="合同编号" :value="infoData.number" />
       <van-cell title="合同名称" :value="infoData.name" />
       <van-cell title="合同金额" :value="infoData.amounts">
@@ -8,6 +15,11 @@
           <span class="text-[#FF8B32]" v-if="infoData.amounts">¥ {{ infoData.amounts }}</span>
         </template>
       </van-cell>
+      <van-cell title="开票金额" :value="infoData.invoicedAmount">
+        <template #default>
+          <span class="text-[#FF8B32]" v-if="infoData.invoicedAmount">¥ {{ infoData.invoicedAmount }}</span>
+        </template>
+      </van-cell>
       <van-cell title="已回款金额" :value="infoData.payment">
         <template #default>
           <span class="text-[#FF8B32]" v-if="infoData.payment">¥ {{ infoData.payment }}</span>
@@ -46,7 +58,7 @@
         <van-button type="danger" class="w-full block" @click="rejectOperation">驳回合同</van-button>
       </template>
       <van-button type="default" class="w-full block" v-permission="[routingInformation.jurisdiction.edit]"
-        @click="jumpEdit()">编辑合同</van-button>
+        @click="jumpEdit(infoData)">编辑合同</van-button>
       <van-button type="danger" class="w-full block" v-permission="[routingInformation.jurisdiction.delete]"
         @click="deleteRow()">删除合同</van-button>
     </div>
@@ -168,12 +180,11 @@ function deleteRow() {
   })
 }
 
-function jumpEdit() {
+function jumpEdit(row) {
   const formJson = fixedData.formJson[routingInformation.key] || []
   const formList = resetListData(formJson?.list)
   const filedObj = getListFieldKey(formList, infoData.value)
-  const formVal = { ...filedObj, id: props.info.id }
-
+  const formVal = { ...filedObj, id: props.info.id, departmentId: row.departmentId, departmentName: row.departmentName }
   router.navigateTo({
     pathName: 'addEditor',
     success: () => {

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/treeSelect/treeSelect.vue

@@ -34,7 +34,7 @@ const props = defineProps({
 
 const { departmentList, userInfo } = storeToRefs(useStore());
 const { setValue } = useStore()
-const treeSelectVal = ref(props.modelValue); // 响应式绑定 v-model 的值
+const treeSelectVal = ref(props.modelValue || ''); // 响应式绑定 v-model 的值
 const treeSelectArray = ref<any>([]);
 const visibleFlag = ref(false);
 const selectLoading = ref(false);

+ 180 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contract/component/addEditorTwo.vue

@@ -0,0 +1,180 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watch } from "vue";
+import { UploadRequestOptions } from 'element-plus';
+import { Delete } from '@element-plus/icons-vue'
+import { tableShowOverflowTooltip } from '@/utils/globalVariables'
+import { formatDate } from '@/utils/times'
+import { downloadFile } from '@/utils/tools'
+import { post, get, uploadFile } from "@/utils/request";
+const props = defineProps<{
+  paymentPlan: any[],
+  enclosure: any[],
+  enclosureDetele: any[]
+}>()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const paymentPlan = ref(props.paymentPlan)
+const enclosure = ref(props.enclosure)
+const enclosureDetele = ref<any[]>([])
+const uploadRef = ref<any>()
+
+watch(() => props.paymentPlan, (newVal, _oldVal) => {
+  paymentPlan.value = newVal || []
+})
+
+watch(() => props.enclosure, (newVal, _oldVal) => {
+  enclosure.value = newVal || []
+})
+
+watch(() => props.enclosureDetele, (newVal, _oldVal) => {
+  enclosureDetele.value = newVal || []
+})
+
+const typeOptions = [
+  { label: '增值税专用发票', value: 0 },
+  { label: '增值税普通发票', value: 1 },
+]
+
+
+function calculateTaxAmount(index: number) {
+  const { amount = 0, taxRate = 0, taxAmount = 0 } = paymentPlan.value[index]
+  if(!amount && !amount) {
+    return
+  }
+
+  paymentPlan.value[index].taxAmount = +((amount * taxRate / 100).toFixed(2))
+}
+
+// 上传附件
+async function httpUploadFile(param: UploadRequestOptions) {
+  const { file } = param
+  enclosure.value.push({ name: file.name, file, needUpload: true })
+  return
+}
+
+function addPlan() {
+  paymentPlan.value.push({
+    isPayed: false,
+    payDate: formatDate(new Date()),
+    amount: '0',
+    isBilled: true
+  })
+}
+
+function deteleTables(row: any, index: number, type: 'paymentPlan' | 'enclosure') {
+  if (type === 'paymentPlan') {
+    paymentPlan.value.splice(index, 1)
+  } else {
+    if(row.id) {
+      enclosureDetele.value.push(row.id)
+    }
+    enclosure.value.splice(index, 1)
+  }
+}
+
+function getAddEditorData() {
+  return {
+    paymentPlan: paymentPlan.value,
+    enclosure: enclosure.value,
+    enclosureDetele: enclosureDetele.value
+  }
+}
+
+defineExpose({
+  getAddEditorData
+})
+
+</script>
+<template>
+  <div>
+    <div>回款计划</div>
+    <el-table :show-overflow-tooltip="tableShowOverflowTooltip" :data="paymentPlan" height="260">
+      <el-table-column label="是否已回款" width="100">
+        <template #default="scope">
+          <el-checkbox v-model="scope.row.isPayed" label="已回款" />
+        </template>
+      </el-table-column>
+      <el-table-column label="回款日期" width="180">
+        <template #default="scope">
+          <el-date-picker v-model="scope.row.payDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
+            placeholder="回款日期" style="width: 150px;" />
+        </template>
+      </el-table-column>
+      <el-table-column label="回款金额" width="180">
+        <template #default="scope">
+          <el-input v-model="scope.row.amount" v-enter-number placeholder="回款金额" style="width: 150px;">
+            <template #prefix>
+              <el-text class="mx-1" type="info">¥</el-text>
+            </template>
+          </el-input>
+        </template>
+      </el-table-column>
+      <el-table-column label="是否已开票" width="100">
+        <template #default="scope">
+          <el-checkbox v-model="scope.row.isBilled" label="已开票" />
+        </template>
+      </el-table-column>
+      <el-table-column label="开票日期" width="180">
+        <template #default="scope">
+          <el-date-picker v-model="scope.row.billDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
+            placeholder="开票日期" style="width: 150px;" />
+        </template>
+      </el-table-column>
+      <el-table-column label="开票种类增值税" width="180">
+        <template #default="scope">
+          <el-select v-model="scope.row.invoiceType" placeholder="请选择">
+            <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="税率%" width="180">
+        <template #default="scope">
+          <el-input-number v-model="scope.row.taxRate" controls-position="right" :min="0.1" :max="100" @change="calculateTaxAmount(scope.$index)"></el-input-number>
+        </template>
+      </el-table-column>
+      <el-table-column label="税额" width="180">
+        <template #default="scope">
+          <el-input-number v-model="scope.row.taxAmount" controls-position="right" :min="0"></el-input-number>
+        </template>
+      </el-table-column>
+      <el-table-column prop="operation" width="80" fixed="right">
+        <template #header>
+          <el-link type="primary" :underline="false" @click="addPlan()">添加</el-link>
+        </template>
+        <template #default="scope">
+          <el-button type="danger" :icon="Delete" size="small"
+            @click="deteleTables(scope.row, scope.$index, 'paymentPlan')"></el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div>附件</div>
+    <el-table :show-overflow-tooltip="tableShowOverflowTooltip" :data="enclosure" height="260">
+      <el-table-column label="序号" width="100">
+        <template #default="scope">
+          {{ scope.$index + 1 }}
+        </template>
+      </el-table-column>
+      <el-table-column label="文件名称" prop="name"></el-table-column>
+      <el-table-column label="操作" width="80">
+        <template #default="scope">
+          <el-link type="primary" :underline="false" v-if="scope.row.id" @click="downloadFile(scope.row.url, scope.row.name)">下载</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="80">
+        <template #header>
+          <el-upload ref="uploadRef" :http-request="httpUploadFile" :show-file-list="false" element-loading-text="正在上传"
+            multiple>
+            <template #trigger>
+              <el-link type="primary" :underline="false">添加</el-link>
+            </template>
+          </el-upload>
+        </template>
+        <template #default="scope">
+          <el-button type="danger" :icon="Delete" size="small"
+            @click="deteleTables(scope.row, scope.$index, 'enclosure')"></el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+<style lang="scss" scoped></style>

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

@@ -8,7 +8,8 @@ import { CONTRACT_DELETION, CONTRACT_OPERATION, DELETE_CONTRACT_FILE, EDIT_CONTR
 import { GET_CONTRACT_TEMPLATE, ADD_CONTRACT } from './api'
 import { UploadRequestOptions } from "element-plus";
 
-import AddEditor from "./component/addEditor.vue";
+import AddEditorTwo from "./component/addEditorTwo.vue";
+import treeSelect from '@/components/translationComponent/treeSelect/treeSelect.vue'
 
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const filterContractForm = reactive({
@@ -131,6 +132,7 @@ function exportContract() {
 async function addEditor(row?: any) {
   allLoading.addEditorSaveLoading = false
   editForm.value = row ? setEditForm(row) : {}
+  editForm.value.departmentId = row?.departmentId || ''
   allDynamicText.addEditorText = row ? '编辑合同' : '新增合同'
   contractTemplate.value.list = setTemplateDataDisable(contractTemplate.value.list, [...getTemplateKey(contractTemplate.value.list)], row?.status == 0)
   generateFormKey.value++
@@ -139,7 +141,9 @@ async function addEditor(row?: any) {
     const { data = [] } = await post(GET_PAYMENT_LIST, { contractId: row.id })
     addEditorVisableForm.paymentPlan = data.map((item: any) => {
       return {
-        isPayed: item.isPayed, amount: item.amount, payDate: item.payDate, id: item.id
+        isPayed: item.isPayed, amount: item.amount, payDate: item.payDate, id: item.id,
+        isBilled: item.isBilled == 1 ? true : false, billDate: item.billDate, invoiceType: item.invoiceType,
+        taxRate: +item.taxRate, taxAmount: +item.taxAmount
       }
     })
   }
@@ -222,7 +226,12 @@ async function addEditorSave() {
     ...data,
     startDate: data.startDate ? formatDate(new Date(data.startDate)) : '',
     endDate: data.endDate ? formatDate(new Date(data.endDate)) : '',
-    paymentListStr: JSON.stringify(newPaymentPlan)
+    paymentListStr: JSON.stringify(newPaymentPlan.map((item: any) => { 
+      return {
+        ...item,
+        isBilled: item.isBilled ? 1 : 0
+      }
+    }))
   })
 
   let totalAmount = 0
@@ -371,8 +380,14 @@ function getObtainContractType() {
             @click="exportContract()">导出</el-button>
         </div>
         <div class="flex-1 w-full overflow-hidden">
-          <el-table ref="contractTableRef" :show-overflow-tooltip="tableShowOverflowTooltip" :data="contractTableList"
+          <el-table ref="contractTableRef" :data="contractTableList"
             border v-loading="allLoading.contractTableLading" style="width: 100%;height: 100%;">
+            <el-table-column label="合同所属部门" width="180">
+              <template #default="scope">
+                <TextTranslation translationTypes="departmentName" :translationValue="scope.row.departmentName">
+                </TextTranslation>
+              </template>
+            </el-table-column>
             <el-table-column prop="number" label="合同编号" width="180"></el-table-column>
             <el-table-column prop="name" label="合同名称" width="180"></el-table-column>
             <el-table-column prop="amounts" label="合同金额" width="180">
@@ -380,6 +395,11 @@ function getObtainContractType() {
                 ¥ {{ scope.row.amounts ? scope.row.amounts.toFixed(2) : '0.00' }}
               </template>
             </el-table-column>
+            <el-table-column label="已开票金额" width="180">
+              <template #default="scope">
+                ¥ {{ scope.row.invoicedAmount ? scope.row.invoicedAmount.toFixed(2) : '0.00' }}
+              </template>
+            </el-table-column>
             <el-table-column prop="payment" label="已回款金额" width="180">
               <template #default="scope">
                 ¥ {{ scope.row.payment ? scope.row.payment.toFixed(2) : '0.00' }}
@@ -445,10 +465,17 @@ function getObtainContractType() {
       </template>
       <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
         <div class="ml-4 mr-4">
+          <div class="flex justify-between items-center mb-5">
+            <div class="w-[112px]">合同所属部门:</div>
+            <tree-select v-model="editForm.departmentId" :size="''" placeholder="请选择部门" :key="editForm.departmentId"></tree-select>
+          </div>
           <GenerateForm ref="generateForm" :data="contractTemplate" :value="editForm" :key="generateFormKey" />
-          <AddEditor ref="addEditorRef" :payment-plan="addEditorVisableForm.paymentPlan"
+          <!-- <AddEditor ref="addEditorRef" :payment-plan="addEditorVisableForm.paymentPlan"
+            :enclosure="addEditorVisableForm.enclosure" :enclosure-detele="addEditorVisableForm.enclosureDetele">
+          </AddEditor> -->
+          <AddEditorTwo ref="addEditorRef" :payment-plan="addEditorVisableForm.paymentPlan"
             :enclosure="addEditorVisableForm.enclosure" :enclosure-detele="addEditorVisableForm.enclosureDetele">
-          </AddEditor>
+          </AddEditorTwo>
         </div>
       </div>
     </el-dialog>

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

@@ -3607,7 +3607,7 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                         BigDecimal leaveDays=new BigDecimal(0);
                         BigDecimal leaveTimes=new BigDecimal(0);
                         for (LeaveSheet leaveSheet : leaveSheets) {
-                            leaveDays=leaveDays.add(new BigDecimal(leaveSheet.getTimeDays()));
+                            leaveDays=leaveDays.add(new BigDecimal(leaveSheet.getTimeDays()==null?0:leaveSheet.getTimeDays()));
                             leaveTimes=leaveTimes.add(new BigDecimal(leaveSheet.getTimeHours()));
                         }
                         user.put("leaveType",leaveType);