ccUsers = userRespDTOS.stream().map(user -> {
BpmOAReimbursementPrintDataRespVO.CCUser cc = new BpmOAReimbursementPrintDataRespVO.CCUser();
cc.setCcUserId(user.getId());
cc.setCcUserName(user.getNickname());
cc.setCcDeptName(deptMapDTO.get(user.getDeptId()).getName());
return cc;
}).collect(Collectors.toList());
BpmOAReimbursementPrintDataRespVO reimbursementPrintData = new BpmOAReimbursementPrintDataRespVO() ;
reimbursementPrintData.setBpmOAReimbursementRespVO(bpmOAReimbursementRespVO) ;
reimbursementPrintData.setProcessTasks(taskRespVOList);
reimbursementPrintData.setCcUsers(ccUsers);
bpmProcessInstancePrintDataRespVO.setBpmOAReimbursementPrintDataRespVO(reimbursementPrintData) ;
}
return bpmProcessInstancePrintDataRespVO ;
}
}
\ No newline at end of file
+package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmOAReimbursementPrintDataRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.print.BpmProcessInstancePrintDataRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.reimbursement.BpmOAReimbursementRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAImprestDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAReimbursementDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmConstants;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAImprestService;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAProcureServiceImpl;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAReimbursementService;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* 流程实例 Service 实现类
*
* ProcessDefinition & ProcessInstance & Execution & Task 的关系:
* 1.
*
* HistoricProcessInstance & ProcessInstance 的关系:
* 1.
*
* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
@Resource
private TaskService engineTaskService;
@Resource
private BpmTaskAssignRuleMapper taskRuleMapper;
@Resource
private BpmUserGroupService userGroupService;
@Resource
private RuntimeService runtimeService;
@Resource
private BpmProcessInstanceExtMapper processInstanceExtMapper;
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private HistoryService historyService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;
@Resource
@Lazy // 解决循环依赖
private BpmMessageService messageService;
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
}
@Override
public List getProcessInstances(Set ids) {
return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public PageResult getMyProcessInstancePage(Long userId,
BpmProcessInstanceMyPageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return new PageResult<>(pageResult.getTotal());
}
// 获得流程 Task Map
List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
List ids = taskMap.values().stream()
.flatMap(Collection::stream)
.map(Task::getAssignee)
.collect(Collectors.toList());
Iterator iterator = ids.iterator();
while (iterator.hasNext()) {
String id = iterator.next();
if (id == null) {
iterator.remove();
}
}
List longIds = ids.stream()
.map(Long::valueOf)
.collect(Collectors.toList());
// 获得 User Map
Map userMap = adminUserApi.getUserMap(longIds);
// 转换返回
return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, userMap);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
// 发起流程
return createProcessInstance0(userId, definition, createReqVO.getVariables(), null);
}
@Override
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// 发起流程
return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
}
@Override
public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
// 获得流程实例
HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
if (processInstance == null) {
return null;
}
BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id);
Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id);
// 获得流程定义
ProcessDefinition processDefinition = processDefinitionService
.getProcessDefinition(processInstance.getProcessDefinitionId());
Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId());
BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt(
processInstance.getProcessDefinitionId());
Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id);
String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());
// 获得 User
AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData();
DeptRespDTO dept = null;
if (startUser != null) {
dept = deptApi.getDept(startUser.getDeptId()).getCheckedData();
}
// 拼接结果
return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt,
processDefinition, processDefinitionExt, bpmnXml, startUser, dept);
}
@Override
public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
// 校验流程实例存在
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
}
// 只能取消自己的
if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
}
// 通过删除流程实例,实现流程实例的取消,
// 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询
deleteProcessInstance(cancelReqVO.getId(),
BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
}
/**
* 获得历史的流程实例
*
* @param id 流程实例的编号
* @return 历史的流程实例
*/
@Override
public HistoricProcessInstance getHistoricProcessInstance(String id) {
return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
}
@Override
public List getHistoricProcessInstances(Set ids) {
return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public void createProcessInstanceExt(ProcessInstance instance) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId());
// 插入 BpmProcessInstanceExtDO 对象
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(instance.getId())
.setProcessDefinitionId(definition.getId())
.setName(instance.getProcessDefinitionName())
.setStartUserId(Long.valueOf(instance.getStartUserId()))
.setCategory(definition.getCategory())
.setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus())
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
processInstanceExtMapper.insert(instanceExtDO);
}
@Override
public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
// 判断是否为 Reject 不通过。如果是,则不进行更新.
// 因为,updateProcessInstanceExtReject 方法,已经进行更新了
if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) {
return;
}
// 需要主动查询,因为 instance 只有 id 属性
// 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(event.getProcessInstanceId())
.setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Override
public void updateProcessInstanceExtComplete(ProcessInstance instance) {
// 需要主动查询,因为 instance 只有 id 属性
// 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(instance.getProcessInstanceId())
.setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
Map processVariables = runtimeService.getVariables(instance.getProcessInstanceId());
String reason = (String) processVariables.get("approve_reason");
// 发送流程被通过的消息
messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance, reason));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProcessInstanceExtReject(String id, String reason) {
// 需要主动查询,因为 instance 只有 id 属性
ProcessInstance processInstance = getProcessInstance(id);
// 删除流程实例,以实现驳回任务时,取消整个审批流程
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason)));
// 更新 status + result
// 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法,
// 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id)
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被不通过的消息
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
private void deleteProcessInstance(String id, String reason) {
runtimeService.deleteProcessInstance(id, reason);
}
private String createProcessInstance0(Long userId, ProcessDefinition definition,
Map variables, String businessKey) {
// 校验流程定义
if (definition == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
if (definition.isSuspended()) {
throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
}
// 创建流程实例
ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
.processDefinitionId(definition.getId())
.businessKey(businessKey)
.name(definition.getName().trim())
.variables(variables)
.start();
// 设置流程名字
runtimeService.setProcessInstanceName(instance.getId(), definition.getName());
// 补全流程实例的拓展表
processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId())
.setFormVariables(variables));
/** 创建流程后,添加抄送人 End add by yj 2024.1.4 */
processCCToUsers(definition, instance);
/** 通过自己发起的流程 */
// approveSelfTask(instance.getId()) ;
return instance.getId();
}
private void approveSelfTask(String processInstanceId) {
List tasks = engineTaskService.createTaskQuery().processInstanceId(processInstanceId).list();
if (tasks != null && tasks.size() > 0) {
Task task = tasks.get(0);
String assigneeId = task.getAssignee();
//如果当前登陆用户是审批人,那么自动审批通过
if (assigneeId.equals(SecurityFrameworkUtils.getLoginUserId().toString())) {
BpmTaskApproveReqVO reqVO = new BpmTaskApproveReqVO();
reqVO.setId(task.getId());
reqVO.setReason(BpmConstants.AUTO_APPRAVAL);
taskService.approveTask(getLoginUserId(), reqVO);
}
}
}
/**
* 获得抄送我的流程实例的分页
*
* @param userId 用户编号
* @param pageReqVO 分页请求
* @return 流程实例的分页
*/
public PageResult getMyCCProcessInstancePage(Long userId,
BpmProcessInstanceMyPageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
PageResult pageResult = processInstanceExtMapper.selectCCPage(userId, pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return new PageResult<>(pageResult.getTotal());
}
// 获得流程 Task Map
List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
// 转换返回
return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap, null);
}
public List getProcessInstancesGroupByModelName(BpmProcessInstanceStatisticsReqVO pageReqVO) {
pageReqVO = getUserids(pageReqVO);
return processInstanceExtMapper.getProcessInstancesGroupByModelName(pageReqVO);
}
public List getProcessInstancesGroupByResultStatus(BpmProcessInstanceStatisticsReqVO pageReqVO) {
pageReqVO = getUserids(pageReqVO);
return processInstanceExtMapper.getProcessInstancesGroupByResultStatus(pageReqVO);
}
public PageResult getStatisticsProcessInstancePage(@Valid BpmProcessInstanceMyPageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
PageResult pageResult = processInstanceExtMapper.selectStatisticePage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return new PageResult<>(pageResult.getTotal());
}
// 获得流程 Task Map
List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
List ids = taskMap.values().stream()
.flatMap(Collection::stream)
.map(Task::getAssignee)
.collect(Collectors.toList());
Iterator iterator = ids.iterator();
while (iterator.hasNext()) {
String id = iterator.next();
if (id == null) {
iterator.remove();
}
}
List longIds = ids.stream()
.map(Long::valueOf)
.collect(Collectors.toList());
//获得 审批人 User Map
Map assigneeUserMap = adminUserApi.getUserMap(longIds);
//获得 发起人 User Map
List bpieDOs = pageResult.getList();
longIds = new ArrayList<>();
for (BpmProcessInstanceExtDO bpieDO : bpieDOs) {
longIds.add(bpieDO.getStartUserId());
}
Map startUserMap = adminUserApi.getUserMap(longIds);
// 转换返回
return BpmProcessInstanceConvert.INSTANCE.convertStatisticsPage(pageResult, taskMap, assigneeUserMap, startUserMap);
}
@Resource
PermissionApi permissionApi;
/**
* 根据数据权限,查询关联操作的用户IDS
*
* @param pageReqVO
* @param
* @return
*/
private T getUserids(T pageReqVO) {
try {
Class clazz = (Class) pageReqVO.getClass();
Field idField = clazz.getDeclaredField("userIds");
idField.setAccessible(true); // 设置可访问性
Long[] userIds = null;
Long userId = WebFrameworkUtils.getLoginUserId();
DeptDataPermissionRespDTO deptDataPermission = permissionApi.getDeptDataPermission(userId).getCheckedData();
//查询全部
if (deptDataPermission.getAll()) {
//idField.set(pageReqVO, null); // 设置属性值
return pageReqVO;
}
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {
//设置成0,一个不存在的用户Id,就查询不到数据了。
userIds = new Long[]{0L};
idField.set(pageReqVO, userIds);
return pageReqVO;
}
//情况三 至查询自己
if (deptDataPermission.getSelf()) {
userIds = new Long[]{userId};
idField.set(pageReqVO, userIds);
return pageReqVO;
}
Set deptIds = deptDataPermission.getDeptIds();
//查询部门关联的用户Id
List users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData();
List tempList = new ArrayList<>();
for (AdminUserRespDTO user : users) {
Long id = user.getId();
tempList.add(id);
}
tempList.add(userId);
userIds = tempList.stream().toArray(Long[]::new); //将临时的List集合转换成数组集合
idField.set(pageReqVO, userIds);
return pageReqVO;
} catch (Exception exception) {
exception.printStackTrace();
throw exception(BPM_SYSTEM_BUG);
}
}
/**
* /创建流程后,添加抄送人 Begin add by yj 2024.1.4
* 在设计流程的时候,需要添加一个任务块 名字必须叫Activity_cc 分配权限的时候,需要选择用户组。
*
* @param definition
* @param instance
*/
private void processCCToUsers(ProcessDefinition definition, ProcessInstance instance) {
//获取bpm_task_assign_reule (Bpm 任务规则表)的流程中有没有配置抄送节点 固定抄送名称为:Activity_cc
String processDefinitionId = definition.getId();
List rules = taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, null);
for (BpmTaskAssignRuleDO rule : rules) {
String key = rule.getTaskDefinitionKey(); //任务名称
Integer type = rule.getType();
if (!key.isEmpty() && key.equals(BpmConstants.CC_NAME) && type == 40) {
StringBuffer str = new StringBuffer();
Set options = rule.getOptions();
List list = new ArrayList(options);
for (Long groupId : list) {
//需要根据这个groupId,查询这个组中的用户id
BpmUserGroupDO userGroup = userGroupService.getUserGroup(groupId);
Set userIds = userGroup.getMemberUserIds();
List userIdList = new ArrayList(userIds);
for (Long user_id : userIdList) {
str.append("[").append(user_id).append("]");
}
}
//流程是采购计划时
if (definition.getKey().equals(BpmOAProcureServiceImpl.PROCESS_KEY)) {
AdminUserRespDTO userRespDTO = adminUserApi.getUser(Long.valueOf(instance.getStartUserId())).getCheckedData();
DeptRespDTO deptRespDTO = deptApi.getDept(userRespDTO.getDeptId()).getCheckedData();
//发起人部门为 生产部及以下时
if (deptRespDTO.getFlag().contains("130")) {
//添加 供应部抄送
BpmUserGroupDO userGroup = userGroupService.getUserGroup(121L);
Set userIds = userGroup.getMemberUserIds();
List userIdList = new ArrayList(userIds);
for (Long user_id : userIdList) {
str.append("[").append(user_id).append("]");
}
}
}
String ccids = str.toString();
//根据processDefinitionId 将ccids保存到bpm_process_instance_ext中的ccids字段
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessDefinitionId(processDefinitionId)
.setCcids(ccids).setProcessInstanceId(instance.getProcessInstanceId());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
break;
}
}
}
@Resource
private BpmTaskExtMapper taskExtMapper;
@Override
public List getUserProcessTpo10(BpmProcessInstanceStatisticsReqVO pageReqVO) {
//按审核人分组查询,统计已完成流程数量及平均耗时间
List respVOS = processInstanceExtMapper.getUserProcessTpo10(pageReqVO);
if (respVOS == null || respVOS.size() == 0) {
return null;
}
List idList = respVOS.stream()
.map(BpmProcessFinishStatisticsRespVO::getUserId)
.collect(Collectors.toList());
//根据userId,查询UserMap
Map userMap = adminUserApi.getUserMap(idList);
Long[] idsArray = new Long[userMap.size()];
// 使用 map 的 keySet() 方法获取键集合
Set keys = userMap.keySet();
// 遍历键集合,将每个键添加到数组中
int index = 0;
for (Long key : keys) {
idsArray[index++] = key;
}
pageReqVO.setUserIds(idsArray);
//按审核人分组查询,未审批完成的流程数量
List bpmTaskExtDOs = processInstanceExtMapper.selectUnfinishProcessCount(pageReqVO);
//按审核人分组查询,未完成的记录数
Map unFinfishCountCountMap = new HashMap<>();
for (BpmProcessFinishStatisticsRespVO item : bpmTaskExtDOs) {
unFinfishCountCountMap.put(item.getUserId(), item.getUnFinfishCount());
}
respVOS.forEach(respVO -> respVO.setName(userMap.get(respVO.getUserId()).getNickname()));
respVOS.forEach(respVO -> respVO.setUnFinfishCount(unFinfishCountCountMap.get(respVO.getUserId())));
//获取排行榜第一记录的耗时,作为基础数据
double num = Double.parseDouble(respVOS.get(0).getUserTime()); // 先将字符串转换为双精度浮点数
int baseNumber = (int) num; // 再通过类型转换操作符将其转换为整数
DecimalFormat df = new DecimalFormat("#.00");
respVOS.forEach(respVO -> {
//格式化流程完成率
float finfishCount = (float) respVO.getFinfishCount();
float unFinfishCount = respVO.getUnFinfishCount() == null ? 0 : respVO.getUnFinfishCount();
float all = finfishCount + unFinfishCount;
float result = finfishCount / all;
float rate = result * 100;
respVO.setCompletionRate(df.format(rate) + "%");
double dValue = Double.parseDouble(respVO.getUserTime()); // 将字符串转换为double类型
int roundedValue = (int) Math.round(dValue); // 使用Math.round()进行四舍五入,并转换为int类型
respVO.setUserTime(roundedValue + "");
//格式化进度百度比, 参照最高的数据进行百分比显示
double percentage = ((int) Double.parseDouble(respVO.getUserTime()) / (double) baseNumber) * 100;
respVO.setPercentage((int) Math.round(percentage));
//设置未完成
respVO.setUnFinfishCount(Integer.valueOf((int) unFinfishCount));
});
return respVOS;
}
@Override
public BpmProcessInstanceExtDO getProcessInstanceDO(String id) {
return processInstanceExtMapper.selectByProcessInstanceId(id);
}
@Resource
private FileApi fileApi;
@Resource
@Lazy // 解决循环依赖
private BpmOAReimbursementService reimbursementService;
@Resource
@Lazy // 解决循环依赖
private BpmOAImprestService imprestService;
@Override
public BpmProcessInstancePrintDataRespVO getOAReportPrintData(BpmProcessInstancePrintDataReqVO reqVO) {
BpmProcessInstancePrintDataRespVO bpmProcessInstancePrintDataRespVO = new BpmProcessInstancePrintDataRespVO();
String key = reqVO.getKey(); //流程标识
String processInstanceId = reqVO.getId(); //流程实例ID
bpmProcessInstancePrintDataRespVO.setId(processInstanceId);
bpmProcessInstancePrintDataRespVO.setKey(key);
BpmProcessInstanceRespVO bpmProcessInstance = getProcessInstanceVO(processInstanceId);
Long businessKey = Long.valueOf(bpmProcessInstance.getBusinessKey()); //流程业务表-主键ID
if ("oa_reimbursement".equals(key)) { //报销流程
BpmOAReimbursementDO reimbursement = reimbursementService.getReimbursement(businessKey);
BpmOAReimbursementRespVO bpmOAReimbursementRespVO = reimbursementService.convert(reimbursement); //报销业务数据
List taskRespVOList = taskService.getTaskListByProcessInstanceId(processInstanceId); //报销审核人数据
//给User添加签名地址
taskRespVOList.forEach(taskRespVO ->
taskRespVO.getAssigneeUser().setSignURL(
fileApi.getUserSignImgPath(
taskRespVO.getAssigneeUser().getId()
).getData()
)
);
//备用金信息查询
BpmOAImprestDO bpmOAImprestDO = imprestService.getImprestByReimbursementId(reimbursement.getId());
if (bpmOAImprestDO != null) {
//设置备用金 金额
bpmOAReimbursementRespVO.setAmount(bpmOAImprestDO.getAmount());
}
//获取流程抄送用户编号
BpmProcessInstanceExtDO processInstanceExtDO = getProcessInstanceDO(processInstanceId);
//获取流程抄送用户信息
List ccUserIds = new ArrayList<>();
if (processInstanceExtDO.getCcids() != null && !processInstanceExtDO.getCcids().isEmpty()) {
Pattern pattern = Pattern.compile("\\[(\\d+)]");
Matcher matcher = pattern.matcher(processInstanceExtDO.getCcids());
while (matcher.find()) {
ccUserIds.add(Long.parseLong(matcher.group(1)));
}
}
List userRespDTOS = adminUserApi.getUserList(ccUserIds).getCheckedData();
//获取抄送用户部门信息
Map deptMapDTO = deptApi.getDeptMap(convertSet(userRespDTOS, AdminUserRespDTO::getDeptId));
//拼接数据
List ccUsers = userRespDTOS.stream().map(user -> {
BpmOAReimbursementPrintDataRespVO.CCUser cc = new BpmOAReimbursementPrintDataRespVO.CCUser();
cc.setCcUserId(user.getId());
cc.setCcUserName(user.getNickname());
cc.setCcDeptName(deptMapDTO.get(user.getDeptId()).getName());
return cc;
}).collect(Collectors.toList());
BpmOAReimbursementPrintDataRespVO reimbursementPrintData = new BpmOAReimbursementPrintDataRespVO();
reimbursementPrintData.setBpmOAReimbursementRespVO(bpmOAReimbursementRespVO);
reimbursementPrintData.setProcessTasks(taskRespVOList);
reimbursementPrintData.setCcUsers(ccUsers);
bpmProcessInstancePrintDataRespVO.setBpmOAReimbursementPrintDataRespVO(reimbursementPrintData);
}
return bpmProcessInstancePrintDataRespVO;
}
}
\ No newline at end of file
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java
index b5dc0018..043e006f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java
@@ -10,6 +10,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/dto/ExportAttendanceExcelDTO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/dto/ExportAttendanceExcelDTO.java
index 67fbe74b..2cee7c5a 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/dto/ExportAttendanceExcelDTO.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/dto/ExportAttendanceExcelDTO.java
@@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
@Accessors(chain = true)
public class ExportAttendanceExcelDTO {
- @Schema(description = "报表类型 1月度统计 2每日统计 3打卡记录")
+ @Schema(description = "报表类型 1月度统计 2每日统计")
private Integer type;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/vo/CalculateNum.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/vo/CalculateNum.java
index 73d82819..2cb3fce4 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/vo/CalculateNum.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/vo/CalculateNum.java
@@ -7,6 +7,8 @@ import lombok.Data;
public class CalculateNum {
@Schema(description = "总考勤时间")
long totalWorkingHours = 0L;
+ @Schema(description = "总考勤时间中文")
+ String totalWorkingHoursStr;
@Schema(description = "总出勤天数")
int totalAttendanceDays = 0;
@Schema(description = "总休息天数")
@@ -25,6 +27,10 @@ public class CalculateNum {
String totalEarlyDeparturesTimeStr;
@Schema(description = "缺卡总次数")
int totalMissingCardsNumber = 0;
+ @Schema(description = "上班缺卡总次数")
+ int totalUpMissingCardsNumber = 0;
+ @Schema(description = "下班缺卡总次数")
+ int totalDownMissingCardsNumber = 0;
@Schema(description = "矿工总天数")
int totalMinerDays = 0;
@Schema(description = "外勤次数")
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/attendance/punchrecord/AttendancePunchRecordDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/attendance/punchrecord/AttendancePunchRecordDO.java
index 36ce577c..b8799f18 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/attendance/punchrecord/AttendancePunchRecordDO.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/attendance/punchrecord/AttendancePunchRecordDO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@@ -138,4 +139,7 @@ public class AttendancePunchRecordDO extends BaseDO {
* 是否已提醒 0否 1是
*/
private Integer remindFlag;
+
+ @TableField(exist = false)
+ private String deptName;
}
\ No newline at end of file
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/attendance/punchrecord/AttendancePunchRecordMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/attendance/punchrecord/AttendancePunchRecordMapper.java
index 3fa74945..ac02dcc1 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/attendance/punchrecord/AttendancePunchRecordMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/attendance/punchrecord/AttendancePunchRecordMapper.java
@@ -5,8 +5,12 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
/**
* 用户打卡记录 Mapper
@@ -35,4 +39,12 @@ public interface AttendancePunchRecordMapper extends BaseMapperX statistics(@Param("userList") List userList, @Param("dateList") List dateList);
}
\ No newline at end of file
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/attendance/AttendanceServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/attendance/AttendanceServiceImpl.java
index 3dcaf794..66200e68 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/attendance/AttendanceServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/attendance/AttendanceServiceImpl.java
@@ -21,9 +21,11 @@ import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.Attendance
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshift.AttendanceGroupShiftDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.group.AttendanceGroupMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
+import cn.iocoder.yudao.module.system.dal.mysql.dept.PostMapper;
import cn.iocoder.yudao.module.system.handler.PunchHandler;
import cn.iocoder.yudao.module.system.service.attendance.group.AttendanceGroupService;
import cn.iocoder.yudao.module.system.service.attendance.groupshift.AttendanceGroupShiftService;
@@ -34,6 +36,7 @@ import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnT
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
@@ -44,6 +47,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -85,6 +91,8 @@ public class AttendanceServiceImpl implements AttendanceService {
private AttendanceGroupMapper attendanceGroupMapper;
@Resource
private AttendanceGroupUserService attendanceGroupUserService;
+ @Resource
+ private PostMapper postMapper;
// 定义一些常量以提高代码的可读性和可维护性
@@ -608,7 +616,6 @@ public class AttendanceServiceImpl implements AttendanceService {
@Override
public TeamAttendanceStatisticsByCycleVO tesmStatisticsByCycle(TeamAttendanceStatisticsByCycleDTO dto) {
TeamAttendanceStatisticsByCycleVO vo = new TeamAttendanceStatisticsByCycleVO();
- TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO teamAttendanceStatisticsNumVO = new TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO();
//查询考勤组
AttendanceGroupDO attendanceGroupDO = attendanceGroupService.getGroup(dto.getGroupId());
// - 判断当前用户是否有权限查看
@@ -792,11 +799,189 @@ public class AttendanceServiceImpl implements AttendanceService {
} else {
userList = adminUserService.getAllList(CommonStatusEnum.ENABLE.getStatus(), null, dto.getTargetIds());
}
+
// -- 统计
List dateList = DateUtils.betweenDayList(dto.getStartTime(), dto.getEndTime());
- if (dto.getType() == 1) {
- this.monthlyStatistics(response, userList, dateList);
+ String hh_mm = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
+ String first = CollectionUtil.getFirst(dateList);
+ String last = CollectionUtil.getLast(dateList);
+ //查询出数据 -- 时间周期 - 用户列表
+ List list = attendancePunchRecordMapper.statistics(userList.stream().map(AdminUserDO::getId).collect(Collectors.toList()), dateList);
+ //获取用户列表
+ Map userMap = new HashMap<>();
+ if (CollectionUtil.isNotEmpty(userList)) {
+ userMap = userList.stream().collect(Collectors.toMap(AdminUserDO::getId, Function.identity()));
}
+ List userPostList = postMapper.selectList();
+ // 根据id分组
+ Map postMap = userPostList.stream().collect(Collectors.toMap(PostDO::getId, Function.identity()));
+
+ if (dto.getType() == 1) {
+ String headTitle = String.format("月度汇总 统计日期:%s 至 %s", first, last);
+ String detailedHead = String.format("月度汇总 统计日期:%s 至 %s %s", first, last, hh_mm);
+ this.monthlyStatistics(response, userList, dateList, headTitle, detailedHead, list);
+ } else {
+ String headTitle = String.format("每日统计 统计日期:%s 至 %s", first, last);
+ String detailedHead = String.format("报表生成时间:%s %s", last, hh_mm);
+ this.dayStatistics(response, userMap, postMap, dateList, headTitle, detailedHead, list);
+ }
+ }
+
+
+ /**
+ * 按日导出
+ *
+ * @param response
+ * @param userMap
+ * @param postMap
+ * @param dateList
+ * @param headTitle
+ * @param detailedHead
+ * @param list
+ */
+ private void dayStatistics(HttpServletResponse response, Map userMap, Map postMap, List dateList, String headTitle, String detailedHead, List list) {
+ List> data = new ArrayList<>();
+ // -- 根据部门分组 - 根据考勤组分组
+ Map> userPunchMap = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getUserId));
+
+ // -- 先计算下要循环几次 - 一个用户一个部门一个考勤组一个班次一个日期 最大的打卡次数 -
+ Map> map = list.stream().collect(Collectors.groupingBy(
+ a -> a.getUserId() + "_"
+ + a.getDeptId() + "_"
+ + a.getAttendanceGroupId() + "_"
+ + a.getAttendanceGroupShiftId() + "_"
+ + a.getDayTime()));
+
+ int maxSize = map.values().stream().mapToInt(List::size).max().orElse(0);
+ // -- 如果膜2大于0
+ maxSize = (maxSize % 2) > 0 ? maxSize / 2 + 1 : maxSize / 2;
+
+ //先根据人员分组 - 再根据部门分组 - 再根据考勤组分组 - 再根据日期分组
+ long time = System.currentTimeMillis();
+ for (Map.Entry> entry : userPunchMap.entrySet()) {
+ AdminUserDO adminUserDO = userMap.get(entry.getKey());
+ List postNames = this.getPostNames(adminUserDO, postMap);
+
+
+ Map> deptMap = entry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDeptId));
+ for (Map.Entry> deptEntry : deptMap.entrySet()) {
+ Map> groupMap = deptEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupId));
+
+ for (Map.Entry> groupEntry : groupMap.entrySet()) {
+ //按日期分组
+ Map> dayMap = groupEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDayTime, TreeMap::new, Collectors.toList()));
+
+ for (String dateStr : dateList) {
+ List items = dayMap.get(dateStr);
+ Map> groupShiftMap = new HashMap<>();
+ if (CollectionUtil.isNotEmpty(items)) {
+ groupShiftMap = items.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftId));
+ } else {
+ groupShiftMap.put(-1L, Collections.emptyList());
+ }
+ for (Map.Entry> groupShiftEntry : groupShiftMap.entrySet()) {
+ List row = new ArrayList<>();
+ row.add(adminUserDO.getNickname());
+ row.add(groupEntry.getValue().get(0).getAttendanceGroupName());
+ row.add(groupEntry.getValue().get(0).getDeptName());
+ row.add(String.join("/", postNames));
+ row.add(dateStr);
+ row.add(CollectionUtil.isEmpty(groupShiftEntry.getValue()) ? "休息" : groupShiftEntry.getValue().get(0).getAttendanceGroupShiftName());
+ Map> workMap = groupShiftEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftItemId, TreeMap::new, Collectors.toList()));
+ // TODO: 2024/7/2 这里可能会有排序问题 具体看数据在调试
+ for (Map.Entry> groupShiftItemEntry : workMap.entrySet()) {
+ for (AttendancePunchRecordDO attendancePunchRecordDO : groupShiftItemEntry.getValue()) {
+ row.add(attendancePunchRecordDO.getPunchTime() == null ? "/" : attendancePunchRecordDO.getPunchTime().format(Constants.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND));
+ row.add(this.statusToStr(attendancePunchRecordDO.getStatus()));
+ }
+ }
+ if (maxSize > workMap.entrySet().size()) {
+ for (int i = 0; i < maxSize - workMap.entrySet().size(); i++) {
+ row.add("/");
+ row.add("/");
+ row.add("/");
+ row.add("/");
+ }
+ }
+
+ //工时
+ String workHour = this.calculateAverageWorkingHour(workMap);
+ row.add(workHour);
+ //迟到时长
+ String beLate = this.calculateBeLate(workMap);
+ row.add(beLate);
+ //早退时长
+ String leaveEarly = this.calculateEarlyDepartures(workMap);
+ row.add(leaveEarly);
+ Map missingCardsMap = this.calculateCommuteMissingCardsList(workMap);
+ row.add(missingCardsMap.get(Constants.ZERO).toString());
+ row.add(missingCardsMap.get(Constants.ONE).toString());
+ data.add(row);
+ }
+ }
+ }
+ }
+ }
+ log.info("考勤按日统计耗时:{}", (System.currentTimeMillis() - time));
+ // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
+
+ try {
+ EasyExcel.write(response.getOutputStream())
+ .head(generateDailyHead(headTitle, detailedHead, maxSize))
+ .autoCloseStream(false)
+ .excelType(ExcelTypeEnum.XLS)
+ .registerWriteHandler(new CustomCellStyleHandler())
+ .sheet("考勤统计按日导出")
+ .doWrite(data);
+ response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
+ response.setContentType("application/vnd.ms-excel;charset=UTF-8");
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /**
+ * 获取岗位名称
+ *
+ * @param adminUserDO
+ * @param postMap
+ * @return
+ */
+ private List getPostNames(AdminUserDO adminUserDO, Map postMap) {
+ if (adminUserDO.getPostIds() != null) {
+ return adminUserDO.getPostIds().stream()
+ .map(postMap::get)
+ .filter(Objects::nonNull)
+ .map(PostDO::getName)
+ .collect(Collectors.toList());
+ }
+ return Collections.emptyList();
+ }
+
+
+ /**
+ * 状态转换
+ *
+ * @param status
+ * @return
+ */
+ private String statusToStr(int status) {
+ if (status == 0) {
+ return "正常";
+ } else if (status == 1) {
+ return "迟到";
+ } else if (status == 2) {
+ return "早退";
+ } else if (status == 3) {
+ return "缺卡";
+ } else if (status == 4) {
+ return "未打卡";
+ } else if (status == 5) {
+ return "补卡";
+ }
+ return "";
}
/**
@@ -805,37 +990,104 @@ public class AttendanceServiceImpl implements AttendanceService {
* @param response
* @param userList
* @param dateList
+ * @param headTitle
+ * @param detailedHead
+ * @param list
*/
- private void monthlyStatistics(HttpServletResponse response, List userList, List dateList) {
- //查询出数据 -- 时间周期 - 用户列表
- List list = attendancePunchRecordMapper.selectList(new LambdaQueryWrapper()
- .in(AttendancePunchRecordDO::getUserId, userList)
- .in(AttendancePunchRecordDO::getDayTime, dateList));
+ private void monthlyStatistics(HttpServletResponse response, List userList, List dateList, String headTitle, String detailedHead, List list) {
+ List> data = new ArrayList<>();
+
// -- 根据部门分组 - 根据考勤组分组
+ Map> userPunchMap = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getUserId));
+ //获取用户列表
+ Map userMap = new HashMap<>();
+ if (CollectionUtil.isNotEmpty(userList)) {
+ userMap = userList.stream().collect(Collectors.toMap(AdminUserDO::getId, Function.identity()));
+ }
+ List userPostList = postMapper.selectList();
+ // 根据id分组
+ Map postMap = userPostList.stream().collect(Collectors.toMap(PostDO::getId, Function.identity()));
+ //先根据人员分组 - 再根据部门分组 - 再根据考勤组分组 - 再根据日期分组
+ for (Map.Entry> entry : userPunchMap.entrySet()) {
+ AdminUserDO adminUserDO = userMap.get(entry.getKey());
+ List postNames = this.getPostNames(adminUserDO, postMap);
+ Map> deptMap = entry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDeptId));
+ for (Map.Entry> deptEntry : deptMap.entrySet()) {
+ Map> groupMap = deptEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupId));
+
+ for (Map.Entry> groupEntry : groupMap.entrySet()) {
+ CalculateNum calculateNum = new CalculateNum();
+ //按日期分组
+ Map> dayMap = groupEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDayTime));
+ for (Map.Entry> dayEntry : dayMap.entrySet()) {
+ // -- 按照班次子表分组
+ Map> workMap = dayEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftItemId));
+ this.calculateAverageWorkingHour(workMap, calculateNum);
+ // -- 出考勤天数
+ this.calculateAttendanceDays(dayEntry.getValue(), calculateNum);
+ // -- 迟到
+ this.calculateBeLate(workMap, calculateNum);
+ // -- 早退
+ this.calculateEarlyDepartures(workMap, calculateNum);
+ // -- 缺卡
+ this.calculateCommuteMissingCardsList(workMap, calculateNum);
+ // -- 旷工
+ this.calculateMiner(dayEntry.getValue(), calculateNum);
+ }
+ //休息天数
+ this.calculateRestDay(dateList, dayMap.keySet().stream().distinct().collect(Collectors.toList()), calculateNum);
+ //考勤总时间中文
+ calculateNum.setTotalWorkingHoursStr(DateUtil.formatBetween(calculateNum.getTotalWorkingHours(), BetweenFormatter.Level.MINUTE));
+ //迟到总时间中文
+ calculateNum.setTotalLateArrivalsTimeStr(DateUtil.formatBetween(calculateNum.getTotalLateArrivalsTime(), BetweenFormatter.Level.MINUTE));
+ //早退总时间中文
+ calculateNum.setTotalEarlyDeparturesTimeStr(DateUtil.formatBetween(calculateNum.getTotalEarlyDeparturesTime(), BetweenFormatter.Level.MINUTE));
+
+ List row = new ArrayList<>();
+ row.add(adminUserDO.getNickname());
+ row.add(groupEntry.getValue().get(0).getAttendanceGroupName());
+ row.add(groupEntry.getValue().get(0).getDeptName());
+ row.add(String.join("/", postNames));
+ row.add(String.valueOf(calculateNum.getTotalAttendanceDays()));
+ row.add(String.valueOf(calculateNum.getTotalRestDays()));
+ row.add(String.valueOf(calculateNum.getTotalWorkingHoursStr()));
+ row.add(String.valueOf(calculateNum.getTotalLateArrivalsNumber()));
+ row.add(String.valueOf(calculateNum.getTotalLateArrivalsTimeStr()));
+
+ row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesNumber()));
+ row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesTimeStr()));
+ row.add(String.valueOf(calculateNum.getTotalUpMissingCardsNumber()));
+ row.add(String.valueOf(calculateNum.getTotalDownMissingCardsNumber()));
+ row.add(String.valueOf(calculateNum.getTotalMinerDays()));
+ data.add(row);
+ }
+ }
+ }
+
+ try {
+ EasyExcel.write(response.getOutputStream())
+ .head(generateHead(headTitle, detailedHead))
+ .autoCloseStream(false)
+ .excelType(ExcelTypeEnum.XLS)
+ .registerWriteHandler(new CustomCellStyleHandler())
+ .sheet("考勤统计按月导出")
+ .doWrite(data);
+ response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
+ response.setContentType("application/vnd.ms-excel;charset=UTF-8");
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
- public static void main(String[] args) {
- String fileName = "/Users/aikai/Downloads/" + System.currentTimeMillis() + ".xlsx";
- // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
- EasyExcel.write(fileName)
- .head(generateHead())
- .registerWriteHandler(new CustomCellStyleHandler())
-// .registerWriteHandler(new LoopMergeStrategy())
-// .registerWriteHandler(loopMergeStrategy)
- .sheet("模板")
- .doWrite(generateData());
- }
- public static List> generateHead() {
+ public static List> generateHead(String headTitle, String detailedHead) {
List> head = new ArrayList<>();
- String headTitle = "月度汇总 统计日期:2024-06-01 至 2024-06-13";
- String detailedHead = "月度汇总 统计日期:2024-06-01 至 2024-06-13 10:48";
head.add(Arrays.asList(headTitle, detailedHead, "姓名", "姓名"));
head.add(Arrays.asList(headTitle, detailedHead, "考勤组", "考勤组"));
head.add(Arrays.asList(headTitle, detailedHead, "部门", "部门"));
- head.add(Arrays.asList(headTitle, detailedHead, "工号", "工号"));
head.add(Arrays.asList(headTitle, detailedHead, "职位", "职位"));
head.add(Arrays.asList(headTitle, detailedHead, "出勤天数", "出勤天数"));
head.add(Arrays.asList(headTitle, detailedHead, "休息天数", "休息天数"));
@@ -853,70 +1105,48 @@ public class AttendanceServiceImpl implements AttendanceService {
// head.add(Arrays.asList(headTitle, detailedHead, "加班时长-按加班规则计算", "工作日加班"));
// head.add(Arrays.asList(headTitle, detailedHead, "加班时长-按加班规则计算", "休息日加班"));
// head.add(Arrays.asList(headTitle, detailedHead, "加班时长-按加班规则计算", "节假日加班"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "六"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "日"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "1"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "2"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "3"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "4"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "5"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "六"));
- head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "日"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "六"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "日"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "1"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "2"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "3"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "4"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "5"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "六"));
+// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "日"));
// 添加更多表头
return head;
}
- public static List> generateData() {
- List> data = new ArrayList<>();
-
- List