This commit is contained in:
zt
2025-12-23 20:09:21 +08:00
parent c713058dd3
commit 74c71a5612
10 changed files with 209 additions and 36 deletions

View File

@ -17,12 +17,14 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.company.domain.bo.CompanyAttendanceRecordBo;
import org.dromara.company.domain.vo.CompanyAttendanceRecordVo;
import org.dromara.company.domain.vo.CompanyMonthAttendanceVo;
import org.dromara.company.service.ICompanyAttendanceRecordService;
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -66,4 +68,20 @@ public class CompanyAttendanceRecordAppController extends BaseController {
return companyAttendanceRecordService.queryAbnormalListPageList(userId, pageQuery);
}
/**
* 当天打卡信息
*/
@GetMapping("/todayInfo")
public R<List<CompanyAttendanceRecordVo>> todayInfo(Long userId) {
return R.ok(companyAttendanceRecordService.todayInfo(userId));
}
/**
* 月份打卡信息
*/
@GetMapping("/monthInfo")
public R<List<CompanyMonthAttendanceVo>> monthInfo(Long userId, LocalDate month) {
return R.ok(companyAttendanceRecordService.monthInfo(userId,month));
}
}

View File

@ -95,5 +95,10 @@ public class CompanyLeaveVo implements Serializable {
@ExcelProperty(value = "备注")
private String remark;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,39 @@
package org.dromara.company.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.project.domain.BusAttendance;
import org.dromara.project.domain.vo.BusAttendanceVo;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.List;
/**
* 考勤视图对象 bus_attendance
*
* @author Lion Li
* @date 2025-08-05
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusAttendance.class)
public class CompanyMonthAttendanceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 打卡日期
*/
private LocalDate clockDate;
/**
* 考勤记录
*/
private List<CompanyAttendanceRecordVo> list;
}

View File

@ -10,6 +10,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.company.domain.vo.CompanyMonthAttendanceVo;
import org.dromara.company.domain.vo.DayRecordCountVo;
import org.dromara.company.domain.vo.DayRecordVo;
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
@ -137,4 +138,15 @@ public interface ICompanyAttendanceRecordService extends IService<CompanyAttenda
* 未处理异常列表
*/
TableDataInfo<CompanyAttendanceRecordVo> queryAbnormalListPageList(Long userId, PageQuery pageQuery);
/**
* 当天打卡信息
*/
List<CompanyAttendanceRecordVo> todayInfo(Long userId);
/**
* 月份打卡信息
*/
List<CompanyMonthAttendanceVo> monthInfo(Long userId, LocalDate month);
}

View File

@ -19,6 +19,7 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.utils.JtsUtil;
import org.dromara.company.domain.*;
import org.dromara.company.domain.dto.RecordCountDto;
import org.dromara.company.domain.vo.CompanyMonthAttendanceVo;
import org.dromara.company.domain.vo.DayRecordCountVo;
import org.dromara.company.domain.vo.DayRecordVo;
import org.dromara.company.service.*;
@ -28,6 +29,7 @@ import org.dromara.contractor.service.ISubConstructionUserService;
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
import org.dromara.project.domain.vo.BusMonthAttendanceVo;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.domain.vo.SysOssVo;
@ -278,13 +280,20 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
uploadFacePic(file, newInRecord);
return this.save(newInRecord);
} else {
CompanyAttendanceRecord newOutRecord = buildPunchRecord(userId, companyId, deptId, nickname, source, req,
attendanceDate, punchTime, rule, BusAttendanceCommuterEnum.CLOCKOUT);
// 处理早退逻辑
fillEarlyLeaveStatus(newOutRecord, attendanceDate, punchTime, rule);
// 上传人脸照片
uploadFacePic(file, newOutRecord);
return this.save(newOutRecord);
if(CollectionUtil.isEmpty(outPunchRecords)){
CompanyAttendanceRecord newOutRecord = buildPunchRecord(userId, companyId, deptId, nickname, source, req,
attendanceDate, punchTime, rule, BusAttendanceCommuterEnum.CLOCKOUT);
// 处理早退逻辑
fillEarlyLeaveStatus(newOutRecord, attendanceDate, punchTime, rule);
// 上传人脸照片
uploadFacePic(file, newOutRecord);
return this.save(newOutRecord);
}else {
CompanyAttendanceRecord existOutRecord = outPunchRecords.getFirst();
existOutRecord.setClockTime(punchTime);
fillEarlyLeaveStatus(existOutRecord, attendanceDate, punchTime, rule);
return this.updateById(existOutRecord);
}
}
}
// 下班打卡逻辑
@ -414,8 +423,8 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
, Long userId, CompanyAttendanceRule rule) {
String verifyType = rule.getVerifyType();
//人脸校验
if ( CompanyConstant.VERIFY_TYPE_FACE.equals(verifyType)) {
if (constructionUserService.faceComparison(file, userId)) {
if (CompanyConstant.VERIFY_TYPE_FACE.equals(verifyType)) {
if (!constructionUserService.faceComparison(file, userId)) {
throw new ServiceException("人脸校验失败");
}
}
@ -455,21 +464,21 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
return false;
}
boolean res = false;
for (CompanyAttendanceWifi white: list ){
for (CompanyAttendanceWifi white : list) {
String s = WifiValidationUtil.normalizeSsid(white.getWifiName());
String s1 = WifiValidationUtil.extractBssidPrefix(white.getWifiMark());
boolean equals = s.equals(normalizedSsid);
boolean equals1 = s1.equals(requestBssidPrefix);
if(equals && equals1){
if (equals && equals1) {
res = true;
}
}
// 4. 模糊匹配SSID精确匹配 + BSSID前缀匹配
return res;
} else if ( CompanyConstant.CLOCK_TYPE_RANGE.equals(clockType)) { //电子围栏校验
} else if (CompanyConstant.CLOCK_TYPE_RANGE.equals(clockType)) { //电子围栏校验
//获取范围
List<CompanyAttendanceRange> list = rangeService.lambdaQuery().eq(CompanyAttendanceRange::getId, ruleId).list();
List<CompanyAttendanceRange> list = rangeService.lambdaQuery().eq(CompanyAttendanceRange::getRuleId, ruleId).list();
List<String> punchRangeList = list.stream().map(CompanyAttendanceRange::getPunchRange).toList();
List<GeoPoint> matchingRange = JtsUtil.findMatchingRange(req.getLat(), req.getLng(), punchRangeList);
return matchingRange != null;
@ -654,7 +663,7 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
List<Long> list1 = list.stream().map(CompanyAttendanceRule::getId).toList();
if (!list1.isEmpty()) {
List<CompanyAttendanceRuleDept> list2 = ruleDeptService.lambdaQuery()
.eq(CompanyAttendanceRuleDept::getRuleId, list1).list();
.in(CompanyAttendanceRuleDept::getRuleId, list1).list();
List<Long> list3 = list2.stream().map(CompanyAttendanceRuleDept::getDeptId).toList();
if (!list3.isEmpty()) {
List<SysUserVo> sysUserVos = userService.selectUserListByDeptList(list3);
@ -707,7 +716,7 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
dayRecordVo.setDeptId(sysUserVo.getDeptId());
dayRecordVo.setUserId(sysUserVo.getUserId());
dayRecordVo.setUsername(sysUserVo.getNickName());
List<CompanyAttendanceRecord> recordList1 = map.getOrDefault(sysUserVo.getUserId(),new ArrayList<>());
List<CompanyAttendanceRecord> recordList1 = map.getOrDefault(sysUserVo.getUserId(), new ArrayList<>());
for (CompanyAttendanceRecord record : recordList1) {
if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(record.getClockType())) {
dayRecordVo.setClockInTime(record.getClockTime());
@ -813,9 +822,9 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
}
}
Map<String, Integer> map = new LinkedHashMap<>();
map.put(BusAttendanceClockStatusEnum.LATE.getText(),late);
map.put(BusAttendanceClockStatusEnum.UNCLOCK.getText(),absenteeism);
map.put(BusAttendanceClockStatusEnum.LEAVEEARLY.getText(),earlyLeave);
map.put(BusAttendanceClockStatusEnum.LATE.getText(), late);
map.put(BusAttendanceClockStatusEnum.UNCLOCK.getText(), absenteeism);
map.put(BusAttendanceClockStatusEnum.LEAVEEARLY.getText(), earlyLeave);
return map;
}
@ -829,7 +838,7 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
LocalDate monthEndDate = date.with(TemporalAdjusters.lastDayOfMonth());
LambdaQueryWrapper<CompanyAttendanceRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.between(CompanyAttendanceRecord::getClockDate,monthStartDate,monthEndDate);
queryWrapper.between(CompanyAttendanceRecord::getClockDate, monthStartDate, monthEndDate);
queryWrapper.eq(CompanyAttendanceRecord::getCompanyId, dto.getCompanyId());
List<CompanyAttendanceRecord> recordList = baseMapper.selectList(queryWrapper);
@ -839,18 +848,18 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
Map<LocalDate, BigDecimal> map = new LinkedHashMap<>();
LocalDate currentDate = monthStartDate;
while (!currentDate.isAfter(monthEndDate)){
while (!currentDate.isAfter(monthEndDate)) {
//出勤人数
List<CompanyAttendanceRecord> recordList1 = recordMap.get(currentDate);
long attendance = 0;
long total = 0;
if (CollectionUtil.isNotEmpty(recordList1)){
if (CollectionUtil.isNotEmpty(recordList1)) {
attendance = recordList1.stream().filter(e -> BusAttendanceClockStatusEnum.ATTENDANCE_LIST.contains(e.getClockStatus()))
.map(CompanyAttendanceRecord::getUserId).distinct().count();
//总人数
total = recordList1.stream().map(CompanyAttendanceRecord::getUserId).distinct().count();
if(currentDate.isEqual(LocalDate.now())){
if (currentDate.isEqual(LocalDate.now())) {
List<CompanyAttendanceRule> list = ruleService.lambdaQuery().eq(CompanyAttendanceRule::getCompanyId, dto.getCompanyId()).list();
List<Long> list1 = list.stream().map(CompanyAttendanceRule::getId).toList();
if (!list1.isEmpty()) {
@ -866,32 +875,62 @@ public class CompanyAttendanceRecordServiceImpl extends ServiceImpl<CompanyAtten
}
//计算
BigDecimal rate = BigDecimal.ZERO ;
BigDecimal rate = BigDecimal.ZERO;
if (total > 0) {
rate = new BigDecimal(attendance).divide(new BigDecimal(total), 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
}
map.put(currentDate,rate);
currentDate=currentDate.plusDays(1);
map.put(currentDate, rate);
currentDate = currentDate.plusDays(1);
}
return map;
}
@Override
public List<CompanyAttendanceRecordVo> abnormalList(Long userId) {
return List.of();
}
@Override
public TableDataInfo<CompanyAttendanceRecordVo> queryAbnormalListPageList(Long userId, PageQuery pageQuery) {
LambdaQueryWrapper<CompanyAttendanceRecord> lqw = Wrappers.lambdaQuery();
lqw.eq(CompanyAttendanceRecord::getUserId,userId);
lqw.eq(CompanyAttendanceRecord::getHandle,"0");
lqw.in(CompanyAttendanceRecord::getClockStatus,BusAttendanceClockStatusEnum.ABNORMAL_LIST);
lqw.eq(CompanyAttendanceRecord::getUserId, userId);
lqw.eq(CompanyAttendanceRecord::getHandle, "0");
lqw.in(CompanyAttendanceRecord::getClockStatus, BusAttendanceClockStatusEnum.ABNORMAL_LIST);
lqw.orderByDesc(CompanyAttendanceRecord::getClockDate);
lqw.orderByAsc(CompanyAttendanceRecord::getClockType);
Page<CompanyAttendanceRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@Override
public List<CompanyAttendanceRecordVo> todayInfo(Long userId) {
LambdaQueryWrapper<CompanyAttendanceRecord> lqw = Wrappers.lambdaQuery();
lqw.eq(CompanyAttendanceRecord::getUserId, userId);
lqw.eq(CompanyAttendanceRecord::getClockDate, LocalDate.now());
return baseMapper.selectVoList(lqw);
}
@Override
public List<CompanyMonthAttendanceVo> monthInfo(Long userId, LocalDate month) {
LocalDate start = month.with(TemporalAdjusters.firstDayOfMonth());
LocalDate end = month.with(TemporalAdjusters.lastDayOfMonth());
LambdaQueryWrapper<CompanyAttendanceRecord> lqw = Wrappers.lambdaQuery();
lqw.eq(CompanyAttendanceRecord::getUserId, userId);
lqw.between(CompanyAttendanceRecord::getClockDate, start,end);
List<CompanyAttendanceRecordVo> list = baseMapper.selectVoList(lqw);
Map<LocalDate, List<CompanyAttendanceRecordVo>> map = list.stream().collect(Collectors.groupingBy(CompanyAttendanceRecordVo::getClockDate));
List<CompanyMonthAttendanceVo> result = new ArrayList<>();
LocalDate currentDate = start;
while (!currentDate.isAfter(end)) {
CompanyMonthAttendanceVo vo = new CompanyMonthAttendanceVo();
vo.setClockDate(currentDate);
vo.setList(map.getOrDefault(currentDate, Collections.emptyList()));
result.add(vo);
currentDate = currentDate.plusDays(1);
}
return result;
}
}

View File

@ -535,11 +535,30 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
@CacheEvict(cacheNames = CacheNames.SYS_NICKNAME, key = "#user.userId")
@Transactional(rollbackFor = Exception.class)
public int updateUser(SysUserBo user) {
SysUser sysUser1 = baseMapper.selectById(user.getUserId());
String appUserType = sysUser1.getAppUserType();
if("0".equals(appUserType) || "2".equals(appUserType)){
int size = user.getProjectRoles().size();
if(size > 1){
throw new ServiceException("分包和施工只能关联一个项目");
}
SubConstructionUser bySysUserId = constructionUserService.getBySysUserId(user.getUserId());
if(bySysUserId == null){
throw new ServiceException("请先实名");
}
if(bySysUserId.getProjectId() == null){
throw new ServiceException("请先关联项目");
}
Long projectId = user.getProjectRoles().getFirst().getProjectId();
if(!Objects.equals(bySysUserId.getProjectId(), projectId)){
throw new ServiceException("与现有项目不匹配");
}
}
// 新增用户与角色管理
insertUserRole(user, true);
// 新增用户与岗位管理
insertUserPost(user, true);
/* // 获取旧的系统用户
/* // 获取旧的系统用户
SysUser oldUser = baseMapper.selectById(user.getUserId());*/
SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
// 防止错误更新后导致的数据误删除

View File

@ -55,4 +55,9 @@ public class FlowInstanceBo implements Serializable {
*/
private List<Long> createByIds;
/**
* 筛选 1-考勤
*/
private String type;
}

View File

@ -60,4 +60,8 @@ public class FlowTaskBo implements Serializable {
*/
private String isRead;
/**
* 筛选 1-考勤
*/
private String type;
}

View File

@ -2,6 +2,7 @@ package org.dromara.workflow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -267,11 +268,11 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
@Override
public TableDataInfo<FlowInstanceVo> selectCurrentInstanceList(FlowInstanceBo instanceBo, PageQuery pageQuery) {
QueryWrapper<FlowInstanceBo> queryWrapper = buildQueryWrapper(instanceBo);
if (!"0".equals(instanceBo.getProjectId())){
List<Long> definitionIds = new ArrayList<>();
if (!"0".equals(instanceBo.getProjectId()) && instanceBo.getProjectId() != null ){
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(StringUtils.isNotBlank(instanceBo.getProjectId()),FlowDefinition::getFlowCode, instanceBo.getProjectId()));
List<Long> definitionIds = new ArrayList<>();
if (flowDefinitions != null && !flowDefinitions.isEmpty()) {
flowDefinitions.forEach(flowDefinition -> {
definitionIds.add(flowDefinition.getId());
@ -280,8 +281,18 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
if (definitionIds.isEmpty()) {
return null;
}
queryWrapper.in("fi.definition_id",definitionIds);
}
if("1".equals(instanceBo.getType())){
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(FlowDefinition::getFlowCode, "company"));
if (flowDefinitions != null && !flowDefinitions.isEmpty()) {
flowDefinitions.forEach(flowDefinition -> {
definitionIds.add(flowDefinition.getId());
});
}
}
queryWrapper.in(CollectionUtil.isNotEmpty(definitionIds),"fi.definition_id",definitionIds);
queryWrapper.eq("fi.create_by", LoginHelper.getUserIdStr());
Page<FlowInstanceVo> page = flwInstanceMapper.selectInstanceList(pageQuery.build(), queryWrapper);
return TableDataInfo.build(page);

View File

@ -276,7 +276,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
public TableDataInfo<FlowTaskVo> pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
List<Long> definitionIds = new ArrayList<>();
if (!"0".equals(flowTaskBo.getProjectId())) {
if (!"0".equals(flowTaskBo.getProjectId()) && flowTaskBo.getProjectId() != null ) {
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(StringUtils.isNotBlank(flowTaskBo.getProjectId()), FlowDefinition::getFlowCode, flowTaskBo.getProjectId()));
@ -289,6 +289,17 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
return null;
}
}
if("1".equals(flowTaskBo.getType())){
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(FlowDefinition::getFlowCode, "company"));
if (flowDefinitions != null && !flowDefinitions.isEmpty()) {
flowDefinitions.forEach(flowDefinition -> {
definitionIds.add(flowDefinition.getId());
});
}
}
queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr());
queryWrapper.in("t.flow_status", BusinessStatusEnum.WAITING.getStatus());
@ -306,7 +317,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
public TableDataInfo<FlowHisTaskVo> pageByTaskFinish(FlowTaskBo flowTaskBo, PageQuery pageQuery) {
QueryWrapper<FlowTaskBo> queryWrapper = buildQueryWrapper(flowTaskBo);
List<Long> definitionIds = new ArrayList<>();
if (!"0".equals(flowTaskBo.getProjectId())) {
if (!"0".equals(flowTaskBo.getProjectId()) && flowTaskBo.getProjectId() != null ) {
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(StringUtils.isNotBlank(flowTaskBo.getProjectId()),FlowDefinition::getFlowCode, flowTaskBo.getProjectId()));
@ -319,6 +330,16 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
return null;
}
}
if("1".equals(flowTaskBo.getType())){
List<FlowDefinition> flowDefinitions = flowDefinitionMapper.selectList(new LambdaQueryWrapper<FlowDefinition>()
.select(FlowDefinition::getId)
.like(FlowDefinition::getFlowCode, "company"));
if (flowDefinitions != null && !flowDefinitions.isEmpty()) {
flowDefinitions.forEach(flowDefinition -> {
definitionIds.add(flowDefinition.getId());
});
}
}
queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey());
queryWrapper.in("t.approver", LoginHelper.getUserIdStr());
queryWrapper.orderByDesc("t.create_time").orderByDesc("t.update_time");