feat(oa/erp): 完善预投工时分配功能

- 添加员工级分配明细列表功能
- 新增查询员工级分配详情接口getStaffAllocDetails
- 将新增和编辑方法统一为saveAlloc方法
- 禁止手动维护分配明细,改为由主单自动生成
- 增强数据校验和权限控制逻辑
- 实现工时分配的事务性处理和数据一致性保障
- 将工时字段从Long类型改为BigDecimal类型以支持小数精度
- 修改项目ID字段描述为来源预投项目ID
- 更新涉及人数字段从Long改为Integer类型
- 移除分配单编号的非空验证要求
dev
yangk 1 month ago
parent af05b7fb12
commit 1d35ca444e

@ -19,6 +19,7 @@ import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.oa.erp.domain.vo.ErpTimesheetPreAllocVo;
import org.dromara.oa.erp.domain.bo.ErpTimesheetPreAllocBo;
import org.dromara.oa.erp.domain.vo.PreAllocDetailVo;
import org.dromara.oa.erp.service.IErpTimesheetPreAllocService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -64,9 +65,19 @@ public class ErpTimesheetPreAllocController extends BaseController {
*/
@SaCheckPermission("oa/erp:timesheetPreAlloc:query")
@GetMapping("/{allocId}")
public R<ErpTimesheetPreAllocVo> getInfo(@NotNull(message = "主键不能为空")
public R<PreAllocDetailVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("allocId") Long allocId) {
return R.ok(erpTimesheetPreAllocService.queryById(allocId));
return R.ok(erpTimesheetPreAllocService.queryDetailById(allocId));
}
/**
*
*/
@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));
}
/**
@ -77,7 +88,7 @@ public class ErpTimesheetPreAllocController extends BaseController {
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ErpTimesheetPreAllocBo bo) {
return toAjax(erpTimesheetPreAllocService.insertByBo(bo));
return toAjax(erpTimesheetPreAllocService.saveAlloc(bo));
}
/**
@ -88,7 +99,7 @@ public class ErpTimesheetPreAllocController extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ErpTimesheetPreAllocBo bo) {
return toAjax(erpTimesheetPreAllocService.updateByBo(bo));
return toAjax(erpTimesheetPreAllocService.saveAlloc(bo));
}
/**

@ -77,7 +77,7 @@ public class ErpTimesheetPreAllocDetailController extends BaseController {
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ErpTimesheetPreAllocDetailBo bo) {
return toAjax(erpTimesheetPreAllocDetailService.insertByBo(bo));
return R.fail("分配明细由主单自动生成,不允许手工维护");
}
/**
@ -88,7 +88,7 @@ public class ErpTimesheetPreAllocDetailController extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ErpTimesheetPreAllocDetailBo bo) {
return toAjax(erpTimesheetPreAllocDetailService.updateByBo(bo));
return R.fail("分配明细由主单自动生成,不允许手工维护");
}
/**
@ -101,7 +101,7 @@ public class ErpTimesheetPreAllocDetailController extends BaseController {
@DeleteMapping("/{allocDetailIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("allocDetailIds") Long[] allocDetailIds) {
return toAjax(erpTimesheetPreAllocDetailService.deleteWithValidByIds(List.of(allocDetailIds), true));
return R.fail("分配明细由主单自动生成,不允许手工维护");
}
/**

@ -4,8 +4,8 @@ import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
@ -45,7 +45,7 @@ public class ErpTimesheetPreAlloc extends TenantEntity {
private Long standardMonthId;
/**
* ID()
* ID
*/
private Long projectId;
@ -62,17 +62,17 @@ public class ErpTimesheetPreAlloc extends TenantEntity {
/**
* (,)
*/
private Long sourceTotalHours;
private BigDecimal sourceTotalHours;
/**
* ()
*/
private Long allocatedTotalHours;
private BigDecimal allocatedTotalHours;
/**
*
*/
private Long staffCount;
private Integer staffCount;
/**
* 0 1 2

@ -6,6 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.math.BigDecimal;
/**
* erp_timesheet_pre_alloc_detail
@ -35,7 +36,7 @@ public class ErpTimesheetPreAllocDetail extends TenantEntity {
/**
*
*/
private Long sortOrder;
private Integer sortOrder;
/**
* ID
@ -70,7 +71,7 @@ public class ErpTimesheetPreAllocDetail extends TenantEntity {
/**
* ()
*/
private Long allocHours;
private BigDecimal allocHours;
/**
*

@ -8,8 +8,9 @@ import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.List;
/**
* erp_timesheet_pre_alloc
@ -31,7 +32,6 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
/**
*
*/
@NotBlank(message = "分配单编号不能为空", groups = { AddGroup.class, EditGroup.class })
private String allocCode;
/**
@ -46,9 +46,9 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
private Long standardMonthId;
/**
* ID()
* ID
*/
@NotNull(message = "项目ID(备件、物流)不能为空", groups = { AddGroup.class, EditGroup.class })
@NotNull(message = "来源预投项目ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long projectId;
/**
@ -64,24 +64,21 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
/**
* (,)
*/
@NotNull(message = "来源预投工时合计(天,带出汇总)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long sourceTotalHours;
private BigDecimal sourceTotalHours;
/**
* ()
*/
@NotNull(message = "已分配合计(天)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long allocatedTotalHours;
private BigDecimal allocatedTotalHours;
/**
*
*/
private Long staffCount;
private Integer staffCount;
/**
* 0 1 2
*/
@NotBlank(message = "单据状态0未分配 1部分分配 2已分配不能为空", groups = { AddGroup.class, EditGroup.class })
private String allocStatus;
/**
@ -104,5 +101,9 @@ public class ErpTimesheetPreAllocBo extends BaseEntity {
*/
private String remark;
/**
*
*/
private List<PreAllocStaffAllocBo> staffAllocList;
}

@ -8,6 +8,7 @@ import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
/**
* erp_timesheet_pre_alloc_detail
@ -35,7 +36,7 @@ public class ErpTimesheetPreAllocDetailBo extends BaseEntity {
/**
*
*/
private Long sortOrder;
private Integer sortOrder;
/**
* ID
@ -73,7 +74,7 @@ public class ErpTimesheetPreAllocDetailBo extends BaseEntity {
* ()
*/
@NotNull(message = "分配工时(天)不能为空", groups = { AddGroup.class, EditGroup.class })
private Long allocHours;
private BigDecimal allocHours;
/**
*

@ -0,0 +1,37 @@
package org.dromara.oa.erp.domain.bo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
*
*
* @author Yangk
* @date 2026-05-21
*/
@Data
public class PreAllocStaffAllocBo {
/**
* ID
*/
private Long staffUserId;
/**
*
*/
private String staffName;
/**
* ()
*/
private BigDecimal sourceHours;
/**
*
*/
private List<PreAllocTargetBo> allocItems;
}

@ -0,0 +1,26 @@
package org.dromara.oa.erp.domain.bo;
import lombok.Data;
import java.math.BigDecimal;
/**
*
*
* @author Yangk
* @date 2026-05-21
*/
@Data
public class PreAllocTargetBo {
/**
* ID
*/
private Long targetProjectId;
/**
* ()
*/
private BigDecimal allocHours;
}

@ -10,7 +10,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
@ -44,7 +44,7 @@ public class ErpTimesheetPreAllocDetailVo implements Serializable {
*
*/
@ExcelProperty(value = "排序号")
private Long sortOrder;
private Integer sortOrder;
/**
* ID
@ -86,7 +86,7 @@ public class ErpTimesheetPreAllocDetailVo implements Serializable {
* ()
*/
@ExcelProperty(value = "分配工时(天)")
private Long allocHours;
private BigDecimal allocHours;
/**
*

@ -1,7 +1,6 @@
package org.dromara.oa.erp.domain.vo;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
@ -12,7 +11,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
@ -55,9 +54,9 @@ public class ErpTimesheetPreAllocVo implements Serializable {
private Long standardMonthId;
/**
* ID()
* ID
*/
@ExcelProperty(value = "项目ID(备件、物流)")
@ExcelProperty(value = "来源预投项目ID")
private Long projectId;
/**
@ -76,19 +75,19 @@ public class ErpTimesheetPreAllocVo implements Serializable {
* (,)
*/
@ExcelProperty(value = "来源预投工时合计(天,带出汇总)")
private Long sourceTotalHours;
private BigDecimal sourceTotalHours;
/**
* ()
*/
@ExcelProperty(value = "已分配合计(天)")
private Long allocatedTotalHours;
private BigDecimal allocatedTotalHours;
/**
*
*/
@ExcelProperty(value = "涉及人数")
private Long staffCount;
private Integer staffCount;
/**
* 0 1 2

@ -0,0 +1,64 @@
package org.dromara.oa.erp.domain.vo;
import lombok.Data;
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
*/
@Data
public class PreAllocDetailVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long allocId;
private String allocCode;
private String monthCode;
private Long standardMonthId;
/**
* ID
*/
private Long projectId;
private String projectCode;
private String projectName;
private BigDecimal sourceTotalHours = BigDecimal.ZERO;
private BigDecimal allocatedTotalHours = BigDecimal.ZERO;
private BigDecimal remainingTotalHours = BigDecimal.ZERO;
private Integer staffCount = 0;
private String allocStatus;
private String appliedFlag;
private Long summaryId;
private Date applyTime;
private String remark;
/**
*
*/
private List<PreAllocStaffAllocVo> staffAllocList = new ArrayList<>();
}

@ -0,0 +1,53 @@
package org.dromara.oa.erp.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* @author Yangk
* @date 2026-05-21
*/
@Data
public class PreAllocStaffAllocVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long staffUserId;
/**
*
*/
private String staffName;
/**
* ()
*/
private BigDecimal sourceHours = BigDecimal.ZERO;
/**
* ()
*/
private BigDecimal allocatedHours = BigDecimal.ZERO;
/**
* ()
*/
private BigDecimal remainingHours = BigDecimal.ZERO;
/**
*
*/
private List<PreAllocTargetVo> allocItems = new ArrayList<>();
}

@ -0,0 +1,41 @@
package org.dromara.oa.erp.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
*
*
* @author Yangk
* @date 2026-05-21
*/
@Data
public class PreAllocTargetVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long targetProjectId;
/**
*
*/
private String targetProjectCode;
/**
*
*/
private String targetProjectName;
/**
* ()
*/
private BigDecimal allocHours;
}

@ -1,10 +1,10 @@
package org.dromara.oa.erp.service;
import org.dromara.oa.erp.domain.ErpTimesheetPreAlloc;
import org.dromara.oa.erp.domain.vo.ErpTimesheetPreAllocVo;
import org.dromara.oa.erp.domain.bo.ErpTimesheetPreAllocBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.oa.erp.domain.vo.PreAllocDetailVo;
import java.util.Collection;
import java.util.List;
@ -25,6 +25,14 @@ public interface IErpTimesheetPreAllocService {
*/
ErpTimesheetPreAllocVo queryById(Long allocId);
/**
*
*
* @param allocId
* @return
*/
PreAllocDetailVo queryDetailById(Long allocId);
/**
*
*
@ -42,6 +50,15 @@ public interface IErpTimesheetPreAllocService {
*/
List<ErpTimesheetPreAllocVo> queryList(ErpTimesheetPreAllocBo bo);
/**
*
*
* @param monthCode (YYYYMM)
* @param projectId ID
* @return
*/
PreAllocDetailVo getStaffAllocDetails(String monthCode, Long projectId);
/**
*
*
@ -58,6 +75,14 @@ public interface IErpTimesheetPreAllocService {
*/
Boolean updateByBo(ErpTimesheetPreAllocBo bo);
/**
*
*
* @param bo
* @return
*/
Boolean saveAlloc(ErpTimesheetPreAllocBo bo);
/**
*
*

@ -1,24 +1,52 @@
package org.dromara.oa.erp.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.springframework.stereotype.Service;
import org.dromara.oa.erp.domain.bo.ErpTimesheetPreAllocBo;
import org.dromara.oa.erp.domain.vo.ErpTimesheetPreAllocVo;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
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.ErpTimesheetPreAllocVo;
import org.dromara.oa.erp.domain.vo.PreAllocDetailVo;
import org.dromara.oa.erp.domain.vo.PreAllocStaffAllocVo;
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.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.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Service
@ -30,7 +58,25 @@ import java.util.Collection;
@Service
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
private RemoteCodeRuleService remoteCodeRuleService;
/**
*
@ -39,23 +85,46 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
* @return
*/
@Override
public ErpTimesheetPreAllocVo queryById(Long allocId){
public ErpTimesheetPreAllocVo queryById(Long allocId) {
return baseMapper.selectVoById(allocId);
}
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
@Override
public TableDataInfo<ErpTimesheetPreAllocVo> queryPageList(ErpTimesheetPreAllocBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<ErpTimesheetPreAlloc> lqw = buildQueryWrapper(bo);
Page<ErpTimesheetPreAllocVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
/**
*
*
* @param allocId
* @return
*/
@Override
public PreAllocDetailVo queryDetailById(Long allocId) {
ErpTimesheetPreAlloc alloc = baseMapper.selectById(allocId);
if (alloc == null) {
throw new ServiceException("预投工时分配单不存在");
}
Long deptId = LoginHelper.getDeptId();
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());
}
/**
*
*
* @param bo
* @param pageQuery
* @return
*/
@Override
public TableDataInfo<ErpTimesheetPreAllocVo> queryPageList(ErpTimesheetPreAllocBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<ErpTimesheetPreAlloc> lqw = buildQueryWrapper(bo);
Page<ErpTimesheetPreAllocVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
*
@ -70,25 +139,79 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
}
private MPJLambdaWrapper<ErpTimesheetPreAlloc> buildQueryWrapper(ErpTimesheetPreAllocBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<ErpTimesheetPreAlloc> lqw = JoinWrappers.lambda(ErpTimesheetPreAlloc.class)
.selectAll(ErpTimesheetPreAlloc.class)
.eq(ErpTimesheetPreAlloc::getDelFlag, "0")
.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(bo.getSourceTotalHours() != null, ErpTimesheetPreAlloc::getSourceTotalHours, bo.getSourceTotalHours())
.eq(bo.getAllocatedTotalHours() != null, ErpTimesheetPreAlloc::getAllocatedTotalHours, bo.getAllocatedTotalHours())
.eq(bo.getStaffCount() != null, ErpTimesheetPreAlloc::getStaffCount, bo.getStaffCount())
.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())
;
return lqw;
Long deptId = LoginHelper.getDeptId();
return JoinWrappers.lambda(ErpTimesheetPreAlloc.class)
.selectAll(ErpTimesheetPreAlloc.class)
.eq(ErpTimesheetPreAlloc::getDelFlag, DEL_FLAG_NORMAL)
.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
*/
@Override
public PreAllocDetailVo getStaffAllocDetails(String monthCode, Long projectId) {
validateMonthCode(monthCode);
if (projectId == null) {
throw new ServiceException("来源预投项目不能为空");
}
Long deptId = LoginHelper.getDeptId();
ErpTimesheetSummary summary = getSingleSummary(monthCode, deptId);
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);
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.setAllocStatus(resolveAllocStatus(sourceTotal, allocatedTotal));
return detailVo;
}
/**
@ -99,13 +222,7 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
*/
@Override
public Boolean insertByBo(ErpTimesheetPreAllocBo bo) {
ErpTimesheetPreAlloc add = MapstructUtils.convert(bo, ErpTimesheetPreAlloc.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setAllocId(add.getAllocId());
}
return flag;
return saveAlloc(bo);
}
/**
@ -116,16 +233,64 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
*/
@Override
public Boolean updateByBo(ErpTimesheetPreAllocBo bo) {
ErpTimesheetPreAlloc update = MapstructUtils.convert(bo, ErpTimesheetPreAlloc.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
return saveAlloc(bo);
}
/**
*
*
*
* @param bo
* @return
*/
private void validEntityBeforeSave(ErpTimesheetPreAlloc entity){
//TODO 做一些数据校验,如唯一约束
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveAlloc(ErpTimesheetPreAllocBo bo) {
validateMonthCode(bo.getMonthCode());
if (bo.getProjectId() == null) {
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("只能维护当前登录部门的预投工时分配");
}
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);
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);
boolean isNew = alloc == null;
if (isNew) {
alloc = new ErpTimesheetPreAlloc();
alloc.setAllocCode(generateAllocCode());
alloc.setCreateDept(deptId);
} else if (StringUtils.isBlank(alloc.getAllocCode())) {
alloc.setAllocCode(generateAllocCode());
}
fillAlloc(alloc, bo, lockedSummary, sourceProject, allocationResult);
if (isNew) {
baseMapper.insert(alloc);
} else {
baseMapper.updateById(alloc);
}
rewriteSummaryDetails(lockedSummary.getSummaryId(), sourceProject.getProjectId(), allocationResult.getSummaryDetails());
rewritePreAllocDetails(alloc, sourceProject, allocationResult.getTargetAggregations());
recalculateSummary(lockedSummary.getSummaryId());
bo.setAllocId(alloc.getAllocId());
return true;
}
/**
@ -136,10 +301,477 @@ public class ErpTimesheetPreAllocServiceImpl implements IErpTimesheetPreAllocSer
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
if (ids == null || ids.isEmpty()) {
return false;
}
List<ErpTimesheetPreAlloc> allocList = baseMapper.selectList(Wrappers.<ErpTimesheetPreAlloc>lambdaQuery()
.in(ErpTimesheetPreAlloc::getAllocId, ids)
.eq(ErpTimesheetPreAlloc::getDelFlag, DEL_FLAG_NORMAL));
if (allocList.size() != ids.size()) {
throw new ServiceException("部分预投工时分配单不存在或已删除");
}
Long deptId = LoginHelper.getDeptId();
for (ErpTimesheetPreAlloc alloc : allocList) {
if (APPLIED.equals(alloc.getAppliedFlag())) {
throw new ServiceException("已回写月汇总的预投工时分配单不允许删除");
}
if (alloc.getCreateDept() != null && !Objects.equals(alloc.getCreateDept(), deptId)) {
throw new ServiceException("只能删除当前登录部门的预投工时分配单");
}
}
preAllocDetailMapper.delete(Wrappers.<ErpTimesheetPreAllocDetail>lambdaQuery()
.in(ErpTimesheetPreAllocDetail::getAllocId, ids));
return baseMapper.deleteByIds(ids) > 0;
}
private void validateMonthCode(String monthCode) {
if (StringUtils.isBlank(monthCode) || !MONTH_CODE_PATTERN.matcher(monthCode).matches()) {
throw new ServiceException("月份编码必须是YYYYMM格式");
}
}
private ErpTimesheetSummary getSingleSummary(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()) {
throw new ServiceException("当前部门该月份的月汇总工时不存在");
}
if (summaries.size() > 1) {
throw new ServiceException("当前部门该月份存在多个月汇总工时,请先修正月汇总数据");
}
return summaries.get(0);
}
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("月汇总工时不存在或已删除");
}
return summary;
}
private ErpProjectInfo getProject(Long projectId, String message) {
ErpProjectInfo project = projectInfoMapper.selectById(projectId);
if (project == null || !DEL_FLAG_NORMAL.equals(project.getDelFlag())) {
throw new ServiceException(message);
}
return project;
}
private void validateProjectCategory(ErpProjectInfo project, String expectedCategory, String message) {
if (!expectedCategory.equals(project.getProjectCategory())) {
throw new ServiceException(message);
}
}
private void validateTargetProject(ErpProjectInfo project) {
if (!TARGET_PROJECT_CATEGORIES.contains(project.getProjectCategory())) {
throw new ServiceException("目标项目只能选择物流或备件项目");
}
}
private ErpTimesheetPreAlloc queryActiveAlloc(Long summaryId, Long projectId) {
List<ErpTimesheetPreAlloc> allocList = baseMapper.selectList(Wrappers.<ErpTimesheetPreAlloc>lambdaQuery()
.eq(ErpTimesheetPreAlloc::getSummaryId, summaryId)
.eq(ErpTimesheetPreAlloc::getProjectId, projectId)
.eq(ErpTimesheetPreAlloc::getDelFlag, DEL_FLAG_NORMAL));
if (allocList.size() > 1) {
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);
if (allocId == null) {
return keyAlloc;
}
ErpTimesheetPreAlloc idAlloc = baseMapper.selectById(allocId);
if (idAlloc == null || !DEL_FLAG_NORMAL.equals(idAlloc.getDelFlag())) {
throw new ServiceException("预投工时分配单不存在");
}
if (keyAlloc != null && !Objects.equals(keyAlloc.getAllocId(), allocId)) {
throw new ServiceException("当前部门该月份该预投项目已存在分配单");
}
if (!Objects.equals(idAlloc.getSummaryId(), summaryId) || !Objects.equals(idAlloc.getProjectId(), projectId)) {
throw new ServiceException("不允许修改分配单的月份或来源预投项目");
}
if (idAlloc.getCreateDept() != null && !Objects.equals(idAlloc.getCreateDept(), deptId)) {
throw new ServiceException("只能维护当前登录部门的预投工时分配单");
}
return idAlloc;
}
private void fillAlloc(ErpTimesheetPreAlloc alloc,
ErpTimesheetPreAllocBo bo,
ErpTimesheetSummary summary,
ErpProjectInfo sourceProject,
AllocationResult allocationResult) {
alloc.setMonthCode(summary.getMonthCode());
alloc.setStandardMonthId(summary.getStandardMonthId());
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.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);
}
}
private void rewritePreAllocDetails(ErpTimesheetPreAlloc alloc,
ErpProjectInfo sourceProject,
List<TargetAggregation> aggregations) {
preAllocDetailMapper.delete(Wrappers.<ErpTimesheetPreAllocDetail>lambdaQuery()
.eq(ErpTimesheetPreAllocDetail::getAllocId, alloc.getAllocId()));
if (aggregations.isEmpty()) {
return;
}
List<ErpTimesheetPreAllocDetail> details = new ArrayList<>();
int sortOrder = 1;
for (TargetAggregation aggregation : aggregations) {
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());
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 String resolveAllocStatus(BigDecimal sourceTotal, BigDecimal allocatedTotal) {
BigDecimal safeSource = nvl(sourceTotal);
BigDecimal safeAllocated = nvl(allocatedTotal);
if (safeAllocated.compareTo(BigDecimal.ZERO) <= 0) {
return STATUS_NOT_ALLOCATED;
}
if (safeAllocated.compareTo(safeSource) < 0) {
return STATUS_PART_ALLOCATED;
}
return STATUS_ALLOCATED;
}
private String generateAllocCode() {
return remoteCodeRuleService.selectCodeRuleCode("1034");
}
private BigDecimal nvl(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
private String decimalKey(BigDecimal value) {
return nvl(value).stripTrailingZeros().toPlainString();
}
@lombok.Data
private static class StaffSource {
private Long staffUserId;
private String staffName;
private BigDecimal sourceHours = BigDecimal.ZERO;
private List<ErpTimesheetSummaryDetail> existingRows = new ArrayList<>();
}
@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<>();
}
}

Loading…
Cancel
Save