diff --git a/src/api/oa/crm/businessTripApply/index.ts b/src/api/oa/crm/businessTripApply/index.ts index b3d4ea1..4a0b33c 100644 --- a/src/api/oa/crm/businessTripApply/index.ts +++ b/src/api/oa/crm/businessTripApply/index.ts @@ -67,10 +67,22 @@ export const delBusinessTripApply = (tripId: string | number | Array { + return request({ + url: '/oa/crm/businessTripApply/submitAndFlowStart', + method: 'post', + data: data + }); }; diff --git a/src/api/oa/crm/businessTripApply/types.ts b/src/api/oa/crm/businessTripApply/types.ts index 49a2aa5..04e382f 100644 --- a/src/api/oa/crm/businessTripApply/types.ts +++ b/src/api/oa/crm/businessTripApply/types.ts @@ -238,6 +238,31 @@ export interface BusinessTripApplyForm extends BaseEntity { * 附件ID(多个用逗号分隔) */ ossId?: string | number; + + /** + * 项目名称 + */ + projectName?: string; + + /** + * 项目编码 + */ + projectCode?: string; + + /** + * 流程变量 + */ + variables?: Record; + + /** + * 业务扩展参数 + */ + bizExt?: Record; + + /** + * 流程编码 + */ + flowCode?: string; } export interface BusinessTripApplyQuery extends PageQuery { diff --git a/src/api/oa/erp/budgetInfo/index.ts b/src/api/oa/erp/budgetInfo/index.ts index f603184..afb9e44 100644 --- a/src/api/oa/erp/budgetInfo/index.ts +++ b/src/api/oa/erp/budgetInfo/index.ts @@ -121,3 +121,15 @@ export const listMaterialInfo = (query?: MaterialInfoQuery): AxiosPromise import('@/views/oa/crm/crmQuoteInfo/edit.vue'), name: 'crmQuoteInfoView', meta: { title: '报价单查看', activeMenu: '/oa/crm/crmQuoteInfo' } + }, + { + path: 'businessTripApply/edit', + component: () => import('@/views/oa/crm/businessTripApply/edit.vue'), + name: 'BusinessTripApplyEdit', + meta: { title: '出差申请编辑', activeMenu: '/oa/crm/businessTripApply' } } ] }, diff --git a/src/views/oa/crm/businessTripApply/edit.vue b/src/views/oa/crm/businessTripApply/edit.vue new file mode 100644 index 0000000..f3bce39 --- /dev/null +++ b/src/views/oa/crm/businessTripApply/edit.vue @@ -0,0 +1,510 @@ + + + diff --git a/src/views/oa/crm/businessTripApply/index.vue b/src/views/oa/crm/businessTripApply/index.vue index e882c6a..a5c9dd9 100644 --- a/src/views/oa/crm/businessTripApply/index.vue +++ b/src/views/oa/crm/businessTripApply/index.vue @@ -49,7 +49,7 @@ - + 新增 @@ -478,7 +497,8 @@ const form = reactive({ supplierContactName: undefined, supplierContactPhone: undefined, supplierContactEmail: undefined, - remark: undefined + remark: undefined, + ossId: undefined }); const rules = { @@ -759,22 +779,16 @@ const generateQuoteCode = async () => { } }; -// 附件上传对话框(UI占位,复用通用上传组件) -const type = ref(0); -const dialog = reactive({ visible: false, title: '' }); -const ossFileModel = ref(undefined); -const handleFile = () => { - type.value = 0; - dialog.visible = true; - dialog.title = '上传报价附件'; -}; -const submitOss = () => { - dialog.visible = false; - proxy?.$modal.msgSuccess('附件已更新'); -}; -const cancel = () => { - dialog.visible = false; -}; +// 附件ID字符串转换(用于FileUpload组件) +const ossIdString = computed({ + get() { + const v = (form as any).ossId; + return v === undefined || v === null ? '' : String(v); + }, + set(val: string) { + (form as any).ossId = val || undefined; + } +}); const submitForm = (status: string, mode: boolean) => { quoteFormRef.value?.validate(async (valid: boolean) => { if (!valid) return; @@ -873,6 +887,7 @@ onMounted(async () => { form.quoteId = id as any; const res = await getCrmQuoteInfo(id as any); Object.assign(form, res.data); + console.log('crmQuoteInfo/edit.vue数据加载成功,ossId:', form.ossId); // 添加调试日志 // 编辑模式:已存在编号则禁用生成 isCodeGenerated.value = !!form.quoteCode; // 根据已有联系人ID回填姓名、电话、邮箱(若后端已返回则保持) diff --git a/src/views/oa/erp/afterSales/edit.vue b/src/views/oa/erp/afterSales/edit.vue index 99cb726..9505de4 100644 --- a/src/views/oa/erp/afterSales/edit.vue +++ b/src/views/oa/erp/afterSales/edit.vue @@ -35,17 +35,7 @@ - - - + @@ -425,7 +415,8 @@ import { getAfterSales, addAfterSales, updateAfterSales, submitAfterSalesAndFlow import { listProjectInfo } from '@/api/oa/erp/projectInfo'; import { listUser } from '@/api/system/user'; import { getErpProjectContractsList } from '@/api/oa/erp/projectContracts'; -import { listContractInfo } from '@/api/oa/erp/contractInfo'; +import { getContractInfo } from '@/api/oa/erp/contractInfo'; +import { getCustomerInfo } from '@/api/oa/crm/customerInfo'; import { listCustomerContact } from '@/api/oa/crm/customerContact'; import { listUnitInfo } from '@/api/oa/base/unitInfo'; import { getRuleGenerateCode } from '@/api/system/codeRule'; @@ -509,12 +500,12 @@ const data = reactive({ }, rules: { afterSalesSubject: [{ required: true, message: '售后主题不能为空', trigger: 'blur' }], - afterSalesCode: [{ required: true, message: '售后编号不能为空', trigger: 'blur' }], + // afterSalesCode: [{ required: true, message: '售后编号不能为空', trigger: 'blur' }], projectCode: [{ required: true, message: '请选择项目', trigger: 'change' }], projectName: [{ required: true, message: '请选择项目', trigger: 'change' }], afterSalesDate: [{ required: true, message: '请选择日期', trigger: 'change' }], afterSalesType: [{ required: true, message: '请选择售后类型', trigger: 'change' }], - stakeholderId: [{ required: true, message: '客户干系人不能为空', trigger: 'change' }], + // stakeholderId: [{ required: true, message: '客户干系人不能为空', trigger: 'change' }], handlerId: [{ required: true, message: '售后处理人不能为空', trigger: 'change' }] } }); @@ -698,23 +689,38 @@ function loadContractOptions(projectId: any) { } // 合同变更联动 -> 客户 -> 联系人 -function handleContractChange(val: any) { +async function handleContractChange(val: any) { form.value.customerName = undefined; form.value.customerId = undefined; contactOptions.value = []; if (!val) return; - listContractInfo({ contractId: val, pageNum: 1, pageSize: 10 } as any).then((res: any) => { - if (res.rows && res.rows.length > 0) { - const contract = res.rows[0]; + try { + // 1. 获取合同详情 + const resContract = await getContractInfo(val); + if (resContract.data) { + const contract = resContract.data; form.value.customerId = contract.oneCustomerId; - form.value.customerName = contract.oneCustomerName; - if (contract.oneCustomerId) { - loadContactOptions(contract.oneCustomerId); + + // 2. 如果合同详情中有客户名则直接用,否则单独查询客户详情 + if (contract.oneCustomerName) { + form.value.customerName = contract.oneCustomerName; + } else if (contract.oneCustomerId) { + const resCustomer = await getCustomerInfo(contract.oneCustomerId); + if (resCustomer.data) { + form.value.customerName = resCustomer.data.customerName; + } + } + + // 3. 加载联系人 + if (form.value.customerId) { + loadContactOptions(form.value.customerId); } } - }); + } catch (error) { + console.error('加载合同关联信息失败:', error); + } } // 加载客户联系人 @@ -900,62 +906,70 @@ const getMaterialSummaries = (param: any) => { /** 提交按钮 */ const submitForm = (status: string, mode: boolean) => { + if (status === 'draft') { + executeSubmit(status, mode); + return; + } afterSalesFormRef.value.validate(async (valid: boolean) => { if (valid) { - buttonLoading.value = true; - try { - const submitData: any = { ...form.value }; - - // 数据格式转换:数组 -> 字符串 - if (Array.isArray(submitData.handlerId)) submitData.handlerId = submitData.handlerId.join(','); - if (Array.isArray(submitData.stakeholderId)) submitData.stakeholderId = submitData.stakeholderId.join(','); - - if (status !== 'draft') { - // --- 提交审批 --- - submitData.flowCode = FlowCodeEnum.AFTER_SALES_KEY; - submitData.variables = { - afterSalesId: submitData.afterSalesId, - totalCost: submitData.totalCost || 0, - startUserId: useUserStore().userId - }; - submitData.bizExt = { - businessTitle: `售后申请:${submitData.afterSalesSubject}`, - businessCode: submitData.afterSalesCode - }; - submitData.afterSalesStatus = '1'; // 审批中 - submitData.flowStatus = 'waiting'; - - const res = await submitAfterSalesAndFlowStart(submitData); - if (res && res.data) { - form.value = res.data; - if (!form.value.laborCostsList) form.value.laborCostsList = []; - if (!form.value.materialCostsList) form.value.materialCostsList = []; - } - proxy.$modal.msgSuccess('提交成功,流程已发起'); - } else { - // --- 暂存草稿 --- - submitData.flowStatus = 'draft'; - submitData.afterSalesStatus = '0'; // 草稿 - if (submitData.afterSalesId) { - await updateAfterSales(submitData); - } else { - await addAfterSales(submitData); - } - proxy.$modal.msgSuccess('暂存成功'); - } - // 返回列表 - proxy.$tab.closePage(route); - router.go(-1); - } catch (error) { - console.error('提交发生错误:', error); - proxy.$modal.msgError('提交失败,请查看控制台错误详情'); - } finally { - buttonLoading.value = false; - } + executeSubmit(status, mode); } }); }; +const executeSubmit = async (status: string, mode: boolean) => { + buttonLoading.value = true; + try { + const submitData: any = { ...form.value }; + + // 数据格式转换:数组 -> 字符串 + if (Array.isArray(submitData.handlerId)) submitData.handlerId = submitData.handlerId.join(','); + if (Array.isArray(submitData.stakeholderId)) submitData.stakeholderId = submitData.stakeholderId.join(','); + + if (status !== 'draft') { + // --- 提交审批 --- + submitData.flowCode = FlowCodeEnum.AFTER_SALES_KEY; + submitData.variables = { + afterSalesId: submitData.afterSalesId, + totalCost: submitData.totalCost || 0, + startUserId: useUserStore().userId + }; + submitData.bizExt = { + businessTitle: `售后申请:${submitData.afterSalesSubject}`, + businessCode: submitData.afterSalesCode + }; + submitData.afterSalesStatus = '1'; // 审批中 + submitData.flowStatus = 'waiting'; + + const res = await submitAfterSalesAndFlowStart(submitData); + if (res && res.data) { + form.value = res.data; + if (!form.value.laborCostsList) form.value.laborCostsList = []; + if (!form.value.materialCostsList) form.value.materialCostsList = []; + } + proxy.$modal.msgSuccess('提交成功,流程已发起'); + } else { + // --- 暂存草稿 --- + submitData.flowStatus = 'draft'; + submitData.afterSalesStatus = '0'; // 草稿 + if (submitData.afterSalesId) { + await updateAfterSales(submitData); + } else { + await addAfterSales(submitData); + } + proxy.$modal.msgSuccess('暂存成功'); + } + // 返回列表 + proxy.$tab.closePage(route); + router.go(-1); + } catch (error) { + console.error('提交发生错误:', error); + proxy.$modal.msgError('提交失败,请查看控制台错误详情'); + } finally { + buttonLoading.value = false; + } +}; + // 审批记录初始化 const handleApprovalRecord = () => { if (form.value.afterSalesId) { diff --git a/src/views/oa/erp/budgetInfo/edit.vue b/src/views/oa/erp/budgetInfo/edit.vue index dd6291b..9c5616c 100644 --- a/src/views/oa/erp/budgetInfo/edit.vue +++ b/src/views/oa/erp/budgetInfo/edit.vue @@ -1,5 +1,5 @@ - - + + + + + @@ -41,25 +46,12 @@ default-first-option style="width: 100%" clearable - v-if="scope.row.materialType === MATERIAL_TYPE.MAIN" > - - - + - - + + + + + + + + @@ -131,11 +120,8 @@ --> -
-
- 主要材料费小计: {{ format2TenThousandNumber(mainMaterialTotalAmount) }} 万元 - 合计: {{ formatNumber(totalAmount) }} 万元 -
+
+

填写说明:指研究开发活动过程中投入的各种半成品、原材料、辅助材料等费用。(半成品:包括自制半成品、外协加工件。原材料:外购件、带料外协的外购件。辅助材料:现场使用的耗材)

@@ -153,34 +139,11 @@ const props = defineProps<{ projectId: string; }>(); -const MATERIAL_TYPE = { - MAIN: '1', //主要材料费 - OTHER: '2' //其他材料费 -}; // 材料费数据 const materialData = ref([]); const toDeletedMaterialCostIdList = ref([]); -// 其他材料费 -const otherMaterialData = ref(); - -const defaultOtherMaterial = ref({ - sortOrder: 1, - materialName: '其他材料费', - materialType: MATERIAL_TYPE.OTHER, - unitId: undefined, - unitPrice: undefined, - amount: undefined, - price: 0 -}); -// 响应式数据 -const otherMaterial = ref(defaultOtherMaterial); - -// 合并所有材料数据(主要材料 + 其他材料) -const allMaterialData = computed(() => { - return [...materialData.value, otherMaterial.value]; -}); const unitOptions = ref([]); @@ -188,13 +151,12 @@ const unitOptions = ref([]); // const unitOptions = ref(['kg', 'g', 't', 'm', 'cm', 'mm', 'm²', 'm³', '个', '件', '套', '批', '箱', '袋', '瓶', '小时', '天', '月', '年', '次']); // 总金额和总数量 -const totalAmount = ref(0); const totalQuantity = ref(0); // 主要材料费合计(排除其他材料费) -const mainMaterialTotalAmount = computed(() => { - return materialData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0); -}); +// const totalAmount = computed(() => { +// return materialData.value.reduce((sum, item) => sum + (Number(item.price) || 0), 0); +// }); // 选中的行 const selectedRows = ref([]); @@ -214,31 +176,6 @@ const format2TenThousandNumber = (value: number) => { return (parseFloat(value) / 10000).toFixed(2); }; -const checkSelectable = (row: rdBudgetMaterialCostVO, index: number) => { - return row.materialType === MATERIAL_TYPE.MAIN; -}; - -// 监听 otherMaterialData 的变化 -watch( - otherMaterialData, - (newVal) => { - if (newVal) { - otherMaterial.value = newVal; - } else { - otherMaterial.value = defaultOtherMaterial.value; - } - }, - { immediate: true } -); // immediate: true 立即执行一次 - -// 监听材料数据变化,更新总合计 -watch( - () => [materialData, otherMaterial], - () => { - calculateTotal(); - }, - { deep: true } -); // 添加指定行数 const addSpecifiedRows = async () => { @@ -253,14 +190,13 @@ const addSpecifiedRows = async () => { materialData.value.push({ sortOrder: currentSortOrder + i + 1, materialName: '', - materialType: MATERIAL_TYPE.MAIN, + materialType: '', unitId: undefined, unitPrice: undefined, amount: undefined, price: 0 }); } - otherMaterial.value.sortOrder = currentSortOrder + 1 + addRowCount.value; // 等待DOM更新后滚动到底部 await nextTick(); scrollToBottom(); @@ -290,8 +226,6 @@ const handleDelete = (index: number, row: rdBudgetMaterialCostVO) => { if (row.materialCostId) { toDeletedMaterialCostIdList.value.push(row.materialCostId); } - otherMaterial.value.sortOrder = otherMaterial.value.sortOrder - 1; - calculateTotal(); }; // 批量删除 @@ -311,8 +245,6 @@ const handleBatchDelete = () => { materialData.value.forEach((item, index) => { item.sortOrder = index + 1; }); - otherMaterial.value.sortOrder = otherMaterial.value.sortOrder - selectedRows.value.length; - calculateTotal(); selectedRows.value = []; }; @@ -322,24 +254,44 @@ const calculateAmount = () => { materialData.value.forEach((item) => { item.price = (item.amount || 0) * (item.unitPrice || 0); }); - - // 计算其他材料金额 - // otherMaterial.value.price = (otherMaterial.value.amount || 0) * (otherMaterial.value.unitPrice || 0); - - // 计算总金额 - calculateTotal(); }; -// 计算总金额和总数量 -const calculateTotal = () => { - const mainAmount = parseFloat(((Number(mainMaterialTotalAmount.value) || 0) / 10000).toFixed(2)); - const otherAmount = parseFloat((Number(otherMaterial.value.price) || 0).toFixed(2)); - totalAmount.value = (mainAmount + otherAmount).toFixed(2); -}; +// 合计方法 +const getSummaries = (param: any) => { + const { columns, data } = param; + const sums: any[] = []; + columns.forEach((column: any, index: number) => { + if (index === 2) { + sums[index] = '合计(万元)'; + return; + } + if (column.property === 'price') { + const values = data.map((item: any) => Number(item[column.property])); + if (!values.every((value: number) => Number.isNaN(value))) { + sums[index] = values.reduce((prev: number, curr: number) => { + const value = Number(curr) + if (!Number.isNaN(value)) { + return prev + curr + } else { + return prev + } + }, 0); + sums[index] = formatNumber(sums[index]); + } else { + sums[index] = ''; + } + } else { + sums[index] = ''; + } + }); + return sums; +} + // 获取总金额(供父组件调用) const getTotalAmount = () => { - return (Number(totalAmount.value) || 0) * 10000; + const totalAmount = materialData.value.reduce((sum, item) => sum + Number(item.price), 0) + return totalAmount * 10000; }; // 获取单位列表 @@ -361,32 +313,13 @@ const refreshData = () => { // 重置数据 const resetData = () => { materialData.value.splice(0, materialData.value.length); - totalAmount.value = 0; totalQuantity.value = 0; - - // 重置其他材料费 - // otherMaterial.unit = ''; - // otherMaterial.quantity = 0; - // otherMaterial.unitPrice = 0; - // otherMaterial.amount = 0; }; -// 获取表单数据 -const getFormData = () => { - return { - materialData: [...materialData.value], - otherMaterial: { ...otherMaterial }, - totalAmount: totalAmount.value, - totalQuantity: totalQuantity.value - }; -}; // 暴露方法给父组件 defineExpose({ materialData, - otherMaterial, - otherMaterialData, - allMaterialData, toDeletedMaterialCostIdList, getTotalAmount }); diff --git a/src/views/oa/erp/budgetInfo/rd/OtherCost.vue b/src/views/oa/erp/budgetInfo/rd/OtherCost.vue index 9280276..46c9d76 100644 --- a/src/views/oa/erp/budgetInfo/rd/OtherCost.vue +++ b/src/views/oa/erp/budgetInfo/rd/OtherCost.vue @@ -21,12 +21,26 @@ - - + + - + @@ -34,20 +48,21 @@ + + + - - -
合计: {{ formatNumber(totalAmount) }} 万元
- @@ -63,11 +78,26 @@ const props = defineProps<{ // 响应式数据 const loading = ref(false); -const otherCostList = ref([]); +// const otherCostList = ref([]); const selectedRows = ref([]); const addRowCount = ref(1); const toDeletedOtherCostIdList = ref([]); +// 预算数据 +const otherCostList = ref([ + { sortOrder: 1, itemDesc: '安装费' }, + { sortOrder: 2, itemDesc: '资料/文献费' }, + { sortOrder: 3, itemDesc: '会议费' }, + { sortOrder: 4, itemDesc: '劳务费' }, + { sortOrder: 5, itemDesc: '运输装卸费' }, + { sortOrder: 6, itemDesc: '中介代理费' }, + { sortOrder: 7, itemDesc: '其他费用' } +]); + +const checkSelectable = (row: rdBudgetOtherCostVO, index: number) => { + return row.sortOrder > 7; +}; + // 格式化数字,元转换为万元 const formatNumber = (value: number) => { if (!value) return '0.00'; diff --git a/src/views/oa/erp/budgetInfo/rd/TechCost.vue b/src/views/oa/erp/budgetInfo/rd/TechCost.vue new file mode 100644 index 0000000..e18bcad --- /dev/null +++ b/src/views/oa/erp/budgetInfo/rd/TechCost.vue @@ -0,0 +1,653 @@ + + + + + diff --git a/src/views/oa/erp/budgetInfo/rd/TestingCost.vue b/src/views/oa/erp/budgetInfo/rd/TestingCost.vue index 8eb394b..06b3abc 100644 --- a/src/views/oa/erp/budgetInfo/rd/TestingCost.vue +++ b/src/views/oa/erp/budgetInfo/rd/TestingCost.vue @@ -23,17 +23,31 @@ - + - + - + @@ -64,10 +78,8 @@ -
-
- 测试化验加工费总计(万元):{{ formatNumber(totalAmount) }} -
+
+

注:是指支付给外单位(包括公司内部经济核算单位)的检验、测试、化验及加工等费用。

@@ -215,7 +227,6 @@ const refreshData = () => { const resetData = () => { testData.value.splice(0, testData.value.length); totalAmount.value = 0; - idCounter = 1; }; // 获取表单数据 diff --git a/src/views/oa/erp/budgetInfo/rd/TravelMeetingExchange.vue b/src/views/oa/erp/budgetInfo/rd/TravelMeetingExchange.vue index eab7c8d..4964f3e 100644 --- a/src/views/oa/erp/budgetInfo/rd/TravelMeetingExchange.vue +++ b/src/views/oa/erp/budgetInfo/rd/TravelMeetingExchange.vue @@ -39,10 +39,10 @@ '', '', '', - '合计(万元)', - format2TenThousandNumber(travelTransportTotal), - format2TenThousandNumber(travelAccommodationTotal), - format2TenThousandNumber(travelSubsidyTotal), + '合计', + formatNumber(travelTransportTotal), + formatNumber(travelAccommodationTotal), + formatNumber(travelSubsidyTotal), formatNumber(travelSubtotalTotal), '' ]; @@ -111,118 +111,21 @@
- - -
-

会议费预算明细表

-
- 行数: - - - - - - 添加 - - - - - - 删除 - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - +
-

国际交流费预算明细表

+

交通费明细表

行数: - - + + 添加 - + @@ -233,97 +136,61 @@ - + - + - - - - + + - - - - - - - - - + + +
+

注:开展实验(试验)、考察、业务调研、学术交流等发生的外埠差旅费、市内交通费用等。开支标准按照公司有关规定执行。

+
@@ -331,14 +198,17 @@ import { ref, computed, watch } from 'vue'; import { ElMessage } from 'element-plus'; import { rdBudgetTravelCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetTravelCost/types'; -import { rdBudgetMeetingCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetMeetingCost/types'; -import { rdBudgetExchangeCostVO } from '@/api/oa/erp/budgetInfo/rd/rdBudgetExchangeCost/types'; // Props const props = defineProps<{ projectId?: string; }>(); +const TRIP_TYPE = { + TRAVEL: '1', //差旅费 + TRANSPORTATION: '2' //交通费 +}; + // 响应式数据 const loading = ref(false); @@ -348,17 +218,11 @@ const selectedTravelRows = ref([]); const travelAddRowCount = ref(1); const toDeletedTravelCostIdList = ref([]); -// 会议费相关 -const meetingList = ref([]); -const selectedMeetingRows = ref([]); -const meetingAddRowCount = ref(1); -const toDeletedMeetingCostIdList = ref([]); - -// 国际交流费相关 -const exchangeList = ref([]); -const selectedInternationalRows = ref([]); -const internationalAddRowCount = ref(1); -const toDeletedExchangeCostIdList = ref([]); +// 交通费相关 +const transportationList = ref([]); +const selectedTransportationRows = ref([]); +const transportationAddRowCount = ref(1); +const toDeletedTransportationCostIdList = ref([]); // 计算差旅费合计 //往返路费 @@ -385,19 +249,10 @@ const travelSubtotalTotal = computed(() => { }, 0); }); -// 计算会议费合计(需要转成万元,2位小数再相加) -const meetingFeeTotal = computed(() => { - return meetingList.value.reduce((sum, item) => { - const amountInTenThousand = (Number(item.meetingPrice) || 0) / 10000; - const formattedAmount = parseFloat(amountInTenThousand.toFixed(2)); - return sum + formattedAmount; - }, 0); -}); - -// 计算国际交流费合计(需要转成万元,2位小数再相加) -const internationalSubtotalTotal = computed(() => { - return exchangeList.value.reduce((sum, item) => { - const amountInTenThousand = (Number(item.price) || 0) / 10000; +// 计算交通费合计(需要转成万元,2位小数再相加) +const transportationSubtotalTotal = computed(() => { + return transportationList.value.reduce((sum, item) => { + const amountInTenThousand = (Number(item.subtotalCosts) || 0) / 10000; const formattedAmount = parseFloat(amountInTenThousand.toFixed(2)); return sum + formattedAmount; }, 0); @@ -419,47 +274,19 @@ const format2TenThousandNumber = (value: number) => { const calculateTravelSubtotal = () => { travelList.value.forEach((item) => { // 住宿费 = 人数 * 天数 * 住宿标准 - item.stayCosts = (item.peopleNumber || 0) * (item.days || 0) * (item.stayStandard || 0); + item.stayCosts = (item.peopleNumber || 0) * (item.frequency || 0) * (item.days || 0) * (item.stayStandard || 0); // 补贴 = 90 * 人数 * 天数 - item.subsidyCosts = 90 * (item.peopleNumber || 0) * (item.days || 0); + item.subsidyCosts = 90 * (item.peopleNumber || 0) * (item.frequency || 0) * (item.days || 0); // 小计 = (往返路费 + 住宿费 + 补贴) / 10000 item.subtotalCosts = (item.travelExpenses || 0) + item.stayCosts + item.subsidyCosts; }); }; -// 计算会议费 -const calculateMeetingFee = () => { - meetingList.value.forEach((item) => { - // 公司组织会议:((场地日租金 + 日均杂费) * 天数 + 专家交通住宿费) / 10000 - // 外出参加会议:(人数 * 人均交费) / 10000 - //会议费=公司组织会议费用+外出参加会议费用 - item.meetingPrice = - ((item.rentalFee || 0) + (item.dailyExpense || 0)) * (item.days || 0) + - (item.expertExpense || 0) + - (item.peopleNumber || 0) * (item.perPersonExpense || 0); - }); -}; -// 计算国际交流费小计 -const calculateInternationalSubtotal = () => { - exchangeList.value.forEach((item) => { - // 如果合作交流类型是出国考察,则补贴=40*人数*时间 - if (item.communicationType === '出国考察') { - item.subsidy = 40 * (item.peopleNumber || 0) * (item.days || 0); - } else if (!item.subsidy) { - // 其他类型如果没有设置补贴,默认设为0 - item.subsidy = 0; - } - // 小计 = (往返路费及住宿费 + 补贴) / 10000 - item.price = (item.travelAccommodationExpense || 0) + (item.subsidy || 0); - }); -}; - -const getTravelMeetingExchangeAmount = () => { +const getTravelAmount = () => { return { travelSubtotalTotal: (Number(travelSubtotalTotal.value) || 0) * 10000, - meetingFeeTotal: (Number(meetingFeeTotal.value) || 0) * 10000, - internationalSubtotalTotal: (Number(internationalSubtotalTotal.value) || 0) * 10000 + transportationSubtotalTotal: (Number(transportationSubtotalTotal.value) || 0) * 10000 }; }; @@ -469,12 +296,13 @@ const confirmTravelAdd = () => { for (let i = 0; i < travelAddRowCount.value; i++) { const newItem: rdBudgetTravelCostVO = { sortOrder: currentSortOrder + i + 1, + tripType: TRIP_TYPE.TRAVEL, tripLocation: '', reason: '', frequency: undefined, peopleNumber: undefined, days: undefined, - stayStandard: undefined, + stayStandard: 180, travelExpenses: undefined, stayCosts: 0, subsidyCosts: 0, @@ -540,23 +368,19 @@ const handleTravelBatchDelete = () => { selectedTravelRows.value = []; }; -// 会议费相关方法 -const confirmMeetingAdd = () => { - const currentSortOrder = meetingList.value && meetingList.value.length > 0 ? Math.max(...meetingList.value.map((item) => item.sortOrder)) : 0; - for (let i = 0; i < meetingAddRowCount.value; i++) { - const newItem: rdBudgetMeetingCostVO = { +// 交通费相关方法 +const confirmTransportationAdd = () => { + const currentSortOrder = transportationList.value && transportationList.value.length > 0 ? Math.max(...transportationList.value.map((item) => item.sortOrder)) : 0; + + for (let i = 0; i < transportationAddRowCount.value; i++) { + const newItem: rdBudgetTravelCostVO = { sortOrder: currentSortOrder + i + 1, - meetingContent: '', - rentalFee: undefined, - dailyExpense: undefined, - days: undefined, - expertExpense: undefined, - peopleNumber: undefined, - perPersonExpense: undefined, - meetingPrice: 0 + tripType: TRIP_TYPE.TRANSPORTATION, + tripLocation: '', + reason: '', }; - meetingList.value.push(newItem); + transportationList.value.push(newItem); } // 滚动到最下方并聚焦到新添加的行 @@ -568,83 +392,7 @@ const confirmMeetingAdd = () => { // 尝试聚焦到新添加的第一行的第一个输入框 const rows = document.querySelectorAll('.el-table__body tr'); - const newRow = rows[rows.length - meetingAddRowCount.value]; - if (newRow) { - const firstInput = newRow.querySelector('input, select'); - if (firstInput) { - firstInput.focus(); - } - } - }, 0); -}; - -const handleMeetingSelectionChange = (selection: rdBudgetMeetingCostVO[]) => { - selectedMeetingRows.value = selection; -}; - -// 删除单行 -const handleMeetingDelete = (index: number, row: rdBudgetMeetingCostVO) => { - meetingList.value.splice(index, 1); - // 重新编号 - meetingList.value.forEach((item, index) => { - item.sortOrder = index + 1; - }); - if (row.meetingCostId) { - toDeletedMeetingCostIdList.value.push(row.meetingCostId); - } - calculateMeetingFee(); -}; - -// 批量删除 -const handleMeetingBatchDelete = () => { - if (selectedMeetingRows.value.length === 0) { - ElMessage.warning('请选择要删除的行'); - return; - } - selectedMeetingRows.value.forEach((selectedRow) => { - if (selectedRow.meetingCostId) { - toDeletedMeetingCostIdList.value.push(selectedRow.meetingCostId); - } - }); - - meetingList.value = meetingList.value.filter((item) => !selectedMeetingRows.value.includes(item)); - // 重新编号 - meetingList.value.forEach((item, index) => { - item.sortOrder = index + 1; - }); - calculateMeetingFee(); - selectedMeetingRows.value = []; -}; - -// 国际交流费相关方法 -const confirmInternationalAdd = () => { - const currentSortOrder = exchangeList.value && exchangeList.value.length > 0 ? Math.max(...exchangeList.value.map((item) => item.sortOrder)) : 0; - - for (let i = 0; i < internationalAddRowCount.value; i++) { - const newItem: rdBudgetExchangeCostVO = { - sortOrder: currentSortOrder + i + 1, - communicationType: '', - countryRegion: '', - institution: '', - peopleNumber: undefined, - days: undefined, - travelAccommodationExpense: undefined, - subsidy: undefined, - price: 0 - }; - exchangeList.value.push(newItem); - } - - // 滚动到最下方并聚焦到新添加的行 - setTimeout(() => { - const table = document.querySelector('.el-table__body-wrapper'); - if (table) { - table.scrollTop = table.scrollHeight; - } - - // 尝试聚焦到新添加的第一行的第一个输入框 - const rows = document.querySelectorAll('.el-table__body tr'); - const newRow = rows[rows.length - internationalAddRowCount.value]; + const newRow = rows[rows.length - transportationAddRowCount.value]; if (newRow) { const firstInput = newRow.querySelector('input, select'); if (firstInput) { @@ -655,42 +403,40 @@ const confirmInternationalAdd = () => { }; -const handleInternationalSelectionChange = (selection: rdBudgetExchangeCostVO[]) => { - selectedInternationalRows.value = selection; +const handleTransportationSelectionChange = (selection: rdBudgetTravelCostVO[]) => { + selectedTransportationRows.value = selection; }; // 删除单行 -const handleExchangeDelete = (index: number, row: rdBudgetExchangeCostVO) => { - exchangeList.value.splice(index, 1); +const handleTransportationDelete = (index: number, row: rdBudgetTravelCostVO) => { + transportationList.value.splice(index, 1); // 重新编号 - exchangeList.value.forEach((item, index) => { + transportationList.value.forEach((item, index) => { item.sortOrder = index + 1; }); - if (row.exchangeCostId) { - toDeletedExchangeCostIdList.value.push(row.exchangeCostId); + if (row.travelCostId) { + toDeletedTravelCostIdList.value.push(row.travelCostId); } - calculateInternationalSubtotal(); }; // 批量删除 -const handleExchangeBatchDelete = () => { - if (selectedInternationalRows.value.length === 0) { +const handleTransportationBatchDelete = () => { + if (selectedTransportationRows.value.length === 0) { ElMessage.warning('请选择要删除的行'); return; } - selectedInternationalRows.value.forEach((selectedRow) => { - if (selectedRow.exchangeCostId) { - toDeletedExchangeCostIdList.value.push(selectedRow.exchangeCostId); + selectedTransportationRows.value.forEach((selectedRow) => { + if (selectedRow.travelCostId) { + toDeletedTravelCostIdList.value.push(selectedRow.travelCostId); } }); - exchangeList.value = exchangeList.value.filter((item) => !selectedInternationalRows.value.includes(item)); + transportationList.value = transportationList.value.filter((item) => !selectedTransportationRows.value.includes(item)); // 重新编号 - exchangeList.value.forEach((item, index) => { + transportationList.value.forEach((item, index) => { item.sortOrder = index + 1; }); - calculateInternationalSubtotal(); - selectedInternationalRows.value = []; + selectedTransportationRows.value = []; }; // 监听projectId变化,重新加载数据 @@ -707,11 +453,8 @@ watch( defineExpose({ travelList, toDeletedTravelCostIdList, - meetingList, - toDeletedMeetingCostIdList, - exchangeList, - toDeletedExchangeCostIdList, - getTravelMeetingExchangeAmount + transportationList, + getTravelAmount }); diff --git a/src/views/oa/erp/erpProjectPlan/edit.vue b/src/views/oa/erp/erpProjectPlan/edit.vue index 596c7f0..3f32e63 100644 --- a/src/views/oa/erp/erpProjectPlan/edit.vue +++ b/src/views/oa/erp/erpProjectPlan/edit.vue @@ -80,12 +80,14 @@ - - + @@ -280,22 +282,6 @@ - - - - - - - - - - -
@@ -371,14 +357,6 @@ const projectSelectRef = ref>(); const projectChangeList = ref([]); // 变更记录列表 const activeChangeTab = ref('0'); // 当前活跃的变更标签 -// 附件上传对话框状态 -const type = ref(0); -const dialog = reactive<{ visible: boolean; title: string }>({ - visible: false, - title: '' -}); -const ossFileModel = ref(undefined); - const createEmptyForm = (): ErpProjectPlanForm => ({ projectPlanId: undefined, projectId: undefined, @@ -549,38 +527,16 @@ const recalcReceivableDate = (row: any) => { row.receivableDate = proxy?.parseTime ? proxy.parseTime(target, '{y}-{m}-{d}') : target.toISOString().slice(0, 10); }; -// 打开附件上传对话框(编辑模式) -const handleFile = () => { - type.value = 0; - dialog.visible = true; - dialog.title = '上传项目计划附件'; - // 回显已有附件 - ossFileModel.value = form.value.ossId as any; -}; - -// 查看模式下预览附件 -const handlePreview = () => { - if (!form.value.ossId) { - proxy?.$modal.msgWarning('暂无附件可预览'); - return; +// 附件ID字符串转换(用于FileUpload组件) +const ossIdString = computed({ + get() { + const v = form.value.ossId as any; + return v === undefined || v === null ? '' : String(v); + }, + set(val: string) { + form.value.ossId = val || (undefined as any); } - type.value = 0; - dialog.visible = true; - dialog.title = '预览项目计划附件'; - ossFileModel.value = form.value.ossId as any; -}; - -// 提交附件 -const submitOss = () => { - form.value.ossId = ossFileModel.value as any; - dialog.visible = false; - proxy?.$modal.msgSuccess('附件已更新'); -}; - -// 关闭附件对话框 -const cancel = () => { - dialog.visible = false; -}; +}); /** 提交按钮(与项目信息保持一致的处理方式) */ const submitForm = (status: string, mode: boolean) => { @@ -615,7 +571,8 @@ const submitForm = (status: string, mode: boolean) => { form.value.variables = { projectId: form.value.projectId, projectName: project?.projectName, - managerId: form.value.managerId + managerId: form.value.managerId, + chargeId: form.value.chargeId }; // 流程实例业务扩展字段 form.value.bizExt = { @@ -755,6 +712,7 @@ const loadFormData = async () => { if (!form.value.planStageList) { form.value.planStageList = []; } + console.log('ossId:', form.value.ossId); // 添加ossId调试日志 if (form.value.projectId) { // 编辑模式下,同步项目名称和编号 const project = projectInfoList.value.find((item) => String(item.projectId) === String(form.value.projectId)); diff --git a/src/views/oa/erp/projectAcceptance/edit.vue b/src/views/oa/erp/projectAcceptance/edit.vue index ec0cf1d..b24801d 100644 --- a/src/views/oa/erp/projectAcceptance/edit.vue +++ b/src/views/oa/erp/projectAcceptance/edit.vue @@ -14,7 +14,7 @@
- + @@ -50,13 +50,21 @@ type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择验收日期" + :disabled="formDisabled" clearable /> - + @@ -71,7 +79,13 @@ - + @@ -198,9 +212,12 @@ const onProjectChange = async (val: any) => { const applyLeaderFromManager = () => {}; const loadDetail = async () => { + console.log('loadDetail start'); if (!routeParams.value.id) return; const res = await getProjectAcceptance(routeParams.value.id); + console.log('loadDetail res:', res); Object.assign(form.value, res.data); + console.log('loadDetail end'); }; const submitForm = (status: string, mode: boolean) => { diff --git a/src/views/oa/erp/projectReceiving/edit.vue b/src/views/oa/erp/projectReceiving/edit.vue index 2e4c3f3..3140e14 100644 --- a/src/views/oa/erp/projectReceiving/edit.vue +++ b/src/views/oa/erp/projectReceiving/edit.vue @@ -14,7 +14,7 @@ - + @@ -45,12 +45,35 @@ - + + + + + + + + {{ dict.label }} + + - + @@ -65,7 +88,13 @@ - + @@ -102,6 +131,10 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance; const route = useRoute(); const router = useRouter(); +const { wf_business_status, receiving_status, is_all_receiving } = toRefs( + proxy?.useDict('wf_business_status', 'receiving_status', 'is_all_receiving') +); + // 路由参数(与项目信息保持一致) const routeParams = ref>({}); @@ -138,6 +171,7 @@ const initFormData: ProjectReceivingForm = { deputyId: undefined, deputyName: undefined as any, remark: undefined, + isAllReceiving: undefined, receivingStatus: '1', flowStatus: 'draft' as any, flowCode: FlowCodeEnum.PROJECT_RECEIVING_CODE, @@ -200,7 +234,11 @@ const applyLeaderFromManager = () => {}; const loadDetail = async () => { if (!routeParams.value.id) return; const res = await getProjectReceiving(routeParams.value.id); + console.log('[projectReceiving] 后端返回数据:', res.data); + console.log('[projectReceiving] 后端返回ossId:', res.data?.ossId); Object.assign(form.value, res.data); + console.log('[projectReceiving] 赋值后form.ossId:', form.value.ossId); + console.log('[projectReceiving] ossIdString计算属性值:', ossIdString.value); }; const submitForm = (status: string, mode: boolean) => { diff --git a/src/views/oa/erp/projectReceiving/index.vue b/src/views/oa/erp/projectReceiving/index.vue index 6b96ec6..7db084c 100644 --- a/src/views/oa/erp/projectReceiving/index.vue +++ b/src/views/oa/erp/projectReceiving/index.vue @@ -77,12 +77,17 @@ - + + + + - + @@ -140,7 +145,9 @@ import ApprovalRecord from '@/components/Process/approvalRecord.vue'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const router = useRouter(); const route = useRoute(); -const { wf_business_status, receiving_status } = toRefs(proxy?.useDict('wf_business_status', 'receiving_status')); +const { wf_business_status, receiving_status, is_all_receiving } = toRefs( + proxy?.useDict('wf_business_status', 'receiving_status', 'is_all_receiving') +); const projectReceivingList = ref([]); const buttonLoading = ref(false); @@ -172,8 +179,9 @@ const columns = ref([ { key: 7, label: `部门负责人`, visible: true }, { key: 8, label: `分管副总`, visible: true }, { key: 9, label: `备注`, visible: true }, - { key: 10, label: `业务状态`, visible: true }, - { key: 11, label: `流程状态`, visible: false } + { key: 10, label: `全部到货标识`, visible: true }, + { key: 11, label: `业务状态`, visible: true }, + { key: 12, label: `流程状态`, visible: false } ]); const initFormData: ProjectReceivingForm = { @@ -185,7 +193,8 @@ const initFormData: ProjectReceivingForm = { ossId: undefined, chargeId: undefined, deputyId: undefined, - remark: undefined + remark: undefined, + isAllReceiving: undefined }; const data = reactive>({ form: { ...initFormData }, diff --git a/src/views/oa/erp/timesheetInfo/edit.vue b/src/views/oa/erp/timesheetInfo/edit.vue index e3b9b51..e235397 100644 --- a/src/views/oa/erp/timesheetInfo/edit.vue +++ b/src/views/oa/erp/timesheetInfo/edit.vue @@ -30,17 +30,7 @@ - - - + @@ -59,6 +49,7 @@ value-format="YYYY-MM-DD" placeholder="本周一" style="width: 100%" + :disabled-date="disableNonMonday" @change="handleStartTimeChange" /> @@ -271,7 +262,6 @@ const userStore = useUserStore(); const routeParams = ref({}); const buttonLoading = ref(false); const timesheetFormRef = ref(); -const isCodeGenerated = ref(false); const submitVerifyRef = ref>(); const approvalRecordRef = ref>(); @@ -304,7 +294,7 @@ const data = reactive({ timesheetProjectList: [] as any[] }, rules: { - timesheetCode: [{ required: true, message: '编号不能为空', trigger: 'blur' }], + // timesheetCode: [{ required: true, message: '编号不能为空', trigger: 'blur' }], startTime: [{ required: true, message: '请选择起始日期', trigger: 'change' }], endTime: [{ required: true, message: '请选择结束日期', trigger: 'change' }] } @@ -328,7 +318,6 @@ onMounted(async () => { form.value = res.data; if (!form.value.timesheetDeptList) form.value.timesheetDeptList = []; if (!form.value.timesheetProjectList) form.value.timesheetProjectList = []; - if (form.value.timesheetCode) isCodeGenerated.value = true; } else { setWeekDates(); form.value.timesheetDeptList = []; @@ -448,21 +437,13 @@ const getDeptName = (deptId: any) => { return dept ? dept.deptName : ''; }; -/** 生成编号 */ -const generateCode = async () => { - if (isCodeGenerated.value) return; - try { - const res = await getRuleGenerateCode({ codeRuleCode: CodeRuleEnum.TIMESHEET } as any); - if (res.code === 200) { - form.value.timesheetCode = res.msg; - isCodeGenerated.value = true; - proxy?.$modal.msgSuccess('生成成功'); - } - } catch (error) { - console.error(error); - } +/** 禁用非周一的日期 */ +const disableNonMonday = (date: Date) => { + // getDay() 返回 0=周日, 1=周一, ..., 6=周六 + return date.getDay() !== 1; }; +/** 生成编号 */ /** 自动计算工时 */ const calculateHours = () => { const deptList = form.value.timesheetDeptList || []; @@ -525,103 +506,111 @@ const getSummary = (param: any) => { /** 提交按钮 */ const submitForm = (status: string) => { + if (status === 'draft') { + executeSubmit(status); + return; + } timesheetFormRef.value?.validate(async (valid: boolean) => { if (valid) { - buttonLoading.value = true; - try { - const submitData: any = { ...form.value }; - - if (status !== 'draft') { - // 提交前校验 - if (submitData.timesheetDeptList && submitData.timesheetDeptList.length > 0) { - for (let i = 0; i < submitData.timesheetDeptList.length; i++) { - const row = submitData.timesheetDeptList[i]; - // 检查每一行是否选了审批人 - if (!row.approverId) { - proxy.$modal.msgError(`部门工作:第 ${i + 1} 行未选择审批人`); - buttonLoading.value = false; - return; // 阻止提交 - } - } - } - if (submitData.timesheetProjectList && submitData.timesheetProjectList.length > 0) { - for (let i = 0; i < submitData.timesheetProjectList.length; i++) { - const row = submitData.timesheetProjectList[i]; - if (!row.approverId) { - proxy.$modal.msgError(`项目工作:第 ${i + 1} 行未选择审批人`); - buttonLoading.value = false; - return; // 阻止提交 - } - } - } - if (submitData.timesheetDeptList.length === 0 && submitData.timesheetProjectList.length === 0) { - proxy.$modal.msgError('请至少填写一项部门工时或项目工时'); - buttonLoading.value = false; - return; - } - // 提交审批 - submitData.flowCode = FlowCodeEnum.TIMESHEET_KEY; - - // 提取部门工时审批人 (取 approverId) - const deptApproverSet = new Set(); - submitData.timesheetDeptList.forEach((item: any) => { - if (item.approverId) deptApproverSet.add(item.approverId); - }); - const deptApprovers = Array.from(deptApproverSet); - - // 提取项目工时审批人 (取 approverId) - const projectApproverSet = new Set(); - submitData.timesheetProjectList.forEach((item: any) => { - if (item.approverId) projectApproverSet.add(item.approverId); - }); - const projectApprovers = Array.from(projectApproverSet); - - // 必须选择审批人 - if (deptApprovers.length === 0 && projectApprovers.length === 0) { - proxy.$modal.msgError('请至少填写一项工时并指定审批人'); - buttonLoading.value = false; - return; - } - - // 放入流程变量 - submitData.variables = { - startUserId: userStore.userId, - totalHours: submitData.totalHours, - deptApprovers: deptApprovers, // 对应后端 variables.put("deptApprovers", ...) - projectApprovers: projectApprovers, // 对应后端 variables.put("projectApprovers", ...) - hasDeptWork: deptApprovers.length > 0, // 前端也可以传这个标记,双重保险 - hasProjectWork: projectApprovers.length > 0 // 前端也可以传这个标记 - }; - - submitData.bizExt = { - businessTitle: `工时填报:${userStore.nickname}`, - businessCode: submitData.timesheetCode - }; - submitData.timesheetStatus = '2'; // 已提交 - submitData.flowStatus = 'waiting'; - - const res = await submitTimesheetAndFlowStart(submitData); - if (res?.data) form.value = res.data; - proxy.$modal.msgSuccess('提交成功'); - } else { - // 暂存 - submitData.timesheetStatus = '1'; // 草稿 - submitData.flowStatus = 'draft'; - if (submitData.timesheetId) await updateTimesheetInfo(submitData); - else await addTimesheetInfo(submitData); - proxy.$modal.msgSuccess('暂存成功'); - } - proxy.$tab.closePage(route); - router.go(-1); - } catch (e) { - console.error(e); - } finally { - buttonLoading.value = false; - } + executeSubmit(status); } }); }; +const executeSubmit = async (status: string) => { + buttonLoading.value = true; + try { + const submitData: any = { ...form.value }; + + if (status !== 'draft') { + // 提交前校验 + if (submitData.timesheetDeptList && submitData.timesheetDeptList.length > 0) { + for (let i = 0; i < submitData.timesheetDeptList.length; i++) { + const row = submitData.timesheetDeptList[i]; + // 检查每一行是否选了审批人 + if (!row.approverId) { + proxy.$modal.msgError(`部门工作:第 ${i + 1} 行未选择审批人`); + buttonLoading.value = false; + return; // 阻止提交 + } + } + } + if (submitData.timesheetProjectList && submitData.timesheetProjectList.length > 0) { + for (let i = 0; i < submitData.timesheetProjectList.length; i++) { + const row = submitData.timesheetProjectList[i]; + if (!row.approverId) { + proxy.$modal.msgError(`项目工作:第 ${i + 1} 行未选择审批人`); + buttonLoading.value = false; + return; // 阻止提交 + } + } + } + if (submitData.timesheetDeptList.length === 0 && submitData.timesheetProjectList.length === 0) { + proxy.$modal.msgError('请至少填写一项部门工时或项目工时'); + buttonLoading.value = false; + return; + } + // 提交审批 + submitData.flowCode = FlowCodeEnum.TIMESHEET_KEY; + + // 提取部门工时审批人 (取 approverId) + const deptApproverSet = new Set(); + submitData.timesheetDeptList.forEach((item: any) => { + if (item.approverId) deptApproverSet.add(item.approverId); + }); + const deptApprovers = Array.from(deptApproverSet); + + // 提取项目工时审批人 (取 approverId) + const projectApproverSet = new Set(); + submitData.timesheetProjectList.forEach((item: any) => { + if (item.approverId) projectApproverSet.add(item.approverId); + }); + const projectApprovers = Array.from(projectApproverSet); + + // 必须选择审批人 + if (deptApprovers.length === 0 && projectApprovers.length === 0) { + proxy.$modal.msgError('请至少填写一项工时并指定审批人'); + buttonLoading.value = false; + return; + } + + // 放入流程变量 + submitData.variables = { + startUserId: userStore.userId, + totalHours: submitData.totalHours, + deptApprovers: deptApprovers, // 对应后端 variables.put("deptApprovers", ...) + projectApprovers: projectApprovers, // 对应后端 variables.put("projectApprovers", ...) + hasDeptWork: deptApprovers.length > 0, // 前端也可以传这个标记,双重保险 + hasProjectWork: projectApprovers.length > 0 // 前端也可以传这个标记 + }; + + submitData.bizExt = { + businessTitle: `工时填报:${userStore.nickname}`, + businessCode: submitData.timesheetCode + }; + submitData.timesheetStatus = '2'; // 已提交 + submitData.flowStatus = 'waiting'; + + const res = await submitTimesheetAndFlowStart(submitData); + if (res?.data) form.value = res.data; + proxy.$modal.msgSuccess('提交成功'); + } else { + // 暂存 + submitData.timesheetStatus = '1'; // 草稿 + submitData.flowStatus = 'draft'; + if (submitData.timesheetId) await updateTimesheetInfo(submitData); + else await addTimesheetInfo(submitData); + proxy.$modal.msgSuccess('暂存成功'); + } + proxy.$tab.closePage(route); + router.go(-1); + } catch (e) { + console.error(e); + } finally { + buttonLoading.value = false; + } +}; + const handleApprovalRecord = () => { if (form.value.timesheetId) approvalRecordRef.value?.init(form.value.timesheetId); }; diff --git a/src/views/oa/erp/timesheetInfo/index.vue b/src/views/oa/erp/timesheetInfo/index.vue index 0515a08..9c389ed 100644 --- a/src/views/oa/erp/timesheetInfo/index.vue +++ b/src/views/oa/erp/timesheetInfo/index.vue @@ -54,6 +54,7 @@ + @@ -145,7 +146,8 @@ const columns = ref([ { key: 16, label: `创建时间`, visible: true }, { key: 17, label: `更新者`, visible: true }, { key: 18, label: `更新时间`, visible: true }, - { key: 99, label: `人员`, visible: true } + { key: 99, label: `人员`, visible: true }, + { key: 100, label: `人员部门`, visible: true } ]); const initFormData: TimesheetInfoForm = { @@ -181,7 +183,7 @@ const data = reactive>({ }, rules: { timesheetId: [{ required: true, message: '工时填报ID不能为空', trigger: 'blur' }], - timesheetCode: [{ required: true, message: '工时填报编号不能为空', trigger: 'blur' }], + // timesheetCode: [{ required: true, message: '工时填报编号不能为空', trigger: 'blur' }], userId: [{ required: true, message: '人员ID不能为空', trigger: 'blur' }], startTime: [{ required: true, message: '起始时间不能为空', trigger: 'blur' }], endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }]