feat(oa/erp): 预投工时分配功能由员工级分配改为项目级且不回写月汇总

dev
yangk 1 month ago
parent cf36886098
commit 01edd652dd

@ -75,10 +75,10 @@ public class ErpTimesheetPreAllocController extends BaseController {
*
*/
@SaCheckPermission("oa/erp:timesheetPreAlloc:query")
@GetMapping("/getStaffAllocDetails")
public R<PreAllocDetailVo> getStaffAllocDetails(@NotBlank(message = "月份编码不能为空") String monthCode,
@NotNull(message = "来源预投项目不能为空") Long projectId) {
return R.ok(erpTimesheetPreAllocService.getStaffAllocDetails(monthCode, projectId));
@GetMapping("/getAllocDetails")
public R<PreAllocDetailVo> getAllocDetails(@NotBlank(message = "月份编码不能为空") String monthCode,
@NotNull(message = "来源预投项目不能为空") Long projectId) {
return R.ok(erpTimesheetPreAllocService.getAllocDetails(monthCode, projectId));
}
/**

@ -1,13 +1,15 @@
package org.dromara.oa.erp.domain.bo;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@ -37,11 +39,11 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
/**
* (YYYYMM)
*/
@NotBlank(message = "月份编码(YYYYMM)不能为空", groups = { AddGroup.class, EditGroup.class })
@NotBlank(message = "月份编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String monthCode;
/**
* ID(erp_timesheet_standard_month)
* ID使
*/
private Long standardMonthId;
@ -52,22 +54,22 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
private Long projectId;
/**
* ()
*
*/
private String projectCode;
/**
* ()
*
*/
private String projectName;
/**
* (,)
*
*/
private BigDecimal sourceTotalHours;
/**
* ()
*
*/
private BigDecimal allocatedTotalHours;
@ -77,22 +79,22 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
private Integer staffCount;
/**
* 0 1 2
* 012
*/
private String allocStatus;
/**
* 0 1
* 使
*/
private String appliedFlag;
/**
* ID
* ID使
*/
private Long summaryId;
/**
*
* 使
*/
private Date applyTime;
@ -102,8 +104,8 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
private String remark;
/**
*
*
*/
private List<PreAllocStaffAllocBo> staffAllocList;
private List<PreAllocTargetBo> allocItems;
}

@ -99,20 +99,16 @@ public class ErpTimesheetPreAllocVo implements Serializable {
/**
* 0 1
*/
@ExcelProperty(value = "是否已回写月汇总", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "applied_flag")
private String appliedFlag;
/**
* ID
*/
@ExcelProperty(value = "回写关联的月汇总主表ID")
private Long summaryId;
/**
*
*/
@ExcelProperty(value = "回写月汇总时间")
private Date applyTime;
/**

@ -6,11 +6,10 @@ import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
*
*
*
* @author Yangk
* @date 2026-05-21
@ -48,17 +47,11 @@ public class PreAllocDetailVo implements Serializable {
private String allocStatus;
private String appliedFlag;
private Long summaryId;
private Date applyTime;
private String remark;
/**
*
*
*/
private List<PreAllocStaffAllocVo> staffAllocList = new ArrayList<>();
private List<PreAllocTargetVo> allocItems = new ArrayList<>();
}

@ -1,13 +1,16 @@
package org.dromara.oa.erp.mapper;
import java.sql.Date;
import java.util.List;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import org.dromara.oa.erp.domain.vo.ErpProjectInfoVo;
import org.dromara.oa.erp.domain.vo.ErpTimesheetPreAllocVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.oa.erp.domain.vo.PreAllocSourceStatVo;
/**
* Mapper
@ -34,4 +37,30 @@ public interface ErpTimesheetPreAllocMapper extends BaseMapperPlus<ErpTimesheetP
*/
public List<ErpTimesheetPreAllocVo> selectCustomErpTimesheetPreAllocVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper<ErpTimesheetPreAlloc> queryWrapper);
/**
*
*
* @param deptId ID
* @param startDate
* @param endDate
* @return
*/
List<ErpProjectInfoVo> selectAvailableSourceProjects(@Param("deptId") Long deptId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
/**
*
*
* @param deptId ID
* @param startDate
* @param endDate
* @param projectId ID
* @return
*/
PreAllocSourceStatVo selectSourceStat(@Param("deptId") Long deptId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate,
@Param("projectId") Long projectId);
}

@ -58,7 +58,7 @@ public interface IErpTimesheetPreAllocService {
* @param projectId ID
* @return
*/
PreAllocDetailVo getStaffAllocDetails(String monthCode, Long projectId);
PreAllocDetailVo getAllocDetails(String monthCode, Long projectId);
/**
*

@ -1,9 +1,9 @@
package org.dromara.oa.erp.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.exception.ServiceException;
@ -14,40 +14,33 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.oa.erp.domain.ErpProjectInfo;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import org.dromara.oa.erp.domain.ErpTimesheetPreAllocDetail;
import org.dromara.oa.erp.domain.ErpTimesheetSummary;
import org.dromara.oa.erp.domain.ErpTimesheetSummaryDetail;
import org.dromara.oa.erp.domain.bo.ErpTimesheetPreAllocBo;
import org.dromara.oa.erp.domain.bo.PreAllocStaffAllocBo;
import org.dromara.oa.erp.domain.bo.PreAllocTargetBo;
import org.dromara.oa.erp.domain.vo.ErpProjectInfoVo;
import org.dromara.oa.erp.domain.vo.ErpTimesheetPreAllocVo;
import org.dromara.oa.erp.domain.vo.PreAllocDetailVo;
import org.dromara.oa.erp.domain.vo.PreAllocStaffAllocVo;
import org.dromara.oa.erp.domain.vo.PreAllocSourceStatVo;
import org.dromara.oa.erp.domain.vo.PreAllocTargetVo;
import org.dromara.oa.erp.mapper.ErpProjectInfoMapper;
import org.dromara.oa.erp.mapper.ErpTimesheetPreAllocDetailMapper;
import org.dromara.oa.erp.mapper.ErpTimesheetPreAllocMapper;
import org.dromara.oa.erp.mapper.ErpTimesheetSummaryDetailMapper;
import org.dromara.oa.erp.mapper.ErpTimesheetSummaryMapper;
import org.dromara.oa.erp.service.IErpTimesheetPreAllocService;
import org.dromara.system.api.RemoteCodeRuleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.Date;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Service
@ -60,20 +53,15 @@ import java.util.stream.Collectors;
public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocService {
private static final String DEL_FLAG_NORMAL = "0";
private static final String PROJECT_WORK = "1";
private static final String MANUAL_ROW = "0";
private static final String PRE_PROJECT_CATEGORY = "4";
private static final Set<String> TARGET_PROJECT_CATEGORIES = Set.of("1", "2");
private static final String STATUS_NOT_ALLOCATED = "0";
private static final String STATUS_PART_ALLOCATED = "1";
private static final String STATUS_ALLOCATED = "2";
private static final String APPLIED = "1";
private static final Pattern MONTH_CODE_PATTERN = Pattern.compile("^\\d{6}$");
private final ErpTimesheetPreAllocMapper baseMapper;
private final ErpTimesheetPreAllocDetailMapper preAllocDetailMapper;
private final ErpTimesheetSummaryMapper summaryMapper;
private final ErpTimesheetSummaryDetailMapper summaryDetailMapper;
private final ErpProjectInfoMapper projectInfoMapper;
@DubboReference
@ -91,26 +79,22 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
}
/**
*
*
*
* @param allocId
* @return
* @return
*/
@Override
public PreAllocDetailVo queryDetailById(Long allocId) {
ErpTimesheetPreAlloc alloc = baseMapper.selectById(allocId);
if (alloc == null) {
if (alloc == null || !DEL_FLAG_NORMAL.equals(alloc.getDelFlag())) {
throw new ServiceException("预投工时分配单不存在");
}
Long deptId = LoginHelper.getDeptId();
Long deptId = requireDeptId();
if (alloc.getCreateDept() != null && !Objects.equals(alloc.getCreateDept(), deptId)) {
throw new ServiceException("只能查看当前登录部门的预投工时分配单");
}
ErpTimesheetSummary summary = summaryMapper.selectById(alloc.getSummaryId());
if (summary != null && !Objects.equals(summary.getDeptId(), deptId)) {
throw new ServiceException("只能查看当前登录部门的预投工时分配单");
}
return getStaffAllocDetails(alloc.getMonthCode(), alloc.getProjectId());
return getAllocDetails(alloc.getMonthCode(), alloc.getProjectId());
}
/**
@ -147,71 +131,54 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
.eq(deptId != null, ErpTimesheetPreAlloc::getCreateDept, deptId)
.eq(StringUtils.isNotBlank(bo.getAllocCode()), ErpTimesheetPreAlloc::getAllocCode, bo.getAllocCode())
.eq(StringUtils.isNotBlank(bo.getMonthCode()), ErpTimesheetPreAlloc::getMonthCode, bo.getMonthCode())
.eq(bo.getStandardMonthId() != null, ErpTimesheetPreAlloc::getStandardMonthId, bo.getStandardMonthId())
.eq(bo.getProjectId() != null, ErpTimesheetPreAlloc::getProjectId, bo.getProjectId())
.eq(StringUtils.isNotBlank(bo.getProjectCode()), ErpTimesheetPreAlloc::getProjectCode, bo.getProjectCode())
.like(StringUtils.isNotBlank(bo.getProjectName()), ErpTimesheetPreAlloc::getProjectName, bo.getProjectName())
.eq(StringUtils.isNotBlank(bo.getAllocStatus()), ErpTimesheetPreAlloc::getAllocStatus, bo.getAllocStatus())
.eq(StringUtils.isNotBlank(bo.getAppliedFlag()), ErpTimesheetPreAlloc::getAppliedFlag, bo.getAppliedFlag())
.eq(bo.getSummaryId() != null, ErpTimesheetPreAlloc::getSummaryId, bo.getSummaryId())
.eq(bo.getApplyTime() != null, ErpTimesheetPreAlloc::getApplyTime, bo.getApplyTime())
.orderByDesc(ErpTimesheetPreAlloc::getApplyTime)
.orderByDesc(ErpTimesheetPreAlloc::getAllocId);
}
/**
*
*
*
* @param monthCode (YYYYMM)
* @param projectId ID
* @return
* @return
*/
@Override
public PreAllocDetailVo getStaffAllocDetails(String monthCode, Long projectId) {
public PreAllocDetailVo getAllocDetails(String monthCode, Long projectId) {
validateMonthCode(monthCode);
if (projectId == null) {
throw new ServiceException("来源预投项目不能为空");
}
Long deptId = LoginHelper.getDeptId();
ErpTimesheetSummary summary = getSingleSummary(monthCode, deptId);
Long deptId = requireDeptId();
ErpProjectInfo sourceProject = getProject(projectId, "来源预投项目不存在");
validateProjectCategory(sourceProject, PRE_PROJECT_CATEGORY, "来源项目必须是预投项目");
ErpTimesheetPreAlloc alloc = queryActiveAlloc(summary.getSummaryId(), projectId);
List<StaffSource> staffSources = loadStaffSources(summary.getSummaryId(), sourceProject);
List<PreAllocStaffAllocVo> staffVos = buildStaffVos(staffSources);
PreAllocSourceStatVo sourceStat = loadSourceStat(monthCode, deptId, projectId);
ErpTimesheetPreAlloc alloc = queryActiveAlloc(monthCode, projectId, deptId);
List<PreAllocTargetVo> allocItems = buildTargetVos(alloc == null ? null : alloc.getAllocId());
BigDecimal allocatedTotal = allocItems.stream()
.map(item -> nvl(item.getAllocHours()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal sourceTotal = nvl(sourceStat.getSourceTotalHours());
PreAllocDetailVo detailVo = new PreAllocDetailVo();
if (alloc != null) {
detailVo.setAllocId(alloc.getAllocId());
detailVo.setAllocCode(alloc.getAllocCode());
detailVo.setAppliedFlag(alloc.getAppliedFlag());
detailVo.setApplyTime(alloc.getApplyTime());
detailVo.setRemark(alloc.getRemark());
} else {
detailVo.setAppliedFlag("0");
}
detailVo.setMonthCode(monthCode);
detailVo.setStandardMonthId(summary.getStandardMonthId());
detailVo.setSummaryId(summary.getSummaryId());
detailVo.setProjectId(sourceProject.getProjectId());
detailVo.setProjectCode(sourceProject.getProjectCode());
detailVo.setProjectName(sourceProject.getProjectName());
detailVo.setStaffAllocList(staffVos);
BigDecimal sourceTotal = staffVos.stream()
.map(PreAllocStaffAllocVo::getSourceHours)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal allocatedTotal = staffVos.stream()
.map(PreAllocStaffAllocVo::getAllocatedHours)
.reduce(BigDecimal.ZERO, BigDecimal::add);
detailVo.setSourceTotalHours(sourceTotal);
detailVo.setAllocatedTotalHours(allocatedTotal);
detailVo.setRemainingTotalHours(sourceTotal.subtract(allocatedTotal));
detailVo.setStaffCount((int) staffVos.stream()
.filter(item -> item.getSourceHours().compareTo(BigDecimal.ZERO) > 0)
.count());
detailVo.setStaffCount(sourceStat.getStaffCount());
detailVo.setAllocStatus(resolveAllocStatus(sourceTotal, allocatedTotal));
detailVo.setAllocItems(allocItems);
return detailVo;
}
@ -224,34 +191,9 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
@Override
public List<ErpProjectInfoVo> listAvailableSourceProjects(String monthCode) {
validateMonthCode(monthCode);
Long deptId = LoginHelper.getDeptId();
ErpTimesheetSummary summary = getSingleSummaryOrNull(monthCode, deptId);
if (summary == null) {
return Collections.emptyList();
}
List<ErpTimesheetSummaryDetail> detailList = summaryDetailMapper.selectList(Wrappers.<ErpTimesheetSummaryDetail>lambdaQuery()
.select(ErpTimesheetSummaryDetail::getOriginalProjectId)
.eq(ErpTimesheetSummaryDetail::getSummaryId, summary.getSummaryId())
.eq(ErpTimesheetSummaryDetail::getIsProject, PROJECT_WORK)
.eq(ErpTimesheetSummaryDetail::getDelFlag, DEL_FLAG_NORMAL)
.isNotNull(ErpTimesheetSummaryDetail::getOriginalProjectId)
.gt(ErpTimesheetSummaryDetail::getOriginalHours, BigDecimal.ZERO));
List<Long> projectIds = detailList.stream()
.map(ErpTimesheetSummaryDetail::getOriginalProjectId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (projectIds.isEmpty()) {
return Collections.emptyList();
}
return projectInfoMapper.selectVoList(Wrappers.<ErpProjectInfo>lambdaQuery()
.in(ErpProjectInfo::getProjectId, projectIds)
.eq(ErpProjectInfo::getProjectCategory, PRE_PROJECT_CATEGORY)
.eq(ErpProjectInfo::getDelFlag, DEL_FLAG_NORMAL)
.orderByAsc(ErpProjectInfo::getProjectCode)
.orderByAsc(ErpProjectInfo::getProjectName));
Long deptId = requireDeptId();
MonthRange range = monthRange(monthCode);
return baseMapper.selectAvailableSourceProjects(deptId, range.startDate(), range.endDate());
}
/**
@ -277,7 +219,7 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
}
/**
*
*
*
* @param bo
* @return
@ -290,26 +232,18 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
throw new ServiceException("来源预投项目不能为空");
}
Long deptId = LoginHelper.getDeptId();
ErpTimesheetSummary summary = getSingleSummary(bo.getMonthCode(), deptId);
ErpTimesheetSummary lockedSummary = lockSummary(summary.getSummaryId());
if (!Objects.equals(lockedSummary.getDeptId(), deptId)) {
throw new ServiceException("只能维护当前登录部门的预投工时分配");
}
Long deptId = requireDeptId();
ErpProjectInfo sourceProject = getProject(bo.getProjectId(), "来源预投项目不存在");
validateProjectCategory(sourceProject, PRE_PROJECT_CATEGORY, "来源项目必须是预投项目");
List<StaffSource> staffSources = loadStaffSources(lockedSummary.getSummaryId(), sourceProject);
BigDecimal sourceTotal = staffSources.stream()
.map(StaffSource::getSourceHours)
.reduce(BigDecimal.ZERO, BigDecimal::add);
PreAllocSourceStatVo sourceStat = loadSourceStat(bo.getMonthCode(), deptId, sourceProject.getProjectId());
BigDecimal sourceTotal = nvl(sourceStat.getSourceTotalHours());
if (sourceTotal.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("本月该预投项目没有可分配工时");
}
AllocationResult allocationResult = buildAllocationResult(
bo.getStaffAllocList(), staffSources, lockedSummary.getSummaryId(), sourceProject);
ErpTimesheetPreAlloc alloc = resolveAlloc(bo.getAllocId(), lockedSummary.getSummaryId(), sourceProject.getProjectId(), deptId);
AllocationResult allocationResult = buildAllocationResult(bo.getAllocItems(), sourceTotal);
ErpTimesheetPreAlloc alloc = resolveAlloc(bo.getAllocId(), bo.getMonthCode(), sourceProject.getProjectId(), deptId);
boolean isNew = alloc == null;
if (isNew) {
alloc = new ErpTimesheetPreAlloc();
@ -319,16 +253,14 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
alloc.setAllocCode(generateAllocCode());
}
fillAlloc(alloc, bo, lockedSummary, sourceProject, allocationResult);
fillAlloc(alloc, bo, sourceProject, sourceStat, allocationResult, deptId);
if (isNew) {
baseMapper.insert(alloc);
} else {
baseMapper.updateById(alloc);
}
rewriteSummaryDetails(lockedSummary.getSummaryId(), sourceProject.getProjectId(), allocationResult.getSummaryDetails());
rewritePreAllocDetails(alloc, sourceProject, allocationResult.getTargetAggregations());
recalculateSummary(lockedSummary.getSummaryId());
rewritePreAllocDetails(alloc, sourceProject, allocationResult.targetAllocations());
bo.setAllocId(alloc.getAllocId());
return true;
}
@ -352,11 +284,8 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
if (allocList.size() != ids.size()) {
throw new ServiceException("部分预投工时分配单不存在或已删除");
}
Long deptId = LoginHelper.getDeptId();
Long deptId = requireDeptId();
for (ErpTimesheetPreAlloc alloc : allocList) {
if (APPLIED.equals(alloc.getAppliedFlag())) {
throw new ServiceException("已回写月汇总的预投工时分配单不允许删除");
}
if (alloc.getCreateDept() != null && !Objects.equals(alloc.getCreateDept(), deptId)) {
throw new ServiceException("只能删除当前登录部门的预投工时分配单");
}
@ -370,308 +299,72 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
if (StringUtils.isBlank(monthCode) || !MONTH_CODE_PATTERN.matcher(monthCode).matches()) {
throw new ServiceException("月份编码必须是YYYYMM格式");
}
int month = Integer.parseInt(monthCode.substring(4, 6));
if (month < 1 || month > 12) {
throw new ServiceException("月份编码必须是有效月份");
}
}
private ErpTimesheetSummary getSingleSummary(String monthCode, Long deptId) {
private Long requireDeptId() {
Long deptId = LoginHelper.getDeptId();
if (deptId == null) {
throw new ServiceException("当前登录用户未绑定部门,无法进行预投工时分配");
}
List<ErpTimesheetSummary> summaries = summaryMapper.selectList(Wrappers.<ErpTimesheetSummary>lambdaQuery()
.eq(ErpTimesheetSummary::getMonthCode, monthCode)
.eq(ErpTimesheetSummary::getDeptId, deptId)
.eq(ErpTimesheetSummary::getDelFlag, DEL_FLAG_NORMAL));
if (summaries.isEmpty()) {
throw new ServiceException("当前部门该月份的月汇总工时不存在");
}
if (summaries.size() > 1) {
throw new ServiceException("当前部门该月份存在多个月汇总工时,请先修正月汇总数据");
}
return summaries.get(0);
return deptId;
}
private ErpTimesheetSummary getSingleSummaryOrNull(String monthCode, Long deptId) {
if (deptId == null) {
throw new ServiceException("当前登录用户未绑定部门,无法进行预投工时分配");
}
List<ErpTimesheetSummary> summaries = summaryMapper.selectList(Wrappers.<ErpTimesheetSummary>lambdaQuery()
.eq(ErpTimesheetSummary::getMonthCode, monthCode)
.eq(ErpTimesheetSummary::getDeptId, deptId)
.eq(ErpTimesheetSummary::getDelFlag, DEL_FLAG_NORMAL));
if (summaries.isEmpty()) {
return null;
}
if (summaries.size() > 1) {
throw new ServiceException("当前部门该月份存在多个月汇总工时,请先修正月汇总数据");
}
return summaries.get(0);
private MonthRange monthRange(String monthCode) {
YearMonth yearMonth = YearMonth.of(
Integer.parseInt(monthCode.substring(0, 4)),
Integer.parseInt(monthCode.substring(4, 6)));
return new MonthRange(Date.valueOf(yearMonth.atDay(1)), Date.valueOf(yearMonth.atEndOfMonth()));
}
private ErpTimesheetSummary lockSummary(Long summaryId) {
ErpTimesheetSummary summary = summaryMapper.selectOne(Wrappers.<ErpTimesheetSummary>lambdaQuery()
.eq(ErpTimesheetSummary::getSummaryId, summaryId)
.eq(ErpTimesheetSummary::getDelFlag, DEL_FLAG_NORMAL)
.last("FOR UPDATE"));
if (summary == null) {
throw new ServiceException("月汇总工时不存在或已删除");
private PreAllocSourceStatVo loadSourceStat(String monthCode, Long deptId, Long projectId) {
MonthRange range = monthRange(monthCode);
PreAllocSourceStatVo sourceStat = baseMapper.selectSourceStat(deptId, range.startDate(), range.endDate(), projectId);
if (sourceStat == null) {
sourceStat = new PreAllocSourceStatVo();
}
return summary;
sourceStat.setSourceTotalHours(nvl(sourceStat.getSourceTotalHours()));
sourceStat.setStaffCount(sourceStat.getStaffCount() == null ? 0 : sourceStat.getStaffCount());
return sourceStat;
}
private ErpProjectInfo getProject(Long projectId, String message) {
private ErpProjectInfo getProject(Long projectId, String errorMessage) {
ErpProjectInfo project = projectInfoMapper.selectById(projectId);
if (project == null || !DEL_FLAG_NORMAL.equals(project.getDelFlag())) {
throw new ServiceException(message);
throw new ServiceException(errorMessage);
}
return project;
}
private void validateProjectCategory(ErpProjectInfo project, String expectedCategory, String message) {
private void validateProjectCategory(ErpProjectInfo project, String expectedCategory, String errorMessage) {
if (!expectedCategory.equals(project.getProjectCategory())) {
throw new ServiceException(message);
throw new ServiceException(errorMessage);
}
}
private void validateTargetProject(ErpProjectInfo project) {
if (!TARGET_PROJECT_CATEGORIES.contains(project.getProjectCategory())) {
throw new ServiceException("目标项目只能选择物流或备件项目");
throw new ServiceException("目标项目必须是物流或备件项目");
}
}
private ErpTimesheetPreAlloc queryActiveAlloc(Long summaryId, Long projectId) {
private ErpTimesheetPreAlloc queryActiveAlloc(String monthCode, Long projectId, Long deptId) {
List<ErpTimesheetPreAlloc> allocList = baseMapper.selectList(Wrappers.<ErpTimesheetPreAlloc>lambdaQuery()
.eq(ErpTimesheetPreAlloc::getSummaryId, summaryId)
.eq(ErpTimesheetPreAlloc::getMonthCode, monthCode)
.eq(ErpTimesheetPreAlloc::getProjectId, projectId)
.eq(ErpTimesheetPreAlloc::getCreateDept, deptId)
.eq(ErpTimesheetPreAlloc::getDelFlag, DEL_FLAG_NORMAL));
if (allocList.size() > 1) {
throw new ServiceException("当前部门该月份该预投项目存在多张分配单,请先修正分配单数据");
throw new ServiceException("当前部门该月份该预投项目存在多张有效分配单,请先修正数据");
}
return allocList.isEmpty() ? null : allocList.get(0);
}
private List<StaffSource> loadStaffSources(Long summaryId, ErpProjectInfo sourceProject) {
List<ErpTimesheetSummaryDetail> detailList = summaryDetailMapper.selectList(Wrappers.<ErpTimesheetSummaryDetail>lambdaQuery()
.eq(ErpTimesheetSummaryDetail::getSummaryId, summaryId)
.eq(ErpTimesheetSummaryDetail::getOriginalProjectId, sourceProject.getProjectId())
.eq(ErpTimesheetSummaryDetail::getIsProject, PROJECT_WORK)
.eq(ErpTimesheetSummaryDetail::getDelFlag, DEL_FLAG_NORMAL)
.orderByAsc(ErpTimesheetSummaryDetail::getSortOrder)
.orderByAsc(ErpTimesheetSummaryDetail::getSummaryDetailId));
Map<Long, List<ErpTimesheetSummaryDetail>> staffMap = detailList.stream()
.filter(detail -> detail.getStaffUserId() != null)
.collect(Collectors.groupingBy(
ErpTimesheetSummaryDetail::getStaffUserId,
LinkedHashMap::new,
Collectors.toList()));
List<StaffSource> result = new ArrayList<>();
for (Map.Entry<Long, List<ErpTimesheetSummaryDetail>> entry : staffMap.entrySet()) {
List<ErpTimesheetSummaryDetail> rows = entry.getValue();
List<BigDecimal> positiveOriginalHours = rows.stream()
.map(item -> nvl(item.getOriginalHours()))
.filter(value -> value.compareTo(BigDecimal.ZERO) > 0)
.collect(Collectors.toList());
long distinctPositiveCount = positiveOriginalHours.stream()
.map(this::decimalKey)
.distinct()
.count();
if (distinctPositiveCount > 1) {
String staffName = rows.get(0).getStaffName();
throw new ServiceException("员工[" + staffName + "]同一预投项目存在多个原始工时,请先修正月汇总数据");
}
BigDecimal sourceHours = positiveOriginalHours.stream()
.max(Comparator.naturalOrder())
.orElse(BigDecimal.ZERO);
StaffSource staffSource = new StaffSource();
staffSource.setStaffUserId(entry.getKey());
staffSource.setStaffName(rows.get(0).getStaffName());
staffSource.setSourceHours(sourceHours);
staffSource.setExistingRows(rows);
result.add(staffSource);
}
return result;
}
private List<PreAllocStaffAllocVo> buildStaffVos(List<StaffSource> staffSources) {
List<PreAllocStaffAllocVo> staffVos = new ArrayList<>();
for (StaffSource source : staffSources) {
PreAllocStaffAllocVo staffVo = new PreAllocStaffAllocVo();
staffVo.setStaffUserId(source.getStaffUserId());
staffVo.setStaffName(source.getStaffName());
staffVo.setSourceHours(source.getSourceHours());
Map<Long, PreAllocTargetVo> targetMap = new LinkedHashMap<>();
for (ErpTimesheetSummaryDetail row : source.getExistingRows()) {
BigDecimal adjustedHours = nvl(row.getAdjustedHours());
if (adjustedHours.compareTo(BigDecimal.ZERO) <= 0) {
continue;
}
if (Objects.equals(row.getAdjustedProjectId(), row.getOriginalProjectId())) {
continue;
}
if (row.getAdjustedProjectId() == null) {
continue;
}
PreAllocTargetVo targetVo = targetMap.computeIfAbsent(row.getAdjustedProjectId(), targetProjectId -> {
PreAllocTargetVo vo = new PreAllocTargetVo();
vo.setTargetProjectId(targetProjectId);
vo.setTargetProjectCode(row.getAdjustedProjectCode());
vo.setTargetProjectName(row.getAdjustedProjectName());
vo.setAllocHours(BigDecimal.ZERO);
return vo;
});
targetVo.setAllocHours(targetVo.getAllocHours().add(adjustedHours));
}
staffVo.setAllocItems(new ArrayList<>(targetMap.values()));
BigDecimal allocatedHours = staffVo.getAllocItems().stream()
.map(PreAllocTargetVo::getAllocHours)
.reduce(BigDecimal.ZERO, BigDecimal::add);
staffVo.setAllocatedHours(allocatedHours);
staffVo.setRemainingHours(staffVo.getSourceHours().subtract(allocatedHours));
staffVos.add(staffVo);
}
return staffVos;
}
private AllocationResult buildAllocationResult(List<PreAllocStaffAllocBo> inputList,
List<StaffSource> staffSources,
Long summaryId,
ErpProjectInfo sourceProject) {
Map<Long, StaffSource> sourceMap = staffSources.stream()
.filter(item -> item.getSourceHours().compareTo(BigDecimal.ZERO) > 0)
.collect(Collectors.toMap(StaffSource::getStaffUserId, item -> item, (a, b) -> a, LinkedHashMap::new));
Map<Long, List<PreAllocTargetBo>> inputMap = buildInputMap(inputList, sourceMap);
Map<Long, ErpProjectInfo> projectCache = new HashMap<>();
List<ErpTimesheetSummaryDetail> newSummaryDetails = new ArrayList<>();
Map<Long, TargetAggregation> targetAggregations = new LinkedHashMap<>();
int sortOrder = 1;
BigDecimal allocatedTotal = BigDecimal.ZERO;
for (StaffSource source : sourceMap.values()) {
List<PreAllocTargetBo> targetInputs = inputMap.getOrDefault(source.getStaffUserId(), Collections.emptyList());
BigDecimal staffAllocated = BigDecimal.ZERO;
List<TargetAllocation> targetAllocations = new ArrayList<>();
Set<Long> usedTargetIds = new java.util.HashSet<>();
for (PreAllocTargetBo targetInput : targetInputs) {
if (targetInput == null) {
throw new ServiceException("员工分配明细不能为空");
}
Long targetProjectId = targetInput.getTargetProjectId();
if (targetProjectId == null) {
throw new ServiceException("目标项目不能为空");
}
if (!usedTargetIds.add(targetProjectId)) {
throw new ServiceException("员工[" + source.getStaffName() + "]不能重复选择同一目标项目");
}
BigDecimal allocHours = nvl(targetInput.getAllocHours());
if (allocHours.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("员工[" + source.getStaffName() + "]分配工时必须大于0");
}
ErpProjectInfo targetProject = projectCache.computeIfAbsent(targetProjectId,
id -> getProject(id, "目标项目不存在"));
validateTargetProject(targetProject);
staffAllocated = staffAllocated.add(allocHours);
targetAllocations.add(new TargetAllocation(targetProject, allocHours));
}
if (staffAllocated.compareTo(source.getSourceHours()) > 0) {
throw new ServiceException("员工[" + source.getStaffName() + "]分配工时不能超过原预投工时");
}
allocatedTotal = allocatedTotal.add(staffAllocated);
boolean originalHoursWritten = false;
for (TargetAllocation allocation : targetAllocations) {
ErpTimesheetSummaryDetail detail = buildSummaryDetail(
summaryId, sortOrder++, sourceProject, source, allocation.getTargetProject(), allocation.getAllocHours(),
originalHoursWritten ? BigDecimal.ZERO : source.getSourceHours());
originalHoursWritten = true;
newSummaryDetails.add(detail);
targetAggregations.computeIfAbsent(allocation.getTargetProject().getProjectId(), id -> {
TargetAggregation aggregation = new TargetAggregation();
aggregation.setTargetProject(allocation.getTargetProject());
aggregation.setAllocHours(BigDecimal.ZERO);
return aggregation;
}).addAllocHours(allocation.getAllocHours());
}
BigDecimal remainingHours = source.getSourceHours().subtract(staffAllocated);
if (remainingHours.compareTo(BigDecimal.ZERO) > 0) {
ErpTimesheetSummaryDetail detail = buildSummaryDetail(
summaryId, sortOrder++, sourceProject, source, sourceProject, remainingHours,
originalHoursWritten ? BigDecimal.ZERO : source.getSourceHours());
newSummaryDetails.add(detail);
}
}
AllocationResult result = new AllocationResult();
result.setSourceTotal(sourceMap.values().stream()
.map(StaffSource::getSourceHours)
.reduce(BigDecimal.ZERO, BigDecimal::add));
result.setAllocatedTotal(allocatedTotal);
result.setStaffCount(sourceMap.size());
result.setSummaryDetails(newSummaryDetails);
result.setTargetAggregations(new ArrayList<>(targetAggregations.values()));
return result;
}
private Map<Long, List<PreAllocTargetBo>> buildInputMap(List<PreAllocStaffAllocBo> inputList,
Map<Long, StaffSource> sourceMap) {
Map<Long, List<PreAllocTargetBo>> inputMap = new HashMap<>();
if (inputList == null) {
return inputMap;
}
Set<Long> seenStaffIds = new java.util.HashSet<>();
for (PreAllocStaffAllocBo staffInput : inputList) {
if (staffInput == null || staffInput.getStaffUserId() == null) {
throw new ServiceException("员工分配信息不能为空");
}
if (!seenStaffIds.add(staffInput.getStaffUserId())) {
throw new ServiceException("员工分配信息存在重复员工");
}
StaffSource source = sourceMap.get(staffInput.getStaffUserId());
if (source == null) {
throw new ServiceException("员工[" + staffInput.getStaffName() + "]不属于当前预投项目可分配范围");
}
if (staffInput.getSourceHours() != null
&& staffInput.getSourceHours().compareTo(source.getSourceHours()) != 0) {
throw new ServiceException("员工[" + source.getStaffName() + "]预投工时已变化,请重新查询后分配");
}
inputMap.put(staffInput.getStaffUserId(),
staffInput.getAllocItems() == null ? Collections.emptyList() : staffInput.getAllocItems());
}
return inputMap;
}
private ErpTimesheetSummaryDetail buildSummaryDetail(Long summaryId,
int sortOrder,
ErpProjectInfo sourceProject,
StaffSource source,
ErpProjectInfo adjustedProject,
BigDecimal adjustedHours,
BigDecimal originalHours) {
ErpTimesheetSummaryDetail detail = new ErpTimesheetSummaryDetail();
detail.setSummaryId(summaryId);
detail.setSortOrder(sortOrder);
detail.setStaffUserId(source.getStaffUserId());
detail.setStaffName(source.getStaffName());
detail.setIsProject(PROJECT_WORK);
detail.setOriginalProjectId(sourceProject.getProjectId());
detail.setOriginalProjectCode(sourceProject.getProjectCode());
detail.setOriginalProjectName(sourceProject.getProjectName());
detail.setOriginalHours(originalHours);
detail.setAdjustedProjectId(adjustedProject.getProjectId());
detail.setAdjustedProjectCode(adjustedProject.getProjectCode());
detail.setAdjustedProjectName(adjustedProject.getProjectName());
detail.setAdjustedHours(adjustedHours);
detail.setIsGenerated(MANUAL_ROW);
return detail;
}
private ErpTimesheetPreAlloc resolveAlloc(Long allocId, Long summaryId, Long projectId, Long deptId) {
ErpTimesheetPreAlloc keyAlloc = queryActiveAlloc(summaryId, projectId);
private ErpTimesheetPreAlloc resolveAlloc(Long allocId, String monthCode, Long projectId, Long deptId) {
ErpTimesheetPreAlloc keyAlloc = queryActiveAlloc(monthCode, projectId, deptId);
if (allocId == null) {
return keyAlloc;
}
@ -682,7 +375,7 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
if (keyAlloc != null && !Objects.equals(keyAlloc.getAllocId(), allocId)) {
throw new ServiceException("当前部门该月份该预投项目已存在分配单");
}
if (!Objects.equals(idAlloc.getSummaryId(), summaryId) || !Objects.equals(idAlloc.getProjectId(), projectId)) {
if (!Objects.equals(idAlloc.getMonthCode(), monthCode) || !Objects.equals(idAlloc.getProjectId(), projectId)) {
throw new ServiceException("不允许修改分配单的月份或来源预投项目");
}
if (idAlloc.getCreateDept() != null && !Objects.equals(idAlloc.getCreateDept(), deptId)) {
@ -691,88 +384,106 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
return idAlloc;
}
private AllocationResult buildAllocationResult(List<PreAllocTargetBo> inputList, BigDecimal sourceTotal) {
if (inputList == null || inputList.isEmpty()) {
return new AllocationResult(BigDecimal.ZERO, Collections.emptyList());
}
Map<Long, TargetAllocation> targetMap = new LinkedHashMap<>();
BigDecimal allocatedTotal = BigDecimal.ZERO;
for (PreAllocTargetBo targetInput : inputList) {
if (targetInput == null) {
throw new ServiceException("分配明细不能为空");
}
Long targetProjectId = targetInput.getTargetProjectId();
if (targetProjectId == null) {
throw new ServiceException("目标项目不能为空");
}
if (targetMap.containsKey(targetProjectId)) {
throw new ServiceException("同一张分配单不能重复选择同一目标项目");
}
BigDecimal allocHours = nvl(targetInput.getAllocHours());
if (allocHours.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("分配工时必须大于0");
}
ErpProjectInfo targetProject = getProject(targetProjectId, "目标项目不存在");
validateTargetProject(targetProject);
targetMap.put(targetProjectId, new TargetAllocation(targetProject, allocHours));
allocatedTotal = allocatedTotal.add(allocHours);
}
if (allocatedTotal.compareTo(sourceTotal) > 0) {
throw new ServiceException("分配工时合计不能超过来源预投工时");
}
return new AllocationResult(allocatedTotal, new ArrayList<>(targetMap.values()));
}
private void fillAlloc(ErpTimesheetPreAlloc alloc,
ErpTimesheetPreAllocBo bo,
ErpTimesheetSummary summary,
ErpProjectInfo sourceProject,
AllocationResult allocationResult) {
alloc.setMonthCode(summary.getMonthCode());
alloc.setStandardMonthId(summary.getStandardMonthId());
PreAllocSourceStatVo sourceStat,
AllocationResult allocationResult,
Long deptId) {
BigDecimal sourceTotal = nvl(sourceStat.getSourceTotalHours());
alloc.setMonthCode(bo.getMonthCode());
alloc.setStandardMonthId(null);
alloc.setProjectId(sourceProject.getProjectId());
alloc.setProjectCode(sourceProject.getProjectCode());
alloc.setProjectName(sourceProject.getProjectName());
alloc.setSourceTotalHours(allocationResult.getSourceTotal());
alloc.setAllocatedTotalHours(allocationResult.getAllocatedTotal());
alloc.setStaffCount(allocationResult.getStaffCount());
alloc.setAllocStatus(resolveAllocStatus(allocationResult.getSourceTotal(), allocationResult.getAllocatedTotal()));
alloc.setAppliedFlag(APPLIED);
alloc.setSummaryId(summary.getSummaryId());
alloc.setApplyTime(new Date());
alloc.setSourceTotalHours(sourceTotal);
alloc.setAllocatedTotalHours(allocationResult.allocatedTotal());
alloc.setStaffCount(sourceStat.getStaffCount());
alloc.setAllocStatus(resolveAllocStatus(sourceTotal, allocationResult.allocatedTotal()));
alloc.setRemark(bo.getRemark());
alloc.setCreateDept(summary.getDeptId());
}
private void rewriteSummaryDetails(Long summaryId, Long sourceProjectId, List<ErpTimesheetSummaryDetail> details) {
summaryDetailMapper.delete(Wrappers.<ErpTimesheetSummaryDetail>lambdaQuery()
.eq(ErpTimesheetSummaryDetail::getSummaryId, summaryId)
.eq(ErpTimesheetSummaryDetail::getOriginalProjectId, sourceProjectId)
.eq(ErpTimesheetSummaryDetail::getIsProject, PROJECT_WORK));
if (!details.isEmpty()) {
summaryDetailMapper.insertBatch(details);
}
alloc.setCreateDept(deptId);
}
private void rewritePreAllocDetails(ErpTimesheetPreAlloc alloc,
ErpProjectInfo sourceProject,
List<TargetAggregation> aggregations) {
List<TargetAllocation> targetAllocations) {
preAllocDetailMapper.delete(Wrappers.<ErpTimesheetPreAllocDetail>lambdaQuery()
.eq(ErpTimesheetPreAllocDetail::getAllocId, alloc.getAllocId()));
if (aggregations.isEmpty()) {
if (targetAllocations.isEmpty()) {
return;
}
List<ErpTimesheetPreAllocDetail> details = new ArrayList<>();
int sortOrder = 1;
for (TargetAggregation aggregation : aggregations) {
for (TargetAllocation allocation : targetAllocations) {
ErpTimesheetPreAllocDetail detail = new ErpTimesheetPreAllocDetail();
detail.setAllocId(alloc.getAllocId());
detail.setSortOrder(sortOrder++);
detail.setOriginalProjectId(sourceProject.getProjectId());
detail.setOriginalProjectCode(sourceProject.getProjectCode());
detail.setOriginalProjectName(sourceProject.getProjectName());
detail.setTargetProjectId(aggregation.getTargetProject().getProjectId());
detail.setTargetProjectCode(aggregation.getTargetProject().getProjectCode());
detail.setTargetProjectName(aggregation.getTargetProject().getProjectName());
detail.setAllocHours(aggregation.getAllocHours());
detail.setTargetProjectId(allocation.targetProject().getProjectId());
detail.setTargetProjectCode(allocation.targetProject().getProjectCode());
detail.setTargetProjectName(allocation.targetProject().getProjectName());
detail.setAllocHours(allocation.allocHours());
details.add(detail);
}
preAllocDetailMapper.insertBatch(details);
}
private void recalculateSummary(Long summaryId) {
List<ErpTimesheetSummaryDetail> details = summaryDetailMapper.selectList(Wrappers.<ErpTimesheetSummaryDetail>lambdaQuery()
.eq(ErpTimesheetSummaryDetail::getSummaryId, summaryId)
.eq(ErpTimesheetSummaryDetail::getDelFlag, DEL_FLAG_NORMAL));
BigDecimal totalProjectHours = details.stream()
.filter(item -> PROJECT_WORK.equals(item.getIsProject()))
.map(item -> nvl(item.getAdjustedHours()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalDeptHours = details.stream()
.filter(item -> !PROJECT_WORK.equals(item.getIsProject()))
.map(item -> nvl(item.getAdjustedHours()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
long staffCount = details.stream()
.map(ErpTimesheetSummaryDetail::getStaffUserId)
.filter(Objects::nonNull)
.distinct()
.count();
ErpTimesheetSummary update = new ErpTimesheetSummary();
update.setSummaryId(summaryId);
update.setTotalProjectHours(totalProjectHours);
update.setTotalDeptHours(totalDeptHours);
update.setTotalHours(totalProjectHours.add(totalDeptHours));
update.setStaffCount((int) staffCount);
summaryMapper.updateById(update);
private List<PreAllocTargetVo> buildTargetVos(Long allocId) {
if (allocId == null) {
return Collections.emptyList();
}
List<ErpTimesheetPreAllocDetail> detailList = preAllocDetailMapper.selectList(Wrappers.<ErpTimesheetPreAllocDetail>lambdaQuery()
.eq(ErpTimesheetPreAllocDetail::getAllocId, allocId)
.eq(ErpTimesheetPreAllocDetail::getDelFlag, DEL_FLAG_NORMAL)
.orderByAsc(ErpTimesheetPreAllocDetail::getSortOrder)
.orderByAsc(ErpTimesheetPreAllocDetail::getAllocDetailId));
List<PreAllocTargetVo> targetVos = new ArrayList<>();
for (ErpTimesheetPreAllocDetail detail : detailList) {
PreAllocTargetVo targetVo = new PreAllocTargetVo();
targetVo.setTargetProjectId(detail.getTargetProjectId());
targetVo.setTargetProjectCode(detail.getTargetProjectCode());
targetVo.setTargetProjectName(detail.getTargetProjectName());
targetVo.setAllocHours(nvl(detail.getAllocHours()));
targetVos.add(targetVo);
}
return targetVos;
}
private String resolveAllocStatus(BigDecimal sourceTotal, BigDecimal allocatedTotal) {
@ -795,40 +506,12 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
return value == null ? BigDecimal.ZERO : value;
}
private String decimalKey(BigDecimal value) {
return nvl(value).stripTrailingZeros().toPlainString();
private record MonthRange(Date startDate, Date endDate) {
}
@lombok.Data
private static class StaffSource {
private Long staffUserId;
private String staffName;
private BigDecimal sourceHours = BigDecimal.ZERO;
private List<ErpTimesheetSummaryDetail> existingRows = new ArrayList<>();
private record TargetAllocation(ErpProjectInfo targetProject, BigDecimal allocHours) {
}
@lombok.Data
private static class TargetAllocation {
private final ErpProjectInfo targetProject;
private final BigDecimal allocHours;
}
@lombok.Data
private static class TargetAggregation {
private ErpProjectInfo targetProject;
private BigDecimal allocHours = BigDecimal.ZERO;
private void addAllocHours(BigDecimal hours) {
this.allocHours = this.allocHours.add(hours);
}
}
@lombok.Data
private static class AllocationResult {
private BigDecimal sourceTotal = BigDecimal.ZERO;
private BigDecimal allocatedTotal = BigDecimal.ZERO;
private Integer staffCount = 0;
private List<ErpTimesheetSummaryDetail> summaryDetails = new ArrayList<>();
private List<TargetAggregation> targetAggregations = new ArrayList<>();
private record AllocationResult(BigDecimal allocatedTotal, List<TargetAllocation> targetAllocations) {
}
}

@ -11,4 +11,45 @@
${ew.getCustomSqlSegment}
</select>
<select id="selectAvailableSourceProjects" resultType="org.dromara.oa.erp.domain.vo.ErpProjectInfoVo">
select
pi.project_id as projectId,
pi.project_code as projectCode,
pi.project_name as projectName,
pi.project_category as projectCategory
from erp_timesheet_info i
inner join erp_timesheet_project p
on p.timesheet_id = i.timesheet_id
and p.del_flag = '0'
inner join erp_project_info pi
on pi.project_id = p.project_id
and pi.del_flag = '0'
where i.del_flag = '0'
and i.timesheet_status = '3'
and i.dept_id = #{deptId}
and i.start_time &lt;= #{endDate}
and i.end_time &gt;= #{startDate}
and p.hours &gt; 0
and pi.project_category = '4'
group by pi.project_id, pi.project_code, pi.project_name, pi.project_category
order by pi.project_code asc, pi.project_name asc
</select>
<select id="selectSourceStat" resultType="org.dromara.oa.erp.domain.vo.PreAllocSourceStatVo">
select
coalesce(sum(p.hours), 0) as sourceTotalHours,
count(distinct i.user_id) as staffCount
from erp_timesheet_info i
inner join erp_timesheet_project p
on p.timesheet_id = i.timesheet_id
and p.del_flag = '0'
where i.del_flag = '0'
and i.timesheet_status = '3'
and i.dept_id = #{deptId}
and i.start_time &lt;= #{endDate}
and i.end_time &gt;= #{startDate}
and p.project_id = #{projectId}
and p.hours &gt; 0
</select>
</mapper>

Loading…
Cancel
Save