|
|
|
|
@ -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) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|