From c7e1f9483df6576b02f12848888db9946d55e424 Mon Sep 17 00:00:00 2001 From: yangk Date: Fri, 22 May 2026 14:56:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(oa/erp):=20=E9=A2=84=E6=8A=95=E5=B7=A5?= =?UTF-8?q?=E6=97=B6=E5=88=86=E9=85=8D=E5=8A=9F=E8=83=BD=E7=94=B1=E5=91=98?= =?UTF-8?q?=E5=B7=A5=E7=BA=A7=E5=88=86=E9=85=8D=E6=94=B9=E4=B8=BA=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BA=A7=E4=B8=94=E4=B8=8D=E5=9B=9E=E5=86=99=E6=9C=88?= =?UTF-8?q?=E6=B1=87=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/oa/erp/timesheetPreAlloc/index.ts | 18 +- src/api/oa/erp/timesheetPreAlloc/types.ts | 302 ++------------- src/views/oa/erp/timesheetPreAlloc/index.vue | 375 +++++++++---------- 3 files changed, 209 insertions(+), 486 deletions(-) diff --git a/src/api/oa/erp/timesheetPreAlloc/index.ts b/src/api/oa/erp/timesheetPreAlloc/index.ts index d3a5ac5..11887f6 100644 --- a/src/api/oa/erp/timesheetPreAlloc/index.ts +++ b/src/api/oa/erp/timesheetPreAlloc/index.ts @@ -30,9 +30,9 @@ export const getTimesheetPreAlloc = (allocId: string | number): AxiosPromise
 => {
+export const getAllocDetails = (query: { monthCode: string; projectId: string | number }): AxiosPromise => {
   return request({
-    url: '/oa/erp/timesheetPreAlloc/getStaffAllocDetails',
+    url: '/oa/erp/timesheetPreAlloc/getAllocDetails',
     method: 'get',
     params: query
   });
@@ -89,10 +89,10 @@ export const delTimesheetPreAlloc = (allocId: string | number | Array
           
             
-              
+              
             
             
               
             
             
@@ -62,7 +58,7 @@
               type="danger"
               plain
               icon="Delete"
-              :disabled="multiple || hasAppliedSelected"
+              :disabled="multiple"
               @click="handleDelete()"
               v-hasPermi="['oa/erp:timesheetPreAlloc:remove']"
             >
@@ -78,9 +74,9 @@
 
       
         
-        
-        
-        
+        
+        
+        
           
         
-        
+        
           
         
-        
+        
           
         
-        
-        
+        
+        
           
         
-        
-          
-        
         
         
           
         
@@ -130,7 +112,7 @@
       
     
 
-    
+    
       
         
           
@@ -156,7 +138,7 @@
                 :disabled="Boolean(form.allocId)"
                 placeholder="请选择来源预投项目"
                 class="full-width"
-                @change="loadStaffAllocDetails"
+                @change="loadAllocDetails"
               >
                 
         
 
-        
-          
-          
-            
-          
-          
-            
-          
-          
-            
-          
-          
-            
-          
-        
+        
+
+ 分配去向 + 添加去向 +
+ + + + + + + + + + + +
@@ -233,8 +212,8 @@
@@ -248,14 +227,13 @@ import { delTimesheetPreAlloc, addTimesheetPreAlloc, updateTimesheetPreAlloc, - getStaffAllocDetails, + getAllocDetails, listAvailableSourceProjects } from '@/api/oa/erp/timesheetPreAlloc'; import { TimesheetPreAllocVO, TimesheetPreAllocQuery, TimesheetPreAllocForm, - PreAllocStaffAlloc, PreAllocTarget, PreAllocDetailVO } from '@/api/oa/erp/timesheetPreAlloc/types'; @@ -294,15 +272,14 @@ const dialog = reactive({ }); const columns = ref([ - { key: 0, label: `月份`, visible: true }, - { key: 1, label: `来源预投项目`, visible: true }, - { key: 2, label: `来源工时`, visible: true }, - { key: 3, label: `已分配工时`, visible: true }, - { key: 4, label: `涉及人数`, visible: true }, - { key: 5, label: `分配状态`, visible: true }, - { key: 6, label: `回写时间`, visible: true }, - { key: 7, label: `备注`, visible: true }, - { key: 8, label: `分配单编号`, visible: true } + { key: 0, label: `分配单编号`, visible: true }, + { key: 1, label: `月份`, visible: true }, + { key: 2, label: `来源预投项目`, visible: true }, + { key: 3, label: `来源工时`, visible: true }, + { key: 4, label: `已分配工时`, visible: true }, + { key: 5, label: `涉及人数`, visible: true }, + { key: 6, label: `分配状态`, visible: true }, + { key: 7, label: `备注`, visible: true } ]); const getCurrentMonthCode = () => { @@ -318,9 +295,8 @@ const initFormData = (): TimesheetPreAllocForm => ({ allocatedTotalHours: 0, staffCount: 0, allocStatus: '0', - appliedFlag: '0', remark: undefined, - staffAllocList: [] + allocItems: [] }); const data = reactive>({ @@ -362,37 +338,50 @@ const toNumber = (value?: number | string | null) => { const formatHours = (value?: number | string | null) => toNumber(value).toFixed(2); -const calcStaffAllocated = (staff: PreAllocStaffAlloc) => (staff.allocItems || []).reduce((sum, item) => sum + toNumber(item.allocHours), 0); +const normalizeTarget = (item: PreAllocTarget): PreAllocTarget => ({ + targetProjectId: item.targetProjectId ? String(item.targetProjectId) : undefined, + targetProjectCode: item.targetProjectCode, + targetProjectName: item.targetProjectName, + allocHours: toNumber(item.allocHours) +}); -const calcStaffRemaining = (staff: PreAllocStaffAlloc) => toNumber(staff.sourceHours) - calcStaffAllocated(staff); - -const sourceTotalHours = computed(() => (form.value.staffAllocList || []).reduce((sum, staff) => sum + toNumber(staff.sourceHours), 0)); -const allocatedTotalHours = computed(() => (form.value.staffAllocList || []).reduce((sum, staff) => sum + calcStaffAllocated(staff), 0)); +const sourceTotalHours = computed(() => toNumber(form.value.sourceTotalHours)); +const allocatedTotalHours = computed(() => (form.value.allocItems || []).reduce((sum, item) => sum + toNumber(item.allocHours), 0)); const remainingTotalHours = computed(() => sourceTotalHours.value - allocatedTotalHours.value); -const staffCount = computed(() => (form.value.staffAllocList || []).filter((staff) => toNumber(staff.sourceHours) > 0).length); +const staffCount = computed(() => toNumber(form.value.staffCount)); const currentAllocStatus = computed(() => { if (allocatedTotalHours.value <= 0) { return '0'; } return allocatedTotalHours.value < sourceTotalHours.value ? '1' : '2'; }); -const hasAppliedSelected = computed(() => selectedRows.value.some((row) => row.appliedFlag === '1')); -const loadProjectOptions = async () => { - if (querySourceProjectOptions.value.length === 0) { - const sourceRes: any = await getErpProjectInfoList({ projectCategory: '4' }); - querySourceProjectOptions.value = (sourceRes.data || []).map(normalizeProject); +const loadTargetProjectOptions = async () => { + if (targetProjectOptions.value.length > 0) { + return; } - if (targetProjectOptions.value.length === 0) { - const [logisticsRes, spareRes]: any[] = await Promise.all([ - getErpProjectInfoList({ projectCategory: '1' }), - getErpProjectInfoList({ projectCategory: '2' }) - ]); - const merged = [...(logisticsRes.data || []), ...(spareRes.data || [])].map(normalizeProject); - const uniqueMap = new Map(); - merged.forEach((project) => uniqueMap.set(project.projectId, project)); - targetProjectOptions.value = Array.from(uniqueMap.values()); + const [logisticsRes, spareRes]: any[] = await Promise.all([ + getErpProjectInfoList({ projectCategory: '1' }), + getErpProjectInfoList({ projectCategory: '2' }) + ]); + const merged = [...(logisticsRes.data || []), ...(spareRes.data || [])].map(normalizeProject); + const uniqueMap = new Map(); + merged.forEach((project) => uniqueMap.set(project.projectId, project)); + targetProjectOptions.value = Array.from(uniqueMap.values()); +}; + +const loadAllPreProjectOptions = async () => { + const res: any = await getErpProjectInfoList({ projectCategory: '4' }); + querySourceProjectOptions.value = (res.data || []).map(normalizeProject); +}; + +const loadQuerySourceProjectOptions = async (monthCode?: string) => { + if (!monthCode) { + await loadAllPreProjectOptions(); + return; } + const res: any = await listAvailableSourceProjects({ monthCode }); + querySourceProjectOptions.value = (res.data || []).map(normalizeProject); }; const loadSourceProjectOptions = async (monthCode?: string) => { @@ -404,19 +393,18 @@ const loadSourceProjectOptions = async (monthCode?: string) => { sourceProjectOptions.value = (res.data || []).map(normalizeProject); }; -const normalizeStaffAlloc = (staff: PreAllocStaffAlloc): PreAllocStaffAlloc => ({ - staffUserId: staff.staffUserId, - staffName: staff.staffName, - sourceHours: toNumber(staff.sourceHours), - allocatedHours: toNumber(staff.allocatedHours), - remainingHours: toNumber(staff.remainingHours), - allocItems: (staff.allocItems || []).map((item) => ({ - targetProjectId: item.targetProjectId ? String(item.targetProjectId) : undefined, - targetProjectCode: item.targetProjectCode, - targetProjectName: item.targetProjectName, - allocHours: toNumber(item.allocHours) - })) -}); +const appendMissingTargetOptions = (items: PreAllocTarget[]) => { + items.forEach((item) => { + const targetProjectId = item.targetProjectId ? String(item.targetProjectId) : undefined; + if (targetProjectId && !targetProjectOptions.value.some((project) => project.projectId === targetProjectId)) { + targetProjectOptions.value.push({ + projectId: targetProjectId, + projectCode: item.targetProjectCode, + projectName: item.targetProjectName + }); + } + }); +}; const applyDetailToForm = (detail: PreAllocDetailVO) => { const sourceProjectId = detail.projectId ? String(detail.projectId) : undefined; @@ -428,6 +416,10 @@ const applyDetailToForm = (detail: PreAllocDetailVO) => { projectCategory: '4' }); } + + const allocItems = (detail.allocItems || []).map(normalizeTarget); + appendMissingTargetOptions(allocItems); + form.value.allocId = detail.allocId; form.value.allocCode = detail.allocCode; form.value.monthCode = detail.monthCode; @@ -439,33 +431,26 @@ const applyDetailToForm = (detail: PreAllocDetailVO) => { form.value.allocatedTotalHours = toNumber(detail.allocatedTotalHours); form.value.staffCount = detail.staffCount; form.value.allocStatus = detail.allocStatus; - form.value.appliedFlag = detail.appliedFlag; - form.value.summaryId = detail.summaryId; - form.value.applyTime = detail.applyTime; form.value.remark = detail.remark; - form.value.staffAllocList = (detail.staffAllocList || []).map(normalizeStaffAlloc); - form.value.staffAllocList.forEach((staff) => { - (staff.allocItems || []).forEach((item) => { - const targetProjectId = item.targetProjectId ? String(item.targetProjectId) : undefined; - if (targetProjectId && !targetProjectOptions.value.some((project) => project.projectId === targetProjectId)) { - targetProjectOptions.value.push({ - projectId: targetProjectId, - projectCode: item.targetProjectCode, - projectName: item.targetProjectName - }); - } - }); - }); + form.value.allocItems = allocItems; }; -const loadStaffAllocDetails = async () => { +const resetSourceTotals = () => { + form.value.sourceTotalHours = 0; + form.value.allocatedTotalHours = 0; + form.value.staffCount = 0; + form.value.allocStatus = '0'; + form.value.allocItems = []; +}; + +const loadAllocDetails = async () => { if (!form.value.monthCode || !form.value.projectId) { - form.value.staffAllocList = []; + resetSourceTotals(); return; } detailLoading.value = true; try { - const res: any = await getStaffAllocDetails({ + const res: any = await getAllocDetails({ monthCode: form.value.monthCode, projectId: form.value.projectId }); @@ -479,20 +464,28 @@ const handleMonthChange = async () => { form.value.projectId = undefined; form.value.projectCode = undefined; form.value.projectName = undefined; - form.value.staffAllocList = []; + resetSourceTotals(); await loadSourceProjectOptions(form.value.monthCode); }; +const handleQueryMonthChange = async () => { + queryParams.value.projectId = undefined; + await loadQuerySourceProjectOptions(queryParams.value.monthCode); +}; + const getList = async () => { loading.value = true; - const params = { - ...queryParams.value, - projectId: queryParams.value.projectId || undefined - }; - const res: any = await listTimesheetPreAlloc(params); - timesheetPreAllocList.value = res.rows; - total.value = res.total; - loading.value = false; + try { + const params = { + ...queryParams.value, + projectId: queryParams.value.projectId || undefined + }; + const res: any = await listTimesheetPreAlloc(params); + timesheetPreAllocList.value = res.rows; + total.value = res.total; + } finally { + loading.value = false; + } }; const cancel = () => { @@ -510,8 +503,9 @@ const handleQuery = () => { getList(); }; -const resetQuery = () => { +const resetQuery = async () => { queryFormRef.value?.resetFields(); + await loadQuerySourceProjectOptions(); handleQuery(); }; @@ -524,7 +518,7 @@ const handleSelectionChange = (selection: TimesheetPreAllocVO[]) => { const handleAdd = async () => { reset(); - await loadProjectOptions(); + await loadTargetProjectOptions(); await loadSourceProjectOptions(form.value.monthCode); dialog.visible = true; dialog.title = '预投工时分配'; @@ -532,23 +526,24 @@ const handleAdd = async () => { const handleUpdate = async (row?: TimesheetPreAllocVO) => { reset(); - await loadProjectOptions(); + await loadTargetProjectOptions(); const allocId = row?.allocId || ids.value[0]; const res: any = await getTimesheetPreAlloc(allocId); + await loadSourceProjectOptions(res.data?.monthCode); applyDetailToForm(res.data); dialog.visible = true; dialog.title = '预投工时分配'; }; -const addTarget = (staff: PreAllocStaffAlloc) => { - if (!staff.allocItems) { - staff.allocItems = []; +const addTarget = () => { + if (!form.value.allocItems) { + form.value.allocItems = []; } - staff.allocItems.push({ targetProjectId: undefined, allocHours: 0 }); + form.value.allocItems.push({ targetProjectId: undefined, allocHours: 0 }); }; -const removeTarget = (staff: PreAllocStaffAlloc, index: number) => { - staff.allocItems?.splice(index, 1); +const removeTarget = (index: number) => { + form.value.allocItems?.splice(index, 1); }; const validateAllocation = () => { @@ -556,28 +551,26 @@ const validateAllocation = () => { proxy?.$modal.msgError('本月该预投项目没有可分配工时'); return false; } - for (const staff of form.value.staffAllocList || []) { - const usedTargets = new Set(); - for (const item of staff.allocItems || []) { - if (!item.targetProjectId) { - proxy?.$modal.msgError(`员工[${staff.staffName}]存在未选择目标项目的分配行`); - return false; - } - const targetProjectId = String(item.targetProjectId); - if (usedTargets.has(targetProjectId)) { - proxy?.$modal.msgError(`员工[${staff.staffName}]不能重复选择同一目标项目`); - return false; - } - usedTargets.add(targetProjectId); - if (toNumber(item.allocHours) <= 0) { - proxy?.$modal.msgError(`员工[${staff.staffName}]分配工时必须大于0`); - return false; - } - } - if (calcStaffRemaining(staff) < -0.000001) { - proxy?.$modal.msgError(`员工[${staff.staffName}]分配工时不能超过原预投工时`); + const usedTargets = new Set(); + for (const item of form.value.allocItems || []) { + if (!item.targetProjectId) { + proxy?.$modal.msgError('存在未选择目标项目的分配行'); return false; } + const targetProjectId = String(item.targetProjectId); + if (usedTargets.has(targetProjectId)) { + proxy?.$modal.msgError('同一张分配单不能重复选择同一目标项目'); + return false; + } + usedTargets.add(targetProjectId); + if (toNumber(item.allocHours) <= 0) { + proxy?.$modal.msgError('分配工时必须大于0'); + return false; + } + } + if (allocatedTotalHours.value - sourceTotalHours.value > 0.000001) { + proxy?.$modal.msgError('分配工时合计不能超过来源预投工时'); + return false; } return true; }; @@ -591,14 +584,9 @@ const buildSubmitPayload = (): TimesheetPreAllocForm => ({ staffCount: staffCount.value, allocStatus: currentAllocStatus.value, remark: form.value.remark, - staffAllocList: (form.value.staffAllocList || []).map((staff) => ({ - staffUserId: staff.staffUserId, - staffName: staff.staffName, - sourceHours: toNumber(staff.sourceHours), - allocItems: (staff.allocItems || []).map((item: PreAllocTarget) => ({ - targetProjectId: item.targetProjectId, - allocHours: toNumber(item.allocHours) - })) + allocItems: (form.value.allocItems || []).map((item) => ({ + targetProjectId: item.targetProjectId, + allocHours: toNumber(item.allocHours) })) }); @@ -625,11 +613,6 @@ const submitForm = () => { }; const handleDelete = async (row?: TimesheetPreAllocVO) => { - const deleteRows = row ? [row] : selectedRows.value; - if (deleteRows.some((item) => item.appliedFlag === '1')) { - proxy?.$modal.msgError('已回写月汇总的预投工时分配单不允许删除'); - return; - } const allocIds = row?.allocId || ids.value; await proxy?.$modal.confirm(`是否确认删除预投工时分配编号为"${allocIds}"的数据项?`).finally(() => (loading.value = false)); await delTimesheetPreAlloc(allocIds); @@ -648,7 +631,7 @@ const handleExport = () => { }; onMounted(async () => { - await loadProjectOptions(); + await Promise.all([loadTargetProjectOptions(), loadQuerySourceProjectOptions()]); await getList(); }); @@ -695,25 +678,19 @@ onMounted(async () => { font-size: 16px; } -.alloc-table { - width: 100%; +.alloc-section { + min-height: 120px; } -.target-list { +.target-toolbar { display: flex; - flex-direction: column; - gap: 8px; - padding: 4px 0; -} - -.target-row { - display: grid; - grid-template-columns: minmax(260px, 1fr) 150px 32px; - gap: 8px; align-items: center; + justify-content: space-between; + margin-bottom: 8px; + font-weight: 600; } -.target-project-select { +.alloc-table { width: 100%; } @@ -726,9 +703,5 @@ onMounted(async () => { .summary-strip { grid-template-columns: repeat(2, minmax(120px, 1fr)); } - - .target-row { - grid-template-columns: 1fr; - } }