commit
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled

This commit is contained in:
2026-06-05 16:18:40 +08:00
parent 1ca34c6bb2
commit 3cba3bb74e
4393 changed files with 450030 additions and 103 deletions

View File

@@ -0,0 +1,333 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-ftb</artifactId>
<version>3.4.7-RELEASE</version>
</parent>
<artifactId>jnpf-ftb-biz</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.85.Final</version>
</dependency>
<dependency>
<groupId>com.github.tencentyun</groupId>
<artifactId>tls-sig-api-v2</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.0</version>
</dependency>
<!--MQTT使用包-->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-ftb-entity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-ftb-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-file-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-websocket-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-provider-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-permission-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-permission-entity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-office</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-file</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-connector</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-sms</artifactId>
</dependency>
<!-- data模块 -->
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-database</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-springaop</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-visualdev-onlinedev-biz</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-visualdev-base-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-visualdev-onlinedev-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-workflow-engine-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-system-entity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-example-biz</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.19.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-attence-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>cn.6tail</groupId>
<artifactId>lunar</artifactId>
<version>1.3.15</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-message-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.972</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-ocr</artifactId>
<version>3.1.965</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-spring-boot-starter</artifactId>
<version>3.7.2</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-im-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-salary-api</artifactId>
<version>3.4.7-RELEASE</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-contract-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-common-seata</artifactId>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-file-storage</artifactId>
<version>3.4.7-RELEASE</version>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-oauth-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-tenant-entity</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>jnpf-memoo-share-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.project.model</groupId>
<artifactId>seeta-sdk-platform</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.fantaibao</groupId>
<artifactId>jnpf-common-permission</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fantaibao</groupId>
<artifactId>fantaibao-data-analysis-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jnpf</groupId>
<artifactId>fantaibao-patrol-store-api</artifactId>
<version>3.4.7-RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>jnpf-ftb-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>jnpf.JnpfFtbApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,24 @@
package jnpf;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 翻台宝服务启动类
*
* @author yanwenfu
* @create 2023-07-11
*/
@SpringBootApplication
@EnableFeignClients
@Slf4j
public class JnpfFtbApplication {
public static void main(String[] args) {
long start = System.currentTimeMillis();
SpringApplication.run(JnpfFtbApplication.class, args);
System.out.println("翻台宝服务启动成功,耗时:" + (System.currentTimeMillis() - start) + "毫秒");
}
}

View File

@@ -0,0 +1,98 @@
package jnpf.aspect;
import jnpf.attendance.mapper.ApiCallLogMapper;
import jnpf.entity.ApiCallLog;
import jnpf.util.DateUtil;
import jnpf.util.FtbUtil;
import jnpf.util.JsonUtil;
import jnpf.util.UserProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 接口调用记录切面
*
* @author yanwenfu
* @create 2026-01-28
*/
@Aspect
@Component
@Slf4j
public class ApiCallLogAspect {
@Resource
private ApiCallLogMapper apiCallLogMapper;
@Around("@annotation(apiCallLog)")
public Object around(ProceedingJoinPoint joinPoint, FtbApiCallLog apiCallLog) throws Throwable {
long startTime = System.currentTimeMillis();
ApiCallLog logEntity = new ApiCallLog();
HttpServletRequest request = null;
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
request = attributes.getRequest();
}
// ========= 基础信息 =========
logEntity.setId(FtbUtil.getId());
logEntity.setApiName(apiCallLog.name());
logEntity.setDeleteMark(0);
logEntity.setCreatorTime(DateUtil.getNowDate());
logEntity.setCreatorUserId(UserProvider.getLoginUserId());
logEntity.setTenantId(UserProvider.getUser().getTenantId());
if (request != null) {
logEntity.setApiPath(request.getRequestURI());
logEntity.setHttpMethod(request.getMethod());
logEntity.setIp(getIp(request));
} else {
logEntity.setApiPath("UNKNOWN");
logEntity.setHttpMethod("UNKNOWN");
logEntity.setIp("UNKNOWN");
}
// ========= 请求参数 =========
try {
logEntity.setRequestBody(JsonUtil.getObjectToString(joinPoint.getArgs()));
} catch (Exception e) {
logEntity.setRequestBody("请求参数序列化失败");
}
Object result;
try {
result = joinPoint.proceed();
logEntity.setSuccess(1);
try {
logEntity.setResponseBody(JsonUtil.getObjectToString(result));
} catch (Exception e) {
logEntity.setResponseBody("返回结果序列化失败");
}
return result;
} catch (Throwable ex) {
logEntity.setSuccess(0);
logEntity.setErrorMsg(ex.getMessage());
throw ex;
} finally {
logEntity.setCostTime((int) (System.currentTimeMillis() - startTime));
try {
apiCallLogMapper.insert(logEntity);
} catch (Exception e) {
log.error("接口调用日志入库失败", e);
}
}
}
private String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isBlank(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}

View File

@@ -0,0 +1,12 @@
package jnpf.aspect;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FtbApiCallLog {
/** 接口名称 */
String name();
}

View File

@@ -0,0 +1,18 @@
package jnpf.attendance.annotation;
import jnpf.enums.attendance.ActionEnum;
import jnpf.enums.attendance.MachineEnum;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Machine {
/** 操作动作(打卡, 下发人员, 删除人员) */
ActionEnum dealAction();
/** 厂商 */
MachineEnum factory();
}

View File

@@ -0,0 +1,230 @@
package jnpf.attendance.annotation;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import jnpf.attendance.mapper.AttendanceGroupMapper;
import jnpf.attendance.mapper.AttendanceMachineLogMapper;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.base.UserInfo;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AttendanceMachineLog;
import jnpf.enums.attendance.ActionEnum;
import jnpf.enums.attendance.MachineEnum;
import jnpf.model.attendance.vo.GroupInfoVo;
import jnpf.model.attendance.vo.attendance.UserTenantVo;
import jnpf.util.ConstantUtil;
import jnpf.util.DateUtil;
import jnpf.util.FtbUtil;
import jnpf.util.JsonUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 考勤机切面
*
* @author yanwenfu
* @create 2024-07-30
*/
@Aspect
@Component
@RefreshScope
public class MachineAspect {
@Value("${machine-log}")
private Integer machineLog;
@Resource
private AttendanceMachineLogMapper attendanceMachineLogMapper;
@Resource
private AttendanceUserService attendanceUserService;
@Resource
private AttendanceGroupMapper attendanceGroupMapper;
private static final ThreadLocal<AttendanceMachineLog> threadLocal = new ThreadLocal<>();
@Pointcut("@annotation(jnpf.attendance.annotation.Machine)")
public void machineLog() {
}
@Around(value = "machineLog()")
public Object generateLog(ProceedingJoinPoint point) throws Throwable {
if (null == machineLog || machineLog.equals(ConstantUtil.NUM_FALSE)) {
return point.proceed();
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Machine annotation = method.getAnnotation(Machine.class);
Object[] args = point.getArgs();
ActionEnum actionEnum = annotation.dealAction();
MachineEnum machineEnum = annotation.factory();
JSONArray array = new JSONArray();
AttendanceMachineLog machineLog = new AttendanceMachineLog();
if (null != args && args.length > 0) {
String str;
if (actionEnum.equals(ActionEnum.DA_KA)) {
str = "arg[0]";
String paramStr = args[0].toString();
if (StringUtils.isNotEmpty(paramStr)) {
paramStr = paramStr.replaceAll("checkPic=[^,]+,", "");
paramStr = paramStr.replaceAll("\"photo\":\"[^\"]*\",", "");
}
if (MachineEnum.KE_MI.equals(machineEnum)) {
paramStr = JsonUtil.getObjectToString(args[0]);
}
MutablePair<String, String> pair = this.getUserInfo(paramStr, machineEnum);
if (StringUtils.isNotEmpty(pair.getLeft())) {
String[] split = pair.getLeft().split("@");
String tenantId = split[0];
String userId = split[1];
// 查询当前考勤组信息
UserInfo userInfo = new UserInfo();
userInfo.setTenantId(tenantId);
userInfo.setUserId(userId);
GroupInfoVo group = null;
try {
List<AttendanceGroupUser> groupList = attendanceUserService.getAttendanceGroupUsersOfSecondment(DateUtil.getNowDate(), DateUtil.getNowDate(), List.of(userId), null, false);
if (null != groupList && !groupList.isEmpty()) {
String groupName = attendanceGroupMapper.getGroupName(groupList.get(0).getGroupId());
group = new GroupInfoVo(groupList.get(0).getGroupId(), groupName);
}
} catch (Exception e) {
e.printStackTrace();
}
machineLog.setUserId(userId);
machineLog.setUserName(pair.getRight());
if (null != group) {
machineLog.setGroupId(group.getGroupId());
machineLog.setGroupName(group.getGroupName());
}
}
JSONObject json = new JSONObject();
json.set(str, paramStr);
array.add(json);
} else {
for (int i = 0; i < args.length; i++) {
str = "arg[" + i + "]";
JSONObject json = new JSONObject();
try {
if (args[i] != null) {
if (i == 0) {
JSONObject arg = new JSONObject(args[i]);
json.set(str, arg.get("userName"));
arg.clear();
} else {
json.set(str, args[i].toString());
}
} else {
json.set(str, "null");
}
} catch (Exception e) {
json.set(str, e.getMessage());
}
array.add(json);
}
}
}
machineLog.setId(FtbUtil.getId());
machineLog.setFactoryCode(machineEnum.getValue());
machineLog.setAction(actionEnum.getDescription());
machineLog.setParamJson(array.toString());
machineLog.setCreatorTime(new Date());
threadLocal.set(machineLog);
return point.proceed();
}
private MutablePair<String, String> getUserInfo(String paramStr, MachineEnum machineEnum) {
String userId = null;
String userName = null;
String regexUserId;
String regexUserName;
switch (machineEnum) {
case KAI_JIA_YI:
regexUserId = "\\\"userId\\\":\\\"([^\\\"]*)\\\"";
regexUserName = "name=(.*?)(,|\\})";
String regexTenantId = "\\\"tenantId\\\":\\\"([^\\\"]*)\\\"";
Matcher kjyMatcher = Pattern.compile(regexUserId + "|" + regexUserName + "|" + regexTenantId).matcher(paramStr);
String tenantId = "";
while (kjyMatcher.find()) {
if (kjyMatcher.group(1) != null) {
userId = kjyMatcher.group(1);
}
if (kjyMatcher.group(2) != null) {
userName = kjyMatcher.group(2);
}
if (kjyMatcher.group(4) != null) {
tenantId = kjyMatcher.group(4);
}
}
userId = tenantId + "@" + userId;
break;
case MAO_TONG:
regexUserId = "user_id=(.*?)(?=,)";
regexUserName = "user_name=(.*?)(?=,)";
Matcher matcher = Pattern.compile(regexUserId + "|" + regexUserName).matcher(paramStr);
while (matcher.find()) {
if (matcher.group(1) != null) {
userId = matcher.group(1);
}
if (matcher.group(2) != null) {
userName = matcher.group(2);
}
}
break;
case KE_MI:
UserTenantVo userTenant = JsonUtil.getJsonToBean(paramStr, UserTenantVo.class);
userId = userTenant.getTenantId() + "@" + userTenant.getUserId();
userName = userTenant.getUserName();
default:
break;
}
return MutablePair.of(userId, userName);
}
@AfterReturning(pointcut = "machineLog()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
if (null == machineLog || machineLog.equals(ConstantUtil.NUM_FALSE)) {
return;
}
// 这里可以访问目标方法的返回值
AttendanceMachineLog machineLog = threadLocal.get();
if (null != machineLog) {
machineLog.setMethodResult(null == result ? "无返回值" : result.toString());
attendanceMachineLogMapper.insert(machineLog);
threadLocal.remove();
}
}
@AfterThrowing(pointcut = "machineLog()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable ex) {
if (null == machineLog || machineLog.equals(ConstantUtil.NUM_FALSE)) {
return;
}
// 这里可以访问目标方法抛出的异常
AttendanceMachineLog machineLog = threadLocal.get();
if (null != machineLog) {
machineLog.setMethodResult(ex.getMessage());
attendanceMachineLogMapper.insert(machineLog);
threadLocal.remove();
}
}
}

View File

@@ -0,0 +1,307 @@
package jnpf.attendance.antifreeze;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import jnpf.authority.FtbAuthorityApi;
import jnpf.base.ActionResult;
import jnpf.file.FileUploadApi;
import jnpf.model.personnels.dto.staff.roster.FtbPersonnelsStaffRosterDto;
import jnpf.model.personnels.dto.staff.roster.WorkerGroupDataDto;
import jnpf.model.personnels.req.roster.StaffRosterListReq;
import jnpf.permission.V2UserApi;
import jnpf.permission.entity.UserEntity;
import jnpf.permission.eum.v2.UserWorkStatusEnums;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.permission.vo.v2.user.UserBoundInfoVO;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.personnels.FtbPersonnelsRosterManagerApi;
import jnpf.util.StringUtil;
import jnpf.util.UploaderUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Slf4j
public class UserAntifreeze {
@Resource
private V2UserApi v2UserApi;
@Resource
private FileUploadApi fileUploadApi;
@Resource
private FtbAuthorityApi ftbAuthorityApi;
@Resource
private FtbPersonnelsRosterManagerApi managerApi;
/**
* 请求用户服务接口
*
* @param userIds 用户ID集合
* @return 用户信息
*/
public List<PartUserInfoVo> getInfoByIds(List<String> userIds, String tenantId) {
List<PartUserInfoVo> infoByIds = CollUtil.newArrayList();
try {
ActionResult<List<UserBoundVO>> userList = v2UserApi.getAllUserInfoBatch(userIds, tenantId);
if (200 == userList.getCode() && CollectionUtil.isNotEmpty(userList.getData())) {
infoByIds = getPartUserInfoVos(userList.getData());
}
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
/**
* 请求用户服务接口
*
* @param orgId 组织ID
* @param workGroupId 班组ID
* @return 用户信息
*/
public List<UserBoundVO> getInfoByIds(String orgId, String workGroupId) {
List<UserBoundVO> userBoundVoList = ftbAuthorityApi.listTargetOrganizeIdAuthApi(orgId,
List.of(UserWorkStatusEnums.NONE));
if (StringUtil.isEmpty(workGroupId)) {
return userBoundVoList;
}
return userBoundVoList.stream().filter(vo -> StringUtil.equals(vo.getStoreTeamId(), workGroupId)).collect(Collectors.toList());
}
/**
* 请求用户服务接口
*
* @param orgIds 组织ID集合
* @param workStatusEnums 用户工作状态
* @return 用户信息
*/
public List<UserBoundVO> batchGetInfoByIds(List<String> orgIds, List<UserWorkStatusEnums> workStatusEnums) {
return ftbAuthorityApi.listTargetOrganizeIdsAuthApi(orgIds,
UserWorkStatusEnums.getDifferenceEnumsFromBase(workStatusEnums));
}
/**
* 请求用户服务接口
*
* @param userIds 用户ID集合
* @return 用户信息
*/
public List<PartUserInfoVo> getStaffRosterListInfoByIds(List<String> userIds) {
List<PartUserInfoVo> infoByIds = CollUtil.newArrayList();
try {
StaffRosterListReq req = new StaffRosterListReq();
req.setUserIds(userIds);
req.setIsQueryAuth("0");
List<FtbPersonnelsStaffRosterDto> userBoundVos = managerApi.postWithSalaryNoPage(req);
infoByIds = getStaffRosterVos(userBoundVos);
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
private List<PartUserInfoVo> getStaffRosterVos(List<FtbPersonnelsStaffRosterDto> userBoundVos) {
List<PartUserInfoVo> infoByIds;
if (CollUtil.isEmpty(userBoundVos)) {
return CollUtil.newArrayList();
}
infoByIds = userBoundVos.stream().map(x -> {
PartUserInfoVo bean = BeanUtil.toBean(x, PartUserInfoVo.class);
bean.setUserId(x.getUserId());
bean.setRealName(StringUtil.isNotEmpty(x.getName()) ? x.getName() : "");
bean.setMobilePhone(StringUtil.isNotEmpty(x.getPhone()) ? x.getPhone() : "");
bean.setWorkerStatus(Objects.nonNull(x.getWorkerStatus()) ? x.getWorkerStatus() : "");
bean.setHeadIcon(StringUtil.isNotEmpty(x.getHeadLogo()) ? fileUploadApi.getHeadIcon(UploaderUtil.uploaderImg(x.getHeadLogo())) : "");
if(CollUtil.isNotEmpty(x.getOrgList())){
WorkerGroupDataDto groupDataDto = x.getOrgList().stream().findFirst().orElse(null);
if(Objects.nonNull(groupDataDto)){
bean.setOrganizeName(groupDataDto.getAffiliatedOrgName());
bean.setOrganizeId(groupDataDto.getAffiliatedOrg());
bean.setPositionId(groupDataDto.getAffiliatedPosition());
bean.setPositionName(groupDataDto.getAffiliatedPositionName());
}
// Optional.ofNullable(groupDataDto).ifPresent(groupDataDto1 -> {
// bean.setOrganizeName(groupDataDto1.getAffiliatedOrgName());
// bean.setOrganizeId(groupDataDto1.getAffiliatedOrg());
// bean.setPositionId(groupDataDto1.getAffiliatedPosition());
// bean.setPositionName(groupDataDto1.getAffiliatedPositionName());
// });
}
return bean;
}).collect(Collectors.toList());
return infoByIds;
}
/**
* 请求用户服务接口
*
* @param userIds 用户ID集合
* @return 用户信息
*/
public List<PartUserInfoVo> getInfoByIdsManyAndCopyPost(List<String> userIds, String tenantId) {
List<PartUserInfoVo> infoByIds = CollUtil.newArrayList();
try {
List<UserBoundVO> userBoundVos = v2UserApi.userListAndCopy(userIds, null, tenantId);
infoByIds = getPartUserInfoVos(userBoundVos);
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
/**
* 请求用户服务接口
*
* @param userIds 用户ID集合
* @return 用户信息
*/
public List<PartUserInfoVo> getAllByIds(List<String> userIds, String tenantId) {
List<PartUserInfoVo> infoByIds = CollUtil.newArrayList();
try {
ActionResult<List<UserBoundVO>> userList = v2UserApi.getAllUserInfoBatch(userIds, tenantId);
if (200 == userList.getCode() && CollectionUtil.isNotEmpty(userList.getData())) {
infoByIds = getPartUserInfoVos(userList.getData());
}
if (CollUtil.isEmpty(infoByIds)) {
List<UserBoundVO> userBoundVos = v2UserApi.userListAndCopy(userIds, true, tenantId);
infoByIds = getPartUserInfoVos(userBoundVos);
return infoByIds;
}
if (infoByIds.size() < userIds.size()) {
List<String> collect = infoByIds.stream().map(PartUserInfoVo::getUserId).collect(Collectors.toList());
userIds = userIds.stream().filter(userId -> !collect.contains(userId)).collect(Collectors.toList());
if (CollUtil.isNotEmpty(userIds)) {
List<UserBoundVO> userBoundVos = v2UserApi.userListAndCopy(userIds, true, tenantId);
List<PartUserInfoVo> partUserInfoVos = getPartUserInfoVos(userBoundVos);
infoByIds.addAll(partUserInfoVos);
}
}
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
@NotNull
private List<PartUserInfoVo> getPartUserInfoVos(List<UserBoundVO> userList) {
List<PartUserInfoVo> infoByIds;
if (CollUtil.isEmpty(userList)) {
return CollUtil.newArrayList();
}
infoByIds = userList.stream().map(x -> {
PartUserInfoVo bean = BeanUtil.toBean(x, PartUserInfoVo.class);
bean.setUserId(x.getId());
bean.setRealName(StringUtil.isNotEmpty(x.getUserName()) ? x.getUserName() : "");
bean.setMobilePhone(StringUtil.isNotEmpty(x.getPhone()) ? x.getPhone() : "");
bean.setWorkerStatus(Objects.nonNull(x.getWorkStatusEnums()) ? x.getWorkStatusEnums().getCode() : "");
bean.setHeadIcon(StringUtil.isNotEmpty(x.getHeadIcon()) ? fileUploadApi.getHeadIcon(UploaderUtil.uploaderImg(x.getHeadIcon())) : "");
return bean;
}).collect(Collectors.toList());
return infoByIds;
}
@NotNull
private List<UserEntity> getUserInfoVos(List<UserBoundVO> userList) {
if (CollUtil.isEmpty(userList)) {
return CollUtil.newArrayList();
}
return userList.stream().map(x -> {
UserEntity bean = BeanUtil.toBean(x, UserEntity.class);
bean.setId(x.getId());
bean.setRealName(x.getUserName());
bean.setMobilePhone(x.getPhone());
bean.setHeadIcon(fileUploadApi.getHeadIcon(UploaderUtil.uploaderImg(x.getHeadIcon())));
return bean;
}).collect(Collectors.toList());
}
/**
* 请求用户服务接口
*
* @param userId 用户ID
* @return 用户信息
*/
public PartUserInfoVo getInfo(String userId, String tenantId) {
try {
List<PartUserInfoVo> infoByIds = getAllByIds(CollUtil.newArrayList(userId), tenantId);
if (CollUtil.isEmpty(infoByIds)) {
return null;
}
return infoByIds.get(0);
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return null;
}
/**
* 请求用户服务接口
*
* @param userIds 用户ID集合
* @return 用户信息
*/
public List<UserEntity> getInfoByIds(List<String> userIds) {
List<UserEntity> infoByIds = CollUtil.newArrayList();
try {
List<UserBoundVO> userBoundVos = v2UserApi.userListAndCopy(userIds, false, null);
infoByIds = getUserInfoVos(userBoundVos);
if (CollUtil.isEmpty(infoByIds)) {
return CollUtil.newArrayList();
}
infoByIds.stream().filter(Objects::nonNull).filter(user -> StringUtil.isNotEmpty(user.getHeadIcon())).forEach(user -> user.setHeadIcon(fileUploadApi.getHeadIcon(UploaderUtil.uploaderImg(user.getHeadIcon()))));
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
/**
* 请求用户服务接口
*
* @param userId 用户ID
* @return 用户信息
*/
public UserEntity getInfo(String userId) {
try {
ActionResult<UserBoundInfoVO> usersBound = v2UserApi.getUsersBound(userId, null);
if (200 == usersBound.getCode() && null != usersBound.getData()) {
UserEntity infoById = BeanUtil.copyProperties(usersBound.getData(), UserEntity.class);
infoById.setRealName(usersBound.getData().getUserName());
return infoById;
}
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return null;
}
/**
* 请求用户服务接口
*
* @param keyword 关键字
* @return 用户信息
*/
public List<UserEntity> getInfoByLikeName(String keyword) {
List<UserEntity> infoByIds = CollUtil.newArrayList();
if (StringUtil.isEmpty(keyword)) {
return infoByIds;
}
try {
List<UserBoundVO> userBoundVos = v2UserApi.userListAndCopyLikeName(keyword, null, null);
infoByIds = getUserInfoVos(userBoundVos);
} catch (Exception e) {
log.error("用户接口请求失败:", e);
}
return infoByIds;
}
}

View File

@@ -0,0 +1,51 @@
package jnpf.attendance.bean;
import jnpf.enums.attendance.ClockInStatusEnum;
import jnpf.util.ConstantUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 变更配置
*
* @author yanwenfu
* @create 2024-11-11
*/
@Configuration
@Getter
@Setter
public class ChangeConfig {
private Map<Integer, Map<ClockInStatusEnum, List<ClockInStatusEnum>>> changeMap = new HashMap<>();
public ChangeConfig() {
changeMap.put(ConstantUtil.ON_WORK, getOnWorkMap());
changeMap.put(ConstantUtil.OFF_WORK, getOffWorkMap());
}
private Map<ClockInStatusEnum, List<ClockInStatusEnum>> getOnWorkMap() {
Map<ClockInStatusEnum, List<ClockInStatusEnum>> map = new HashMap<>();
map.put(ClockInStatusEnum.NORMAL, List.of(ClockInStatusEnum.WORK_LATE, ClockInStatusEnum.NO_CLOCK));
map.put(ClockInStatusEnum.WORK_LATE, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.WORK_LATE, ClockInStatusEnum.NO_CLOCK));
map.put(ClockInStatusEnum.NO_CLOCK, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.WORK_LATE));
map.put(ClockInStatusEnum.ABSENCE, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.WORK_LATE));
return map;
}
private Map<ClockInStatusEnum, List<ClockInStatusEnum>> getOffWorkMap() {
Map<ClockInStatusEnum, List<ClockInStatusEnum>> map = new HashMap<>();
map.put(ClockInStatusEnum.NORMAL, List.of(ClockInStatusEnum.HOME_EARLY, ClockInStatusEnum.NO_CLOCK));
map.put(ClockInStatusEnum.HOME_EARLY, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.HOME_EARLY, ClockInStatusEnum.NO_CLOCK));
map.put(ClockInStatusEnum.NO_CLOCK, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.HOME_EARLY));
map.put(ClockInStatusEnum.ABSENCE, List.of(ClockInStatusEnum.NORMAL, ClockInStatusEnum.HOME_EARLY));
return map;
}
}

View File

@@ -0,0 +1,160 @@
package jnpf.attendance.bean;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 线程池配置类
* 根据任务类型CPU密集、IO密集、混合创建合适的线程池。
*/
@Configuration
public class FtbThreadPoolExecutor {
// 队列边界
private static final int QUEUE_CAPACITY = 200;
// 线程存活时间 s(秒)
private static final long KEEP_ALIVE_TIME = 15L;
private static final String THREAD_NAME_PREFIX = "FTB服务-考勤模块";
@Bean(name = "attendanceRuleThreadPool", destroyMethod = "shutdown")
public ThreadPoolExecutor attendanceRuleThreadPool() {
return createNamedThreadPool("attendance-rule", TaskType.CPU_INTENSIVE);
}
@Bean(name = "cpuIntensiveThreadPool", destroyMethod = "shutdown")
public ThreadPoolExecutor cpuIntensiveThreadPool() {
return createThreadPool(TaskType.CPU_INTENSIVE);
}
@Bean(name = "ioIntensiveThreadPool", destroyMethod = "shutdown")
public ThreadPoolExecutor ioIntensiveThreadPool() {
return createThreadPool(TaskType.IO_INTENSIVE);
}
@Bean(name = "mixedThreadPool", destroyMethod = "shutdown")
public ThreadPoolExecutor mixedThreadPool() {
return createThreadPool(TaskType.MIXED);
}
@Bean(name = "cultivateThreadPool", destroyMethod = "shutdown")
public ThreadPoolExecutor cultivateThreadPool() {
return createNamedThreadPool("cultivate-thread-v2", TaskType.IO_INTENSIVE);
}
private ThreadPoolExecutor createNamedThreadPool(String name, TaskType type) {
int actualProcessors = Math.min(Runtime.getRuntime().availableProcessors(), 42);
int runTime, cpuTime;
switch (type) {
case CPU_INTENSIVE:
runTime = 10;
cpuTime = 1;
break;
case IO_INTENSIVE:
runTime = 5;
cpuTime = 1;
break;
case MIXED:
default:
runTime = 50;
cpuTime = 1;
break;
}
int maxPoolSize = calculateMaxPoolSize(actualProcessors, runTime, cpuTime, type);
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
return new ThreadPoolExecutor(
actualProcessors,
maxPoolSize,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
queue,
new ThreadFactory() {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = defaultFactory.newThread(r);
thread.setName("FTB服务-考勤模块-" + name + "-thread-" + count.getAndIncrement());
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
private ThreadPoolExecutor createThreadPool(TaskType type) {
// 限制为实际核心数
int actualProcessors = Math.min(Runtime.getRuntime().availableProcessors(), 42);
int runTime, cpuTime;
switch (type) {
case CPU_INTENSIVE:
runTime = 10;
cpuTime = 1;
break;
case IO_INTENSIVE:
runTime = 5;
cpuTime = 1;
break;
case MIXED:
default:
runTime = 50;
cpuTime = 1;
break;
}
int maxPoolSize = calculateMaxPoolSize(actualProcessors, runTime, cpuTime, type);
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
return new ThreadPoolExecutor(
actualProcessors,
maxPoolSize,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
queue,
new ThreadFactory() {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = defaultFactory.newThread(r);
thread.setName(THREAD_NAME_PREFIX + "-" + type.name().toLowerCase() + "-thread-" + count.getAndIncrement());
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
/**
* 根据公式:最佳线程数 = N * (1 + (WT / ST))
* 其中 WT = runTime - cpuTime即线程等待时间
*
* @param availableProcessors CPU核心数
* @param runTime 线程总运行时间(单位可自定,如毫秒)
* @param cpuTime 线程CPU计算时间
* @return 最佳线程池大小
*/
private int calculateMaxPoolSize(int availableProcessors, int runTime, int cpuTime, TaskType type) {
if (cpuTime <= 0) {
throw new IllegalArgumentException("参数不合法cpuTime 应大于 0");
}
if (type == TaskType.IO_INTENSIVE) {
// I/O 密集型4 倍核心数,上限 200
return Math.min(availableProcessors * 4, 200);
} else if (type == TaskType.CPU_INTENSIVE) {
// CPU 密集型:等于核心数
return availableProcessors;
} else {
// 混合型
long ratio = 1L + (long) runTime / cpuTime;
long calculated = (long) availableProcessors * ratio;
return (int) Math.min(calculated, 200);
}
}
public enum TaskType {
CPU_INTENSIVE,
IO_INTENSIVE,
MIXED
}
}

View File

@@ -0,0 +1,128 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AppStatisticsService;
import jnpf.base.ActionResult;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.text.ParseException;
import java.util.List;
/**
* 考勤统计APP
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/app/statistics")
public class AppStatisticsController {
@Resource
private AppStatisticsService appStatisticsService;
@Operation(summary = "我的考勤-主页")
@GetMapping(value = "/home")
public ActionResult<AppStatisticsListVo> getHomeData(@Valid AppStatisticsListDto req) throws QueryException {
try {
log.info("我的考勤-主页,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
AppStatisticsListVo listVo = appStatisticsService.getAppHomeData(req);
long ent = System.currentTimeMillis();
log.info("我的考勤-主页,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(listVo);
} catch (QueryException e) {
if (e.getMessage().contains("用户暂无考勤组")) {
return ActionResult.success(e.getMessage());
} else {
throw new QueryException(e.getMessage());
}
}
}
@Operation(summary = "我的考勤-出勤情况")
@GetMapping("/record")
public ActionResult<List<AppStatisticsRecordVo>> getRecordData(@Valid AppStatisticsRecordDto req) throws QueryException {
try {
List<AppStatisticsRecordVo> list = appStatisticsService.getRecordData(req);
return ActionResult.success(list);
} catch (Exception e) {
if (e.getMessage().contains("暂无数据") || e.getMessage().contains("用户暂无考勤组") || e.getMessage().contains("用户暂无排班")) {
return ActionResult.success("暂无数据");
} else {
throw new QueryException(e.getMessage());
}
}
}
@Operation(summary = "我的考勤-更多统计-默认")
@GetMapping("/moreDefault")
public ActionResult<AppStatisticsMoreVo> getMoreDefaultData(@Valid AppStatisticsMoreDto req) throws Exception {
log.info("我的考勤-更多统计-默认,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
AppStatisticsMoreVo moreVo = appStatisticsService.getMoreDefaultData(req);
long ent = System.currentTimeMillis();
log.info("我的考勤-更多统计-默认,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(moreVo);
}
@Operation(summary = "我的考勤-更多统计-展开")
@GetMapping("/moreExpand")
public ActionResult<AppStatisticsMoreInfoVo> getMoreExpandData(@Valid AppStatisticsMoreInfoDto req) throws Exception {
log.info("我的考勤-更多统计-展开,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
AppStatisticsMoreInfoVo moreInfoVo = appStatisticsService.getMoreExpandData(req);
long ent = System.currentTimeMillis();
log.info("我的考勤-更多统计-展开,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(moreInfoVo);
}
@Operation(summary = "团队考勤-首页")
@PostMapping("/team/home")
public ActionResult<AppStatisticsTeamListVo> getTeamHomeData(@Valid @RequestBody AppStatisticsTeamListDto req) throws QueryException {
log.info("团队考勤-首页,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
AppStatisticsTeamListVo teamListVo = appStatisticsService.getTeamHomeData(req);
long ent = System.currentTimeMillis();
log.info("团队考勤-首页,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(teamListVo);
}
@Operation(summary = "团队考勤-tab列表数据")
@PostMapping("/team/tabList")
public ActionResult<List<AppStatisticsTeamTabVo>> getTabListData(@Valid @RequestBody AppTeamStatisticsTabDto req) {
log.info("团队考勤-tab列表数据入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
List<AppStatisticsTeamTabVo> teamTabVoList = appStatisticsService.getTabListData(req);
long ent = System.currentTimeMillis();
log.info("团队考勤-tab列表数据耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(teamTabVoList);
}
@Operation(summary = "团队考勤-团队统计")
@PostMapping("/team/statistics")
public ActionResult<AppTeamStatisticsVo> getStatisticsData(@Valid @RequestBody AppTeamStatisticsDto req) throws ParseException {
log.info("团队考勤-团队统计,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
AppTeamStatisticsVo statisticsVo = appStatisticsService.getStatisticsData(req);
long ent = System.currentTimeMillis();
log.info("团队考勤-团队统计,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(statisticsVo);
}
@Operation(summary = "团队考勤-团队统计-详情列表")
@PostMapping("/team/statisticsList")
public ActionResult<List<AppTeamStatisticsListVo>> getStatisticsListData(@Valid @RequestBody AppTeamStatisticsListDto req) throws QueryException, ParseException {
log.info("团队考勤-团队统计-详情列表,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
List<AppTeamStatisticsListVo> statisticsListVoList = appStatisticsService.getStatisticsListData(req);
long ent = System.currentTimeMillis();
log.info("团队考勤-团队统计-详情列表,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(statisticsListVoList);
}
}

View File

@@ -0,0 +1,232 @@
package jnpf.attendance.controller;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jnpf.SocketApi;
import jnpf.attendance.annotation.Machine;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.AttendanceUserFaceService;
import jnpf.base.ActionResult;
import jnpf.enums.attendance.ActionEnum;
import jnpf.enums.attendance.MachineEnum;
import jnpf.model.attendance.dto.UserFaceDto;
import jnpf.model.attendance.vo.UserFaceVo;
import jnpf.util.CustomTenantUtil;
import jnpf.util.DateDetail;
import jnpf.util.NoDataSourceBind;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 考勤机控制器
*
* @author yanwenfu
* @create 2023-11-08
*/
@Slf4j
@RestController
public class AttenceMachineController {
@Resource
private AttenceMachineService attenceMachineService;
@Resource
private AttendanceUserFaceService attendanceUserFaceService;
@Autowired
private CustomTenantUtil customTenantUtil;
@Autowired
private SocketApi socketApi;
/**
* 查询用户人脸
* @param userId 用户id
* @return java.lang.Object
*/
@GetMapping(value = "/userface/{userId}")
public Object getUserFace(@PathVariable(value = "userId") String userId) {
UserFaceVo userFace = attendanceUserFaceService.getUserFace(userId);
return ActionResult.success(userFace);
}
/**
* 新增人脸
* @param userFaceDto 人脸数据
* @return java.lang.Object
*/
@PostMapping(value = "/userface")
public Object addUserFace(@RequestBody @Valid UserFaceDto userFaceDto) {
attendanceUserFaceService.addUserFace(userFaceDto);
return ActionResult.success();
}
/**
* 修改人脸
* @param userFaceDto 人脸数据
* @return java.lang.Object
*/
@PutMapping(value = "/userface")
public Object updateUserFace(@RequestBody @Valid UserFaceDto userFaceDto) {
attendanceUserFaceService.updateUserFace(userFaceDto);
return ActionResult.success();
}
/**
* 删除人脸
* @param userId 用户id
* @return java.lang.Object
*/
@DeleteMapping(value = "/userface/{userId}")
public Object deleteUserFace(@PathVariable(value = "userId") String userId) {
attendanceUserFaceService.deleteUserFace(userId);
return ActionResult.success();
}
/**
* 上传打卡记录
* @param params 打卡数据
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(value = "/record/face")
@NoDataSourceBind
// @Machine(dealAction = ActionEnum.DA_KA, factory = MachineEnum.MAO_TONG)
public Map<String, Object> uploadRecordFace(@RequestBody Map<String, Object> params) {
Map<String, Object> map = new HashMap<>();
map.put("Result", 0);
map.put("Msg", "");
return map;
}
/**
* 识别后在线验证
* @param params 打卡数据
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(value = "/verify_user")
@NoDataSourceBind
@Machine(dealAction = ActionEnum.DA_KA, factory = MachineEnum.MAO_TONG)
public Map<String, Object> verifyUser(@RequestBody Map<String, Object> params) {
log.info("MaoTong-Clock: {}", params);
Map<String, Object> map = new HashMap<>();
Object sn = params.get("sn");
if (null == sn) {
map.put("Result", 3);
map.put("Msg", "设备id不能为空");
return map;
}
Object userIdObj = params.get("user_id");
if (null == userIdObj || StringUtils.isEmpty(userIdObj.toString())) {
map.put("Result", 3);
map.put("Msg", sn + "打卡用户不能为空");
return map;
}
log.error("开始打卡,设备号:{},用户:{}", sn, userIdObj);
String[] split = userIdObj.toString().split("@");
String tenantId = split[0];
String userId = split[1];
customTenantUtil.checkOutTenant(tenantId, userId);
String b = attenceMachineService.clockIn(sn.toString(), userId, tenantId);
String message;
switch (b) {
case "0":
message = "已记录";
break;
case "1":
message = "打卡成功";
break;
case "2":
message = "迟到";
break;
case "3":
message = "早退";
break;
default:
message = b;
break;
}
map.put("Result", 0);
map.put("Msg", message);
JSONObject json = new JSONObject();
json.set("voice_code", -2);
json.set("voice_text", message);
map.put("Content", json);
return map;
}
/**
* 陌生人打卡
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(value = "/stranger")
@NoDataSourceBind
public Map<String, Object> strangerClockIn(@RequestBody Map<String, Object> params) {
String sn = params.get("sn").toString();
log.error("陌生人打卡,设备号:{},打卡时间:{}", sn, DateDetail.getDateTime2Str(new Date()));
Map<String, Object> map = new HashMap<>();
map.put("Result", 0);
map.put("Msg", "");
return map;
}
/**
* 设备录入自定义编号判断
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(value = "/addFace")
@NoDataSourceBind
public Map<String, Object> addFace(@RequestBody Map<String, Object> params) {
Map<String, Object> map = new HashMap<>();
map.put("Result", 0);
map.put("Msg", "");
return map;
}
/**
* 更新用户信息
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(value = "/user/inf_photo")
@NoDataSourceBind
public Map<String, Object> updateUserInfoPhoto(@RequestBody Map<String, Object> params) {
log.error("更新用户信息...");
return attenceMachineService.updateUserInfoPhoto(params);
}
@GetMapping(value = "/sendUser")
public Object testSendUser(@RequestParam(value = "code") String code, @RequestParam(value = "userId") String userId, @RequestParam(value = "sn") String sn) {
attenceMachineService.sendUserToMachine(code, userId, sn);
return ActionResult.success();
}
@GetMapping(value = "/changeImg")
public Object testChangeImg() {
String img = attenceMachineService.changeImg();
return ActionResult.success(img);
}
@GetMapping(value = "/testSendMsg")
public void testSendMsg() {
socketApi.sendMsg2Client("kaijiayi", "123", "虚空消息...");
}
}

View File

@@ -0,0 +1,51 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceAIService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.AttendanceReqDto;
import jnpf.model.attendance.vo.attendance.ClockDataReqVo;
import jnpf.model.attendance.vo.attendance.OvertimeRuleVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 考勤AI接口支持
*
* @author yanwenfu
* @create 2026-05-07
*/
@RestController
@RequestMapping(value = "/attendance/ai")
public class AttendanceAIController {
@Resource
private AttendanceAIService attendanceAIService;
/**
* 根据日期查询考勤打卡记录
* @param dto 查询条件
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.attendance.ClockDataReqVo>
*/
@GetMapping(value = "/clock-record")
public ActionResult<ClockDataReqVo> getClockRecordByDate(AttendanceReqDto dto) {
ClockDataReqVo req = attendanceAIService.getClockRecordByDate(dto);
return ActionResult.success(req);
}
/**
* 查询考勤组加班规则
* @param groupId 考勤组id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.attendance.OvertimeRuleVo>
*/
@GetMapping(value = "/overtime-rule/{groupId}")
public ActionResult<OvertimeRuleVo> getOvertimeRule(@PathVariable(value = "groupId") String groupId) {
OvertimeRuleVo vo = attendanceAIService.getOvertimeRule(groupId);
return ActionResult.success(vo);
}
}

View File

@@ -0,0 +1,829 @@
package jnpf.attendance.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.AttendanceApi;
import jnpf.attendance.service.AttendanceApproveService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.base.vo.PageListVO;
import jnpf.config.ConfigValueUtil;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.engine.FlowTaskApi;
import jnpf.entity.attendance.AttendanceApprovalAdminVo;
import jnpf.entity.workflow.AttendanceWorkOvertimeApprove;
import jnpf.entity.workflow.SelfApprove;
import jnpf.enums.attendance.ApprovalSettingTypeEnum;
import jnpf.enums.personnel.FtbPersonnelsCheckStatusCodeEnum;
import jnpf.exception.ApproveException;
import jnpf.exception.HandleException;
import jnpf.exception.LoginException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.AttendanceToThousandsFacesVo;
import jnpf.model.attendance.vo.attendance.GroupMiniVo;
import jnpf.model.attendance.vo.attendance.LeaveConsumptionDetailVo;
import jnpf.model.attendance.vo.attendance.TimeVo;
import jnpf.model.attendance.vo.flow.HandlerVo;
import jnpf.model.doclibrary.vo.UseDetailVo;
import jnpf.model.workflow.dto.AttendanceBusinessTripApproveOaDto;
import jnpf.model.workflow.dto.AttendanceWorkOvertimeApproveDto;
import jnpf.model.workflow.dto.SelfApproveDto;
import jnpf.util.*;
import jnpf.util.data.DataSourceContextHolder;
import jnpf.workflow.service.SelfApproveService;
import jnpf.workflow.service.WorkOvertimeApproveService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;
/**
* describe
* 考勤审批相关
*
* @author HuangLinPan
* @date 2023/11/22
*/
@RestController
@RequestMapping(value = "/attendance")
@Slf4j
public class AttendanceApproveController implements AttendanceApi {
@Resource
private AttendanceApproveService attendanceApproveService;
@Autowired
private WorkOvertimeApproveService workApproveService;
@Resource
private UserProvider userProvider;
@Autowired
private ConfigValueUtil configValueUtil;
@Resource
private AttendanceGroupService attendanceGroupService;
/**
* 假期劵的使用记录
*
* @param id 劵的id
* @param balanceQueryDto 参数
* @return jnpf.base.ActionResult<java.lang.String>
* @author hlp
*/
@GetMapping("/useDetail/{id}")
public ActionResult<PageListVO<UseDetailVo>> getUseDetail(@PathVariable("id") String id, BalanceQueryDto balanceQueryDto) {
PageInfo<UseDetailVo> vo = attendanceApproveService.getUseDetail(id, balanceQueryDto);
return ActionResult.page(vo.getList(), FtbUtil.getPagination(vo));
}
/**
* 定时失效劵
*
* @param tenantId 租户id
* @return java.lang.Boolean
* @author hlp
*/
@Override
@PostMapping(value = "/invalidationCoupons")
public Boolean invalidationCoupons(@RequestParam String tenantId) {
return attendanceApproveService.invalidationCoupons(tenantId);
}
/**
* 每月定时计算用户存休
*
* @param tenantId 租户Id
*/
@Override
@PostMapping(value = "/storageRest")
public Boolean storageRest(@RequestParam(value = "tenantId") String tenantId) {
return attendanceApproveService.storageRest(tenantId);
}
/**
* 加班--开始日期选择后触发接口返回当天及后一天的排班信息
*
* @param balanceQueryDto 参数
* @return jnpf.base.ActionResult<java.util.List < jnpf.model.attendance.vo.UserClassesVo>>
* @author hlp
*/
@GetMapping("/workOverTime/getClasses")
public ActionResult<List<UserClassesVo>> getClasses(BalanceQueryDto balanceQueryDto) {
List<UserClassesVo> vo = attendanceApproveService.getClasses(balanceQueryDto);
return ActionResult.success(vo);
}
/**
* 加班--加班明细
*
* @param balanceQueryDto 参数
* @return jnpf.base.ActionResult<java.util.List < jnpf.model.attendance.vo.UserClassesVo>>
* @author hlp
*/
@GetMapping("/workOverTime/shifts")
public ActionResult<LeaveShiftVo> getWorkOverTimeShifts(BalanceQueryDto balanceQueryDto) {
try {
LeaveShiftVo vo = attendanceApproveService.getWorkOverTimeShifts(balanceQueryDto);
return ActionResult.success(vo);
} catch (Exception e) {
return ActionResult.fail(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode(), e.getMessage());
}
}
/**
* 获取请假申请时长及设计班次(请假时选择了开始和结束时间后请求该接口)
*
* @param leaveQueryDto 参数
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.LeaveShiftVo>
* @author hlp
*/
@GetMapping("/leaveDuration")
public ActionResult<LeaveShiftVo> getLeaveDuration(LeaveQueryDto leaveQueryDto) throws HandleException {
if (null == leaveQueryDto.getStartTime() || leaveQueryDto.getStartTime().isEmpty()) {
return ActionResult.fail("开始时间不能为空");
}
if (null == leaveQueryDto.getEndTime() || leaveQueryDto.getEndTime().isEmpty()) {
return ActionResult.fail("结束时间不能为空");
}
LeaveShiftVo vo = attendanceApproveService.getLeaveDuration(leaveQueryDto);
return ActionResult.success(vo);
}
/**
* 请假审批通过后的触发接口
*
* @param tenantId 租户id
* @param applyId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 1.通过 2.未通过
* @param userId 审批用户Id
*/
@GetMapping(value = "/approve/leave/pass")
@NoDataSourceBind
public void leaveApprove(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId, @RequestParam(value = "status") Integer status, @RequestParam("userId") String userId, @RequestParam("userName") String userName) throws ApproveException {
log.error("请假审批通过后的触发接口 tenantId : {},入参applyId:{}", tenantId, applyId);
checkOutTenant(tenantId);
attendanceApproveService.leaveApprove(applyId, status, tenantId, userId, userName);
}
/**
* 加班审批通过后触发接口
*
* @param tenantId 租户id
* @param applyId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 2.未通过 1.通过
* @param userId 审批用户Id
* @author hlp
*/
@GetMapping(value = "/approve/work/pass")
@NoDataSourceBind
public void workApprove(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId, @RequestParam(value = "status") Integer status, @RequestParam("userId") String userId, @RequestParam("userName") String userName) throws ApproveException {
log.error("加班审批通过后的触发接口 tenantId : {},入参applyId:{}", tenantId, applyId);
checkOutTenant(tenantId);
attendanceApproveService.workApprove(applyId, status, tenantId, userId, userName);
}
/**
* 借调审批通过后触发接口
*
* @param tenantId 租户id
* @param applyId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 2.未通过 1.通过
* @param userId 审批用户Id
* @author hlp
*/
@GetMapping(value = "/approve/self/pass")
@NoDataSourceBind
public void selfApprove(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId, @RequestParam(value = "departureTime") String departureTime, @RequestParam(value = "backTime") String backTime, @RequestParam(value = "status") Integer status, @RequestParam("userId") String userId, @RequestParam("userName") String userName) throws ApproveException, HandleException {
log.error("借调审批通过后的触发接口 tenantId : {},入参applyId:{}", tenantId, applyId);
checkOutTenant(tenantId);
attendanceApproveService.selfApprove(applyId, departureTime, backTime, status, userId, userName, tenantId);
}
/**
* 审批校验接口
*
* @param taskId 任务id
* @param type 审批类型 1.常规补卡审批 2.调整出勤结果审批 3.外勤审批 4.请假审批 5.加班审批 6.借调审批 7.外出 8.出差
* @return jnpf.base.ActionResult<java.lang.Object>
* @author hlp
*/
@PostMapping("/approvalCheck")
public ActionResult<Object> getApprovalAdmin(@RequestParam("taskId") String taskId, @RequestParam("type") String type) {
return attendanceApproveService.getApprovalAdmin(taskId, type);
}
/**
* 提交时审批校验接口
*
* @param type 审批类型 1.常规补卡审批 2.调整出勤结果审批 3.外勤审批 4.请假审批 5.加班审批 6.借调审批 7.外出 8.出差
* @return jnpf.base.ActionResult<java.lang.Object>
* @author hlp
*/
@PostMapping("/submitApprovalCheck")
public ActionResult<Object> submitValidation(@RequestParam("type") String type) {
String body = getBody(ServletUtil.getRequest());
return attendanceApproveService.submitValidation(body, type);
}
/**
* 获取考勤组审批管理员
*
* @param taskId 考勤组id
* @param type 审批类型 1.常规补卡审批 2.调整出勤结果审批 3.外勤审批 4.请假审批 5.加班审批 6.外出 7.出差 其他:借调
* @return
*/
@PostMapping("/getApprovalAdmin")
public ActionResult<HandlerVo> getApprovalAdmin(HttpServletRequest request, String taskId, String type) throws Exception {
ServletInputStream inputStream = request.getInputStream();
byte[] bytes = IOUtils.toByteArray(inputStream);
String body = new String(bytes, "UTF-8");
type = type.split("\\?")[0];
Integer typeInt = Integer.parseInt(type);
HandlerVo handlerVo = new HandlerVo();
if (ApprovalSettingTypeEnum.LEAVE.getCode().equals(typeInt)) {
/** 请假审批*/
// System.out.println("请假审批...");
AttendanceApprovalAdminVo approveUser = attendanceApproveService.getLeaveApproveUser(body);
List<AttendanceUserVo> userList = approveUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组请假审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("请假审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.OVERTIME.getCode().equals(typeInt)) {
/** 加班审批*/
// System.out.println("加班审批...");
AttendanceApprovalAdminVo approveUser = attendanceApproveService.getOvertimeApproveUser(body);
// System.out.println("approveUser:" + approveUser.toString());
if (CollectionUtil.isEmpty(approveUser.getUserList())) {
log.error("没有找到该考勤组加班审批管理员");
return ActionResult.success(handlerVo);
}
handlerVo.setHandleId(toUserIdsStr(approveUser.getUserList()));
// System.out.println("加班审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.ROUTINE.getCode().equals(typeInt)) {
/** 补卡审批*/
// System.out.println("补卡审批...");
AttendanceApprovalAdminVo approveUser = attendanceApproveService.getReplacementCardApproveUser(body);
List<AttendanceUserVo> userList = approveUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组补卡审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("补卡审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.ACTION_RESULT.getCode().equals(typeInt)) {
/** 调整出勤结果*/
// System.out.println("调整出勤结果审批...");
AttendanceApprovalAdminVo approvalUser = attendanceApproveService.getAttendanceAlter(body);
List<AttendanceUserVo> userList = approvalUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组调整出勤结果审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("调整出勤审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.OUT.getCode().equals(typeInt)) {
/** 外勤审批*/
// System.out.println("外勤审批...");
AttendanceApprovalAdminVo approvalUser = attendanceApproveService.getFieldApproval();
List<AttendanceUserVo> userList = approvalUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组外勤审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("外勤审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.GO_OUT.getCode().equals(typeInt)) {
/** 外出审批*/
AttendanceApprovalAdminVo approvalUser = attendanceApproveService.getGoOutApproval();
List<AttendanceUserVo> userList = approvalUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组外出审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("外勤审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
if (ApprovalSettingTypeEnum.BUSINESS_TRIP.getCode().equals(typeInt)) {
/** 出差审批*/
// System.out.println("出差审批...");
AttendanceApprovalAdminVo approvalUser = attendanceApproveService.getBusinessTripApproval();
List<AttendanceUserVo> userList = approvalUser.getUserList();
if (CollectionUtil.isEmpty(userList)) {
log.error("没有找到该考勤组出差审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = toUserIdsStr(userList);
handlerVo.setHandleId(idsStr);
// System.out.println("出差审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
/** 借调审批*/
// System.out.println("借调审批...");
List<String> managerIds = attendanceApproveService.secondedApproval(body);
if (CollectionUtil.isEmpty(managerIds)) {
log.error("没有找到该考勤组借调审批管理员");
return ActionResult.success(handlerVo);
}
String idsStr = CollectionUtil.join(managerIds, ",");
handlerVo.setHandleId(idsStr);
// System.out.println("借调审批-userIds:" + handlerVo);
return ActionResult.success(handlerVo);
}
/**
* 获取考勤组管理员具体信息
*
* @param type 审批类型 1.常规补卡审批 2.调整出勤结果审批 3.外勤审批 4.请假审批 5.加班审批 6.外出 7.出差 0借调
* @return ActionResult<AttendanceApprovalAdminVo>
*/
@GetMapping("/getGroupAdminInfo/{type}")
public ActionResult<AttendanceApprovalAdminVo> getGroupAdminInfo(@PathVariable("type") Integer type,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
@RequestParam(required = false) String clockInResultId,
@RequestParam(required = false) String selfGroupId
) throws HandleException {
AttendanceApprovalAdminVo approvalAdminVo = attendanceApproveService.getGroupAdminInfo(type, startTime, endTime, clockInResultId, selfGroupId);
return ActionResult.success(approvalAdminVo);
}
/**
* 返回用户id
*
* @param userVoList
* @return
*/
private String toUserIdsStr(List<AttendanceUserVo> userVoList) {
List<String> userIds = userVoList.stream().map(AttendanceUserVo::getUserId).collect(Collectors.toList());
return CollectionUtil.join(userIds, ",");
}
public void checkOutTenant(String tenantId) {
if (configValueUtil.isMultiTenancy()) {
// 判断是不是从外面直接请求
if (StringUtil.isNotEmpty(tenantId)) {
//切换成租户库
try {
TenantDataSourceUtil.switchTenant(tenantId);
} catch (LoginException e) {
throw new RuntimeException("切换租户失败");
}
} else {
UserInfo userInfo = UserProvider.getUser();
Assert.notNull(userInfo.getUserId(), "缺少租户信息");
DataSourceContextHolder.setDatasource(userInfo.getTenantId(), userInfo.getTenantDbConnectionString(), userInfo.isAssignDataSource());
}
}
}
/**
* 获取远程请求参数
*
* @return
* @throws IOException
*/
private String getBody(HttpServletRequest request) {
ServletInputStream inputStream = null;
try {
inputStream = request.getInputStream();
byte[] bytes = IOUtils.toByteArray(inputStream);
String body = new String(bytes, "UTF-8");
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取考勤组用户余额
*
* @param groupId 考勤组
* @param month 年月2024-5-13
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.UserBalanceVo>
* @author hlp
*/
@GetMapping("/userBalanceByGroupId")
public ActionResult<List<UserBalanceVo>> getBalanceDetailS(@RequestParam String groupId, @RequestParam String month) {
List<UserBalanceVo> vo = attendanceApproveService.getBalanceDetailS(groupId, month);
return ActionResult.success(vo);
}
/**
* 外出审批通/拒绝时调用接口
*
* @param tenantId 租户Id
* @param applyId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 1.通过 2.未通过
* @param userId 审批用户Id
* @throws ApproveException
*/
@GetMapping(value = "/approve/goOut/pass")
@NoDataSourceBind
public void goOutApprove(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId, @RequestParam(value = "status") Integer status, @RequestParam("userId") String userId, @RequestParam("userName") String userName) throws ApproveException {
log.error("外出审批通过后的触发接口 tenantId : {}", tenantId);
log.error("外出审批通过后的触发接口 入参applyId : {}", applyId);
checkOutTenant(tenantId);
attendanceApproveService.goOutApprove(applyId, status, tenantId, userId, userName);
}
/**
* 出差审批通/拒绝时调用接口
*
* @param tenantId 租户Id
* @param applyId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 1.通过 2.未通过
* @param userId 审批用户Id
* @throws ApproveException
*/
@GetMapping(value = "/approve/businessTrip/pass")
@NoDataSourceBind
public void businessTripApprove(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId, @RequestParam(value = "status") Integer status, @RequestParam("userId") String userId, @RequestParam("userName") String userName) throws ApproveException {
checkOutTenant(tenantId);
attendanceApproveService.businessTripApprove(applyId, status, tenantId, userId, userName);
}
/**
* 发消息通知下一节点审核人
*
* @param workflowImQueryDto 审批参数信息
*/
@GetMapping("/im")
@NoDataSourceBind
public ActionResult workIm(WorkflowImQueryDto workflowImQueryDto) {
log.error("发消息通知下一节点审核人入参: {}", workflowImQueryDto.toString());
// 因OA流调用时会等待流程完成后才会提交事务本接口会依赖于事务的提交结果所以单独开一个线程执行
new Thread(() -> {
checkOutTenant(workflowImQueryDto.getTenantId());
attendanceApproveService.sendIm(workflowImQueryDto);
}).start();
return ActionResult.success();
}
/**
* 获取用户当前时间所在考勤组的请假类型
*/
// @GetMapping("/leaveList")
// public ActionResult getLeaveList() throws HandleException {
// return attendanceApproveService.getLeaveList();
// }
/***************************************以下为考勤V1.7版本提供的新接口****************************************************/
/**
* 新版OA收拢保存考勤出差审批
*
* @param approveOaDto 出差对象
*/
@PostMapping("/businessTripForOa")
public ActionResult<Void> createBusinessTrip(@RequestBody @Valid AttendanceBusinessTripApproveOaDto approveOaDto) {
log.error("FoeOa开始出差了参数................: {}", approveOaDto);
// AttendanceBusinessTripApproveOaDto(id=null, userId=["450558716723340421"],
// departure=[{"detailAddress":"","areaLabels":"贵州省/贵阳市/市辖区","areaIds":["354094501027910","354094501027911","354094501027912"]}],
// destination=[{"detailAd"四川省/南充市/市辖区","areaIds":["354094498033736","354094499168330","354094499168331"]}],
// startTime=null, endTime=null, dayNum=null, transportationVehicles=4, reason=------------, fileJs, time=[1730390400000, 1730476800000])
return attendanceApproveService.createBusinessTrip(approveOaDto);
}
/**
* 新版OA收拢出差审批通过后触发接口
*
* @param taskId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 2.未通过 1.通过
* @author hlp
*/
@GetMapping(value = "/oaApprove/businessTrip/pass")
public ActionResult<Void> businessTripApprove(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "status") Integer status) {
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
result.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
try {
UserInfo userInfo = userProvider.get();
log.error("出差taskId:{},status:{},userInfo:{}", taskId, status, userInfo);
attendanceApproveService.businessTripApprove(taskId, status, userInfo.getTenantId(), userInfo.getUserId(), userInfo.getUserName());
} catch (ApproveException e) {
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
}
return result;
}
/**
* 通过传入的开始结束时间及单位获取时长
*
* @param dto 时长对象
*/
@GetMapping("/getDurationForOa")
public ActionResult<Void> getDurationForOa(@RequestBody DurationForOaDto dto) {
log.error("FoeOa通过传入的开始结束时间及单位获取时长了参数................: {}", dto);
return attendanceApproveService.getDurationForOa(dto);
}
/**
* 新版OA收拢保存考勤外出审批 小时外出逻辑
*
* @param dto 对象
*/
@PostMapping("/goOutForOa")
public ActionResult<Void> createGoOutForOa(@RequestBody GoOutApproveForOaDto dto) {
log.error("FoeOa开始外出了参数................: {}", dto);
return attendanceApproveService.createGoOutForOa(dto);
}
/**
* 新版OA收拢外出审批通过后触发接口 小时外出逻辑
*
* @param taskId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 2.未通过 1.通过
* @author hlp
*/
@GetMapping(value = "/oaApprove/goOut/pass")
public ActionResult<Void> goOutApproveForOa(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "status") Integer status) {
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
result.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
try {
UserInfo userInfo = userProvider.get();
log.error("外出taskId:{},status:{},userInfo:{}", taskId, status, userInfo);
attendanceApproveService.goOutApprove(taskId, status, userInfo.getTenantId(), userInfo.getUserId(), userInfo.getUserName());
} catch (ApproveException e) {
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
}
return result;
}
/**
* 新版OA收拢保存考勤请假审批
*
* @param dto 对象
*/
@PostMapping("/leaveForOa")
public ActionResult<Void> createLeaveForOa(@RequestBody LeaveApproveForOaDto dto) {
log.error("FoeOa开始请假了参数................: {}", dto);
return attendanceApproveService.createLeaveForOa(dto);
}
/**
* 请假审批通过后的触发接口
*
* @param taskId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 1.通过 2.未通过
*/
@GetMapping(value = "/oaApprove/leave/pass")
public ActionResult<Void> leaveApproveForOa(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "status") Integer status) {
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
result.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
try {
UserInfo userInfo = userProvider.get();
log.error("请假taskId:{},status:{},userInfo:{}", taskId, status, userInfo);
attendanceApproveService.leaveApprove(taskId, status, userInfo.getTenantId(), userInfo.getUserId(), userInfo.getUserName());
} catch (Exception e) {
log.error("exception", e);
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
}
log.error("请假 result:{}", result);
return result;
}
/**
* 新建加班申请
*
* @param attendanceWorkOvertimeApproveDto 表单对象
* @return java.lang.Object
*/
@PostMapping("/createWorkOvertimeForOa")
public Object createWorkOvertimeForOa(@RequestBody @Valid AttendanceWorkOvertimeApproveDto attendanceWorkOvertimeApproveDto) {
// AttendanceWorkOvertimeApproveDto(super=AttendanceBaseForOa(super=jnpf.model.workflow.dto.AttendanceWorkOvertimeApproveDto@dc44583a, taskId=623849038774196101, status=null, fFlowtaskid=null, fFlowid=null, uniqueId=null, tenantId=null, flowTitle=null, applyUser=["450558466533106821"], startTime=Wed Nov 06 14:12:00 CST 2024, endTime=Wed Nov 06 15:13:00 CST 2024, time=[1730873520000, 1730877180000], unit=null), id=null, userId=null, workDay=null, startTime=Wed Nov 06 14:12:00 CST 2024, endTime=Wed Nov 06 15:13:00 CST 2024, time=[1730873520000, 1730877180000], reason=null, flowtaskid=null, flowid=null, uniqueid=null, tenantId=null, picture=null, fileJson=null, flowTitle=null, applyUser=["450558466533106821"], applyDate=null, submitStatus=null, shiftInvolved=null, groupId=504574013792892037)
log.error("新建加班申请 参数 : {}", attendanceWorkOvertimeApproveDto);
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
result.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
String userId = userProvider.get().getUserId();
attendanceWorkOvertimeApproveDto.setUserId(userId);
attendanceWorkOvertimeApproveDto.setTenantId(userProvider.get().getTenantId());
attendanceWorkOvertimeApproveDto.setId(attendanceWorkOvertimeApproveDto.getTaskId());
AttendanceWorkOvertimeApprove entity = JsonUtil.getJsonToBean(attendanceWorkOvertimeApproveDto, AttendanceWorkOvertimeApprove.class);
// 处理加班日期
entity.setWorkDay(attendanceWorkOvertimeApproveDto.getStartTime());
try {
attendanceApproveService.submitCheckWorkForOa(attendanceWorkOvertimeApproveDto);
entity.setId(attendanceWorkOvertimeApproveDto.getTaskId());
workApproveService.saveForOa(entity);
} catch (Exception e) {
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
}
return result;
}
/**
* 加班审批通过后触发接口
*
* @param taskId 审批的唯一id 对应表f_id
* @param status 是否审核通过 0.待审核 2.未通过 1.通过
* @author hlp
*/
@GetMapping(value = "/oaApprove/work/pass")
public ActionResult workApproveForOa(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "status") Integer status) {
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
result.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
try {
UserInfo user = UserProvider.getUser();
log.error("加班审批通过后的触发接口 tenantId : {},入参applyId:{}", user.getTenantId(), taskId);
attendanceApproveService.checkWork(taskId);
attendanceApproveService.workApprove(taskId, status, user.getTenantId(), user.getUserId(), user.getUserName());
} catch (Exception e) {
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
}
return result;
}
/**
* 获取用户所选时间段所在考勤组
*/
@PostMapping("/getUserGroupByTime")
public ActionResult getUserGroupByTime(@RequestBody GroupOaDto groupOaDto) {
log.error("getUserGroupByTime groupOaDto:{}", groupOaDto.toString());
if (groupOaDto.isFromOaCondition()){
List<AttendanceGroupVo> attendanceGroupVos = attendanceGroupService.secondedApprovalGroupListNew();
return ActionResult.success(attendanceGroupVos.stream().map(vo->{
GroupMiniVo groupMiniVo = new GroupMiniVo();
groupMiniVo.setGroupId(vo.getId());
groupMiniVo.setGroupName(vo.getGroupName());
return groupMiniVo;
}).collect(Collectors.toList()));
}
if (null == groupOaDto.getUserId()) {
groupOaDto.setUserIdStr(userProvider.get().getUserId());
} else {
groupOaDto.setUserIdStr(groupOaDto.getUserId()[0]);
}
log.error("getUserGroupByTime groupOaDto:{}", groupOaDto);
return attendanceApproveService.getUserGroupByTime(groupOaDto);
}
/**
* oa收拢获取请假申请时长及设计班次(请假时选择了开始和结束时间后请求该接口)
*
* @param leaveQueryForOaDto 参数
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.LeaveShiftVo>
* @author hlp
*/
@PostMapping("/leaveDurationForOa")
public ActionResult<LeaveShiftVo> getLeaveDuration(@RequestBody LeaveQueryForOaDto leaveQueryForOaDto) throws HandleException {
log.error("leaveDurationForOa leaveQueryForOaDto:{}", leaveQueryForOaDto);
if (null == leaveQueryForOaDto.getTime() || leaveQueryForOaDto.getTime().length < 1) {
return ActionResult.fail("时间参数不能为空");
}
LeaveQueryDto leaveQueryDto = new LeaveQueryDto(leaveQueryForOaDto.getUnit(), leaveQueryForOaDto.getStartTime().getTime() + "", leaveQueryForOaDto.getEndTime().getTime() + "", leaveQueryForOaDto.getStartTimeType(), leaveQueryForOaDto.getEndTimeType(), leaveQueryForOaDto.getApplyId(), leaveQueryForOaDto.getTypeId());
log.error("leaveDurationForOa leaveQueryDto:{}", leaveQueryDto);
LeaveShiftVo vo = attendanceApproveService.getLeaveDuration(leaveQueryDto);
return ActionResult.success(vo);
}
/**
* 获取用户当前时间所在考勤组
*/
@GetMapping("/nowGroup")
public ActionResult getNowGroup() {
return attendanceApproveService.getNowGroup();
}
/**
* 提供OA使用时长计算
*
* @param time 时间数组
* @return 时长
*/
@GetMapping("/oaForTime")
public ActionResult getOaForTime(String[] time) {
Date startTime = new Date(Long.valueOf(time[0]));
Date endTime = new Date(Long.valueOf(time[1]));
// 校验结束时间不能小于开始时间
if (endTime.before(startTime)) {
return ActionResult.fail("结束时间不能小于开始时间");
}
BigDecimal totalDuration = FtbUtil.getTimeDifference(startTime, endTime);
TimeVo timeVo = new TimeVo();
timeVo.setTotalDuration(totalDuration);
return ActionResult.success(timeVo);
}
@Override
@PostMapping(value = "/attendanceToThousandsFaces")
public AttendanceToThousandsFacesVo attendanceToThousandsFaces() {
return attendanceApproveService.attendanceToThousandsFaces();
}
/**
* 选择了请假类型和请假时间后请求接口,获取用户余额使用情况:
*
* @param leaveQueryForOaDto 参数
* @return 用户剩余请假时长
*/
@PostMapping("/residueBalance")
public ActionResult getResidueBalance(@RequestBody LeaveQueryForOaDto leaveQueryForOaDto) {
log.error("leaveDurationForOa leaveQueryForOaDto:{}", leaveQueryForOaDto);
if (null == leaveQueryForOaDto.getTime() || leaveQueryForOaDto.getTime().length < 1) {
return ActionResult.fail("时间参数不能为空");
}
LeaveQueryDto leaveQueryDto = new LeaveQueryDto(leaveQueryForOaDto.getUnit(), leaveQueryForOaDto.getStartTime().getTime() + "", leaveQueryForOaDto.getEndTime().getTime() + "", leaveQueryForOaDto.getStartTimeType(), leaveQueryForOaDto.getEndTimeType(), leaveQueryForOaDto.getApplyId(), leaveQueryForOaDto.getTypeId());
if (null == leaveQueryDto.getStartTime() || leaveQueryDto.getStartTime().isEmpty()) {
return ActionResult.fail("开始时间不能为空");
}
if (null == leaveQueryDto.getEndTime() || leaveQueryDto.getEndTime().isEmpty()) {
return ActionResult.fail("结束时间不能为空");
}
if (null == leaveQueryDto.getTypeId() || leaveQueryDto.getTypeId().isEmpty()) {
return ActionResult.fail("请选择请假类型");
}
LeaveConsumptionDetailVo residueBalance = null;
try {
residueBalance = attendanceApproveService.getResidueBalance(leaveQueryDto);
} catch (Exception e) {
ActionResult<Void> result = new ActionResult<>();
result.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
result.setMsg(e.getMessage());
return result;
}
// 3天调休假+0.5天存休(剩余5.5天)
StringBuilder sb = new StringBuilder();
assert residueBalance != null;
if (residueBalance.getInputTypeBalance().compareTo(BigDecimal.ZERO) > 0) {
sb.append(residueBalance.getInputTypeBalanceStr());
}
if (residueBalance.getInputTypeBalance().compareTo(BigDecimal.ZERO) > 0 && residueBalance.getRetirementLeave().compareTo(BigDecimal.ZERO) > 0) {
sb.append("+");
}
if (residueBalance.getRetirementLeave().compareTo(BigDecimal.ZERO) > 0) {
sb.append(residueBalance.getRetirementLeaveStr());
}
if (residueBalance.getBalance().compareTo(BigDecimal.ZERO) > 0) {
sb.append("(剩余").append(residueBalance.getBalance().setScale(2, RoundingMode.HALF_UP)).append("天)");
}
return ActionResult.success(new HashMap<>(){{put("residueBalance",sb.toString());}});
}
}

View File

@@ -0,0 +1,121 @@
package jnpf.attendance.controller;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import jnpf.attendance.service.AttendanceBaseSettingService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceBaseSettingDto;
import jnpf.model.attendance.vo.AttendanceBaseSettingVo;
import jnpf.model.attendance.vo.attendance.QuickCheckInVo;
import jnpf.util.ConstantUtil;
import jnpf.util.UserProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 考勤基础设置表 前端控制器
*
* @author ahua
* @since 2023-11-29
*/
@RestController
@RequestMapping("/base/setting")
public class AttendanceBaseSettingController {
@Autowired
private AttendanceBaseSettingService attendanceBaseSettingService;
/**
* 保存考勤基础设置信息。
*
* @param attendanceBaseSettingDto 考勤基础设置数据传输对象
* @throws HandleException 处理异常
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult<Void> save(@RequestBody AttendanceBaseSettingDto attendanceBaseSettingDto) throws HandleException {
attendanceBaseSettingService.save(attendanceBaseSettingDto);
return ActionResult.success();
}
/**
* 根据考勤组ID获取单个考勤基础设置。
*
* @param groupId 考勤组ID
* @return 考勤基础设置视图对象
*/
@GetMapping("{groupId}")
public ActionResult<AttendanceBaseSettingVo> getOne(@PathVariable String groupId) {
return ActionResult.success(attendanceBaseSettingService.getOne(groupId));
}
/**
* 更改考勤基础设置的启用状态。
*
* @param groupId 考勤组ID
* @param enable 启用状态0禁用1启用
* @return 处理结果视图对象
*/
@PutMapping
public ActionResult<Void> changeStatus(@RequestParam String groupId, @RequestParam Integer enable) {
attendanceBaseSettingService.changeStatus(groupId, enable, attendanceBaseSettingService.getOne(groupId));
return ActionResult.success();
}
/**
* 查询用户外勤是否必须拍照
* @param isFromOaCondition 是否来自OA条件 是需要返回所有可能选项
* @return java.lang.Object
*/
@GetMapping(value = "/takePhoto")
public Object getTakePhotoSetting(@RequestParam(required = false) boolean isFromOaCondition) throws Exception {
if (isFromOaCondition) {
JSONArray array = new JSONArray();
JSONObject json = new JSONObject();
json.set("state", ConstantUtil.NUM_TRUE);
json.set("name", "");
array.put(json);
JSONObject json1 = new JSONObject();
json1.set("state", ConstantUtil.NUM_FALSE);
json1.set("name", "");
array.put(json1);
return ActionResult.success(array);
}
UserInfo user = UserProvider.getUser();
JSONArray shouldTakePhoto = attendanceBaseSettingService.getTakePhotoSetting(user);
return ActionResult.success(shouldTakePhoto);
}
/**
* 查询用户外勤是否必须拍照
* @return java.lang.Object
*/
@GetMapping(value = "/takePhoto2")
public Object getTakePhotoSetting2() throws Exception {
UserInfo user = UserProvider.getUser();
JSONArray shouldTakePhoto = attendanceBaseSettingService.getTakePhotoSetting(user);
return ActionResult.success(shouldTakePhoto.get(0));
}
/**
* 获取用户当前时间所处考勤组的内勤打卡基础设置。(过度使用)
*/
@GetMapping("/getNowGroupAttendancePhoto")
public ActionResult<Integer> getNowGroupAttendancePhoto() {
return ActionResult.success(attendanceBaseSettingService.getNowGroupAttendancePhoto());
}
/**
* 获取用户当前时间所处考勤组是否能快速打卡
* 和用户当前所处考勤组的基础设置中的是否需要拍照是否需要人脸识别以及App个人设置中的上下班快速打开相关
*/
@GetMapping("/quickCheckIn")
public ActionResult<QuickCheckInVo> quickCheckIn() {
return ActionResult.success(attendanceBaseSettingService.quickCheckIn());
}
}

View File

@@ -0,0 +1,305 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.AttendanceBookConfigService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.AttendanceBookConfigVo;
import jnpf.model.attendance.vo.AttendanceResultOptionGroupVo;
import jnpf.model.attendance.vo.AttendanceResultOptionVo;
import jnpf.model.attendance.vo.BookConfigCreatorVo;
import jnpf.model.attendance.vo.BookPersonnelVo;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.util.FtbUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤本配置Controller
*
* @author Generated
* @create 2026-04-15
*/
@Slf4j
@RestController
@RequestMapping("/attendance/book-config")
@Tag(name = "考勤本配置", description = "考勤本配置管理")
public class AttendanceBookConfigController {
@Resource
private AttendanceBookConfigService attendanceBookConfigService;
/**
* web-分页查询考勤本配置列表
*
* @param queryDto 查询条件
* @return 分页结果
*/
@Operation(summary = "web/app-分页查询考勤本配置列表(app用于当前登录人为负责人的考勤本列表)")
@PostMapping("/list")
public ActionResult<PageListVO<AttendanceBookConfigVo>> getList(@RequestBody AttendanceBookConfigQueryDto queryDto) {
try {
log.info("分页查询考勤本配置列表,入参=>{}", JSONObject.toJSON(queryDto));
long st = System.currentTimeMillis();
PageInfo<AttendanceBookConfigVo> pageInfo = attendanceBookConfigService.getPageList(queryDto);
long ent = System.currentTimeMillis();
log.info("分页查询考勤本配置列表,耗时=>{} 毫秒", ent - st);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
} catch (Exception e) {
log.error("分页查询考勤本配置列表异常", e);
return ActionResult.fail("查询失败:" + e.getMessage());
}
}
/**
* web-新增或更新考勤本配置
* <p>根据DTO中的id判断id为空则新增id不为空则更新</p>
*
* @param configDto 考勤本配置DTO
* @return 操作结果
*/
@Operation(summary = "web-新增或更新考勤本配置")
@PostMapping("/save-or-update")
public ActionResult saveOrUpdate(@Valid @RequestBody AttendanceBookConfigDto configDto) {
try {
String action = StringUtils.isBlank(configDto.getId()) ? "新增" : "更新";
log.info("{}考勤本配置,入参=>{}", action, JSONObject.toJSON(configDto));
long st = System.currentTimeMillis();
attendanceBookConfigService.saveOrUpdateAttendanceBookConfig(configDto);
long ent = System.currentTimeMillis();
log.info("{}考勤本配置,耗时=>{} 毫秒", action, ent - st);
return ActionResult.success(action + "成功");
} catch (HandleException e) {
log.error("保存考勤本配置业务异常", e);
return ActionResult.fail(e.getMessage());
} catch (Exception e) {
log.error("保存考勤本配置异常", e);
return ActionResult.fail("保存失败:" + e.getMessage());
}
}
/**
* web-删除考勤本配置
*
* @param id 考勤本ID
* @return 操作结果
*/
@Operation(summary = "web-删除考勤本配置")
@DeleteMapping("/{id}")
public ActionResult delete(@PathVariable String id) {
try {
log.info("删除考勤本配置id=>{}", id);
attendanceBookConfigService.deleteAttendanceBookConfig(id);
return ActionResult.success("删除成功");
} catch (Exception e) {
log.error("删除考勤本配置异常", e);
return ActionResult.fail("删除失败:" + e.getMessage());
}
}
/**
* web-更新启用状态
*
* @param id 考勤本ID
* @return 操作结果
*/
@Operation(summary = "web-更新启用状态")
@PutMapping("/{id}/status")
public ActionResult updateStatus(@PathVariable String id) {
try {
log.info("更新考勤本启用状态id=>{}", id);
attendanceBookConfigService.updateEnableStatus(id);
return ActionResult.success("状态更新成功");
} catch (HandleException e) {
log.error("更新考勤本状态业务异常", e);
return ActionResult.fail(e.getMessage());
} catch (Exception e) {
log.error("更新考勤本状态异常", e);
return ActionResult.fail("状态更新失败:" + e.getMessage());
}
}
/**
* web-获取考勤本配置详情
*
* @param id 考勤本ID
* @return 考勤本配置详情
*/
@Operation(summary = "web-获取考勤本配置详情")
@GetMapping("/{id}")
public ActionResult<AttendanceBookConfigVo> getDetail(@PathVariable String id) {
try {
log.info("获取考勤本配置详情id=>{}", id);
AttendanceBookConfigVo vo = attendanceBookConfigService.getDetail(id);
return ActionResult.success(vo);
} catch (Exception e) {
log.error("获取考勤本配置详情异常", e);
return ActionResult.fail("获取详情失败:" + e.getMessage());
}
}
/**
* app-增量添加使用范围人员
* <p>向指定考勤本配置的使用范围中增量添加用户列表(不会覆盖已有人员)</p>
*
* @param addUserDto 添加用户DTO
* @return 操作结果
*/
@Operation(summary = "app-增量添加使用范围人员")
@PostMapping("/add-scope-users")
public ActionResult addScopeUsers(@Valid @RequestBody AttendanceBookConfigAddUserDto addUserDto) {
try {
log.info("增量添加使用范围人员考勤本ID=>{}, 用户数=>{}",
addUserDto.getBookConfigId(), addUserDto.getUserIdList().size());
long st = System.currentTimeMillis();
attendanceBookConfigService.addScopeUsers(addUserDto);
long ent = System.currentTimeMillis();
log.info("增量添加使用范围人员,耗时=>{} 毫秒", ent - st);
return ActionResult.success("添加成功");
} catch (HandleException e) {
log.error("增量添加使用范围人员业务异常", e);
return ActionResult.fail(e.getMessage());
} catch (Exception e) {
log.error("增量添加使用范围人员异常", e);
return ActionResult.fail("添加失败:" + e.getMessage());
}
}
/**
* app-判断当前登录人是否为考勤本负责人
*
* @param bookConfigId 考勤本配置ID
* @return true-是负责人false-不是负责人
*/
@Operation(summary = "app-判断当前登录人是否为考勤本负责人")
@GetMapping("/{bookConfigId}/is-manager")
public ActionResult<Boolean> isCurrentUserManager(@PathVariable String bookConfigId) {
try {
log.info("判断当前登录人是否为考勤本负责人考勤本ID=>{}", bookConfigId);
boolean isManager = attendanceBookConfigService.isCurrentUserManager(bookConfigId);
return ActionResult.success(isManager);
} catch (HandleException e) {
log.error("判断负责人业务异常", e);
return ActionResult.fail(e.getMessage());
} catch (Exception e) {
log.error("判断负责人异常", e);
return ActionResult.fail("判断失败:" + e.getMessage());
}
}
/**
* web-获取指定组织集合及用户集合在当前时间下的所有涉及用户(筛选考勤本负责人)
* <p>查询这些组织和用户在当前时间的有效用户集合,返回完整的用户信息列表</p>
*
* @param dto 查询参数组织ID列表、用户ID列表
* @return 有效用户信息列表
*/
@Operation(summary = "web-获取指定组织集合及用户集合在当前时间下的所有涉及用户(筛选考勤本负责人)")
@PostMapping("/get-valid-users")
public ActionResult<List<PartUserInfoVo>> getValidUsers(@RequestBody GetValidUsersDto dto) {
try {
log.info("获取有效用户列表,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
List<PartUserInfoVo> userList = attendanceBookConfigService.getValidUsers(dto);
long ent = System.currentTimeMillis();
log.info("获取有效用户列表,耗时=>{} 毫秒,返回用户数: {}", ent - st, userList.size());
return ActionResult.success(userList);
} catch (Exception e) {
log.error("获取有效用户列表异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* app-获取考勤结果下拉列表
* <p>根据考勤本配置返回可用的考勤状态选项,包括基础考勤状态和该考勤本配置的请假类型</p>
*
* @param bookConfigId 考勤本配置ID
* @return 考勤结果下拉选项列表
*/
@Operation(summary = "app-获取考勤结果下拉列表(分组)")
@GetMapping("/{bookConfigId}/result-options")
public ActionResult<AttendanceResultOptionGroupVo> getAttendanceResultOptions(@PathVariable String bookConfigId) {
try {
log.info("获取考勤结果下拉列表分组考勤本ID=>{}", bookConfigId);
long st = System.currentTimeMillis();
AttendanceResultOptionGroupVo options = attendanceBookConfigService.getAttendanceResultOptions(bookConfigId);
long ent = System.currentTimeMillis();
log.info("获取考勤结果下拉列表(分组),耗时=>{} 毫秒", ent - st);
return ActionResult.success(options);
} catch (HandleException e) {
log.error("获取考勤结果下拉列表业务异常", e);
return ActionResult.fail(e.getMessage());
} catch (Exception e) {
log.error("获取考勤结果下拉列表异常", e);
return ActionResult.fail("获取失败:" + e.getMessage());
}
}
/**
* 需求2获取考勤本配置创建人列表
*
* @return 创建人列表
*/
@Operation(summary = "web-获取考勤本配置创建人列表")
@GetMapping("/creator-list")
public ActionResult<List<BookConfigCreatorVo>> getCreatorList() {
try {
log.info("获取考勤本配置创建人列表");
long st = System.currentTimeMillis();
List<BookConfigCreatorVo> creatorList = attendanceBookConfigService.getCreatorList();
long ent = System.currentTimeMillis();
log.info("获取考勤本配置创建人列表,耗时=>{} 毫秒,创建人数: {}", ent - st, creatorList.size());
return ActionResult.success(creatorList);
} catch (Exception e) {
log.error("获取考勤本配置创建人列表异常", e);
return ActionResult.fail("获取失败:" + e.getMessage());
}
}
/**
* 需求3获取考勤本配置人员列表
*
* @param dto 考勤本配置ID
* @return 人员列表
*/
@Operation(summary = "app-获取考勤本配置人员列表")
@PostMapping("/personnel-list")
public ActionResult<List<BookPersonnelVo>> getPersonnelList(@RequestBody BookRecordMonthListDto dto) {
try {
log.info("获取考勤本配置人员列表考勤本ID=>{}", dto.getBookId());
long st = System.currentTimeMillis();
List<BookPersonnelVo> personnelList = attendanceBookConfigService.getBookPersonnelList(dto.getBookId(),dto.getMonth());
long ent = System.currentTimeMillis();
log.info("获取考勤本配置人员列表,耗时=>{} 毫秒,人员数: {}", ent - st, personnelList.size());
return ActionResult.success(personnelList);
} catch (Exception e) {
log.error("获取考勤本配置人员列表异常", e);
return ActionResult.fail("获取失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,119 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.AttendanceBookOperationLogService;
import jnpf.base.ActionResult;
import jnpf.entity.attendance.AttendanceBookOperationLogEntity;
import jnpf.model.attendance.dto.OperationLogQueryDto;
import jnpf.model.attendance.vo.OperationLogPageVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 考勤本操作日志Controller
*
* @author Generated
* @create 2026-04-15
*/
@Slf4j
@RestController
@RequestMapping("/attendance/book-operation-log")
@Tag(name = "考勤本操作日志", description = "考勤本操作日志管理")
public class AttendanceBookOperationLogController {
@Resource
private AttendanceBookOperationLogService attendanceBookOperationLogService;
/**
* 新增操作日志
*
* @param entity 操作日志实体
* @return 操作结果
*/
@Operation(summary = "新增操作日志")
@PostMapping
public ActionResult create(@Valid @RequestBody AttendanceBookOperationLogEntity entity) {
try {
log.info("新增操作日志,入参=>{}", JSONObject.toJSON(entity));
attendanceBookOperationLogService.saveLog(entity);
return ActionResult.success("操作成功");
} catch (Exception e) {
log.error("新增操作日志异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* web-分页查询操作日志
*
* @param queryDto 查询参数
* @return 分页结果
*/
@Operation(summary = "web-分页查询操作日志")
@GetMapping("/page")
public ActionResult<Page<OperationLogPageVo>> queryPage(@Valid OperationLogQueryDto queryDto) {
try {
log.info("分页查询操作日志,入参=>{}", JSONObject.toJSON(queryDto));
long st = System.currentTimeMillis();
Page<OperationLogPageVo> result = attendanceBookOperationLogService.queryPage(queryDto);
long ent = System.currentTimeMillis();
log.info("分页查询操作日志,耗时=>{} 毫秒,返回记录数: {}", ent - st, result.getRecords().size());
return ActionResult.success(result);
} catch (Exception e) {
log.error("分页查询操作日志异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* 获取操作日志详情
*
* @param id 日志ID
* @return 操作结果
*/
@Operation(summary = "获取操作日志详情")
@GetMapping("/{id}")
public ActionResult<AttendanceBookOperationLogEntity> getById(@PathVariable String id) {
try {
log.info("获取操作日志详情id=>{}", id);
AttendanceBookOperationLogEntity entity = attendanceBookOperationLogService.getById(id);
if (entity == null) {
return ActionResult.fail("记录不存在");
}
return ActionResult.success(entity);
} catch (Exception e) {
log.error("获取操作日志详情异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* 删除操作日志
*
* @param id 日志ID
* @return 操作结果
*/
@Operation(summary = "删除操作日志")
@DeleteMapping("/{id}")
public ActionResult delete(@PathVariable String id) {
try {
log.info("删除操作日志id=>{}", id);
boolean removed = attendanceBookOperationLogService.removeById(id);
if (!removed) {
return ActionResult.fail("删除失败,记录不存在");
}
return ActionResult.success("删除成功");
} catch (Exception e) {
log.error("删除操作日志异常", e);
return ActionResult.fail(e.getMessage());
}
}
}

View File

@@ -0,0 +1,244 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.AttendanceBookRecordService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.*;
import jnpf.util.FtbUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 考勤本记录表Controller
*
* @author Generated
* @create 2026-04-15
*/
@Slf4j
@RestController
@RequestMapping("/attendance/book-record")
@Tag(name = "考勤本记录", description = "考勤本记录管理")
public class AttendanceBookRecordController {
@Resource
private AttendanceBookRecordService attendanceBookRecordService;
/**
* app-新增或修改考勤记录Upsert
* 根据考勤本ID、员工ID、考勤日期、时段类型唯一确定一条记录
* 如果记录存在则更新,不存在则新增
*
* @param dto 考勤记录DTO
* @return 操作结果包含记录ID
*/
@Operation(summary = "app-新增或修改考勤记录")
@PostMapping("/saveOrUpdate")
public ActionResult<String> saveOrUpdate(@Valid @RequestBody AttendanceBookRecordDto dto) {
try {
log.info("新增或修改考勤记录,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
String recordId = attendanceBookRecordService.saveOrUpdateRecord(dto);
long ent = System.currentTimeMillis();
log.info("新增或修改考勤记录,耗时=>{} 毫秒记录ID: {}", ent - st, recordId);
return ActionResult.success(recordId);
} catch (Exception e) {
log.error("新增或修改考勤记录异常", e);
return ActionResult.fail("系统异常,请联系管理员");
}
}
/**
* web-获取考勤本月统计数据
*
* @param req 统计请求参数
* @return 本月统计结果(按员工维度,支持分页)
*/
@Operation(summary = "web/app-获取考勤本月统计")
@PostMapping("/monthStatistics")
public ActionResult<PageListVO<BookRecordMonthStatisticsVo>> getMonthStatistics(@Valid @RequestBody BookRecordMonthStatisticsDto req) {
try {
log.info("获取考勤本月统计,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
PageInfo<BookRecordMonthStatisticsVo> pageInfo = attendanceBookRecordService.getMonthStatistics(req);
long ent = System.currentTimeMillis();
// 防止pageInfo或list为null
List<BookRecordMonthStatisticsVo> resultList = pageInfo.getList() != null ? pageInfo.getList() : new ArrayList<>();
log.info("获取考勤本月统计,耗时=>{} 毫秒,返回记录数: {}", ent - st, resultList.size());
return ActionResult.page(resultList, FtbUtil.getPagination(pageInfo));
} catch (Exception e) {
log.error("获取考勤本月统计异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* web-考勤本导入
*
* @param importDto 导入参数包含bookId、month、fileUrl
* @return 操作结果
*/
@Operation(summary = "web-考勤本导入")
@PostMapping("/import")
public ActionResult bookRecordImport(@RequestBody SchedulesImportDto importDto) {
try {
log.info("考勤本导入,入参=>{}", JSONObject.toJSON(importDto));
attendanceBookRecordService.bookRecordImport(importDto);
return ActionResult.success("导入成功");
} catch (Exception e) {
log.error("考勤本导入异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* web-考勤本导出
*
* @param bookId 考勤本ID
* @param month 月份格式yyyy-MM
*/
@Operation(summary = "web-考勤本导出")
@GetMapping("/export")
public ActionResult bookRecordExport(@RequestParam String bookId, @RequestParam String month) {
try {
log.info("考勤本导出bookId=>{}, month=>{}", bookId, month);
attendanceBookRecordService.bookRecordExport(bookId, month);
} catch (Exception e) {
log.error("考勤本导出异常", e);
throw new RuntimeException("导出失败:" + e.getMessage());
}
return ActionResult.success();
}
/**
* web-导出考勤本模板(直接流导出)
* <p>返回的Excel文件与导出接口具有相同的表头结构但不包含任何实际数据记录</p>
*
* @param bookId 考勤本ID
* @param month 月份格式yyyy-MM
* @param response HTTP响应对象
*/
@Operation(summary = "web-导出考勤本模板")
@GetMapping("/export-template")
public void exportTemplate(@RequestParam String bookId, @RequestParam String month, javax.servlet.http.HttpServletResponse response) {
try {
log.info("导出考勤本模板bookId=>{}, month=>{}", bookId, month);
attendanceBookRecordService.exportTemplate(bookId, month, response);
} catch (Exception e) {
log.error("导出考勤本模板异常", e);
throw new RuntimeException("导出模板失败:" + e.getMessage());
}
}
/**
* app-获取日考勤本列表
* <p>根据考勤本的使用范围获取人员列表,展示指定日期的考勤记录</p>
*
* @param req 查询参数考勤本ID、日期
* @return 日考勤本列表(按员工分组)
*/
@Operation(summary = "app-获取日考勤本列表")
@PostMapping("/day-list")
public ActionResult<List<BookRecordDayListVo>> getDayList(@Valid @RequestBody BookRecordDayListDto req) {
try {
log.info("获取日考勤本列表,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
List<BookRecordDayListVo> result = attendanceBookRecordService.getDayList(req);
long ent = System.currentTimeMillis();
log.info("获取日考勤本列表,耗时=>{} 毫秒,返回记录数: {}", ent - st, result.size());
return ActionResult.success(result);
} catch (Exception e) {
log.error("获取日考勤本列表异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* app-获取月考勤本列表
* <p>根据考勤本的使用范围获取人员列表,展示该月所有日期的考勤记录</p>
*
* @param req 查询参数考勤本ID、月份
* @return 月考勤本列表(按员工分组)
*/
@Operation(summary = "app-获取月考勤本列表")
@PostMapping("/month-list")
public ActionResult<List<BookRecordMonthListVo>> getMonthList(@Valid @RequestBody BookRecordMonthListDto req) {
try {
log.info("获取月考勤本列表,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
List<BookRecordMonthListVo> result = attendanceBookRecordService.getMonthList(req);
long ent = System.currentTimeMillis();
log.info("获取月考勤本列表,耗时=>{} 毫秒,返回记录数: {}", ent - st, result.size());
return ActionResult.success(result);
} catch (Exception e) {
log.error("获取月考勤本列表异常", e);
return ActionResult.fail(e.getMessage());
}
}
/**
* 需求10批量新增或修改考勤记录
*
* @param dto 批量考勤记录DTO
* @return 批量操作结果
*/
@Operation(summary = "app-批量新增或修改考勤记录")
@PostMapping("/batch-save-or-update")
public ActionResult<BatchOperationResultVo> batchSaveOrUpdate(@Valid @RequestBody BatchAttendanceBookRecordDto dto) {
try {
log.info("批量新增或修改考勤记录,记录数=>{}", dto.getRecords().size());
long st = System.currentTimeMillis();
BatchOperationResultVo result = attendanceBookRecordService.batchSaveOrUpdateRecord(dto);
long ent = System.currentTimeMillis();
log.info("批量新增或修改考勤记录,耗时=>{} 毫秒,成功: {}, 失败: {}",
ent - st, result.getSuccessCount(), result.getFailCount());
return ActionResult.success(result);
} catch (Exception e) {
log.error("批量新增或修改考勤记录异常", e);
return ActionResult.fail("系统异常,请联系管理员");
}
}
/**
* 获取假勤汇总信息
*
* @param dto 统计请求参数
* @return 假勤汇总信息
*/
@Operation(summary = "app-获取假勤汇总信息")
@PostMapping("/leave-attendance-summary")
public ActionResult<LeaveRemarkSummaryVo> getLeaveAttendanceSummary(@Valid @RequestBody LeaveRemarkSummaryDto dto) {
try {
log.info("获取假勤汇总信息,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
LeaveRemarkSummaryVo result = attendanceBookRecordService.getLeaveAttendanceSummary(dto);
long ent = System.currentTimeMillis();
log.info("获取假勤汇总信息,耗时=>{} 毫秒,备注数: {}", ent - st, result.getTotalCount());
return ActionResult.success(result);
} catch (Exception e) {
log.error("获取假勤汇总信息异常", e);
return ActionResult.fail(e.getMessage());
}
}
}

View File

@@ -0,0 +1,112 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceChangeService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.enums.personnel.FtbPersonnelsCheckStatusCodeEnum;
import jnpf.model.attendance.dto.NoApprovalDto;
import jnpf.model.attendance.vo.ChangeInfoVo;
import jnpf.util.UserProvider;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import javax.validation.Valid;
/**
* 考勤变更控制器
*
* @author yanwenfu
* @create 2024-11-08
*/
@RestController
@RequestMapping(value = "/change")
public class AttendanceChangeController {
@Resource
private AttendanceChangeService attendanceChangeService;
/**
* 被变更记录的信息
* @param clockInResultId 结果id
* @return java.lang.Object
*/
@GetMapping(value = "/info/{clockInResultId}")
public Object getChangeInfo(@PathVariable(value = "clockInResultId") String clockInResultId) throws Exception {
ChangeInfoVo info = attendanceChangeService.getChangeInfo(clockInResultId);
return ActionResult.success(info);
}
/**
* 根据考勤组id及用户id查询过去一年内的变更涉及打卡记录
* @param groupId 考勤组id
* @param userId 用户id
* @return jnpf.base.ActionResult
*/
@GetMapping(value = "/getChangeInfoList")
public ActionResult<List<ChangeInfoVo>> getChangeInfoList(@RequestParam String groupId,@RequestParam String userId) {
List<ChangeInfoVo> infos = attendanceChangeService.getChangeInfoList(groupId, userId);
return ActionResult.success(infos);
}
/**
* 出勤变更(无需审批)
* @param noApprovalDto 出勤变更参数
* @return java.lang.Object
*/
@PostMapping(value = "/noApproval")
public Object attendanceChangeNoApproval(@RequestBody @Valid NoApprovalDto noApprovalDto) throws Exception {
attendanceChangeService.attendanceChangeNoApproval(noApprovalDto);
return ActionResult.success();
}
/**
* 出勤变更(需审批)
* @param taskId 审批id
* @param passed 是否通过(0: 否, 1: 是, 2: 撤回)
* @return java.lang.Object
*/
@GetMapping(value = "/approval")
public Object attendanceChangeApproval(@RequestParam(value = "taskId") String taskId, @RequestParam(value = "passed") String passed) {
UserInfo user = UserProvider.getUser();
ActionResult<Void> actionResult = new ActionResult<>();
try {
attendanceChangeService.attendanceChangeApproval(taskId, passed, user);
} catch (Exception e) {
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
actionResult.setMsg(e.getMessage());
return actionResult;
}
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
actionResult.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
return actionResult;
}
/**
* 出勤变更撤回
* @param clockInResultId 打卡结果id
* @return java.lang.Object
*/
@PutMapping(value = "/rollback/{clockInResultId}")
public Object rollbackChange(@PathVariable(value = "clockInResultId") String clockInResultId) throws Exception {
attendanceChangeService.rollbackChange(clockInResultId);
return ActionResult.success();
}
/**
* 查询能否撤回变更
* @param clockInResultId 打卡结果id
* @return java.lang.Object
*/
@GetMapping(value = "/rollback/status")
public Object getRollbackStatus(@RequestParam(value = "clockInResultId") String clockInResultId) {
Integer status = attendanceChangeService.getRollbackStatus(clockInResultId);
return ActionResult.success(status);
}
}

View File

@@ -0,0 +1,385 @@
package jnpf.attendance.controller;
import jnpf.aspect.FtbApiCallLog;
import jnpf.attendance.FtbClockInApi;
import jnpf.attendance.service.AttendanceClockInService;
import jnpf.attendance.service.DailyRuleChangeService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.enums.personnel.FtbPersonnelsCheckStatusCodeEnum;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.ClockInDto;
import jnpf.model.attendance.dto.NoApprovalDto;
import jnpf.model.attendance.vo.*;
import jnpf.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 打卡控制器
*
* @author yanwenfu
* @create 2023-11-21
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/clockIn")
public class AttendanceClockInController implements FtbClockInApi {
@Resource
private AttendanceClockInService attendanceClockInService;
@Resource
private UserProvider userProvider;
@Resource
private CustomTenantUtil customTenantUtil;
@Resource
private DailyRuleChangeService dailyRuleChangeService;
/**
* 打卡 - 主页
* @return jnpf.base.ActionResult<java.util.List<jnpf.model.attendance.vo.GroupInfoVo>>
*/
@GetMapping(value = "/mainInfo")
public ActionResult<List<GroupInfoVo>> getClockInMainInfo(@RequestParam(required = false) String day) throws Exception {
try {
Date date = StringUtils.isEmpty(day) ? new Date() : DateDetail.getStr2Date10(day);
List<GroupInfoVo> list = attendanceClockInService.getClockInMainInfo(date, userProvider.get(), ConstantUtil.NUM_TRUE);
return ActionResult.success(list);
} catch (QueryException e) {
if (e.getMessage().equals("当前用户暂无考勤组")) {
return ActionResult.success(e.getMessage());
} else {
throw new QueryException(e.getMessage());
}
}
}
/**
* 查询考勤组审批列表
* @param queryDto 查询条件
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.GroupApprovalVo>
*/
@PostMapping(value = "/group-approval/list")
public ActionResult<List<GroupApprovalVo>> getGroupApprovalList(@RequestBody ApprovalQueryDto queryDto) {
List<GroupApprovalVo> list = attendanceClockInService.getGroupApprovalList(queryDto);
return ActionResult.success(list);
}
/**
* 打卡
* @return java.lang.Object
*/
@FtbApiCallLog(name = "考勤打卡")
@PostMapping(value = "/record")
public Object clockIn(@RequestBody @Valid ClockInDto clockInDto) throws Exception {
MutablePair<Integer, String> pair = attendanceClockInService.clockIn(clockInDto);
return ActionResult.success(pair.getLeft());
}
/**
* 更新打卡记录
* @param clockInId 打卡id
* @return java.lang.Object
*/
@PutMapping(value = "/record/{clockInId}")
public Object updateClockIn(@PathVariable(value = "clockInId") String clockInId, @RequestBody @Valid ClockInDto clockInDto) throws Exception {
MutablePair<Integer, String> pair = attendanceClockInService.updateClockIn(clockInId, clockInDto);
return ActionResult.success(pair.getLeft());
}
@PostMapping(value = "/testChangeRule")
public void changeAttendanceRule(@RequestBody List<UserDayVo> userDayList) {
UserInfo userInfo = userProvider.get();
attendanceClockInService.changeAttendanceRuleBatch(userDayList, userInfo);
}
/**
* 外勤打卡
* @param applyId 审批id
*/
@GetMapping(value = "/outside")
public Object outsideClockIn(@RequestParam(value = "taskId") String applyId, @RequestParam(value = "clockInId", required = false) String clockInId) throws Exception {
UserInfo user = userProvider.get();
ActionResult<Void> actionResult = new ActionResult<>();
try {
attendanceClockInService.outsideClockIn(applyId, user.getUserId(), clockInId);
} catch (Exception e) {
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
actionResult.setMsg(e.getMessage());
return actionResult;
}
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
actionResult.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
return actionResult;
}
/**
* 外勤打卡审批(通过/不通过/撤回)
* @param applyId 审批id
* @param passed - 是否通过(0: 否, 1: 是, 2: 撤回) <br>
*/
@GetMapping(value = "/outside/approval")
public Object approvalOutsideClockIn(@RequestParam(value = "taskId") String applyId, @RequestParam(value = "passed") String passed) throws Exception {
UserInfo userInfo = userProvider.get();
ActionResult<Void> actionResult = new ActionResult<>();
try {
attendanceClockInService.approvalOutsideClockIn(applyId, passed, userInfo);
} catch (Exception e) {
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
actionResult.setMsg(e.getMessage());
return actionResult;
}
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
actionResult.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
return actionResult;
}
/**
* 异常打卡审批(通过/不通过/撤回)
* @param applyId 审批id
* @param passed - 是否通过(0: 否, 1: 是, 2: 撤回) <br>
*/
@GetMapping(value = "/unusual-phone/approval")
public Object unusualPhoneClockIn(@RequestParam(value = "taskId") String applyId, @RequestParam(value = "passed") String passed) throws Exception {
UserInfo userInfo = userProvider.get();
ActionResult<Void> actionResult = new ActionResult<>();
try {
attendanceClockInService.approvalUnusualPhoneClockIn(applyId, passed, userInfo);
} catch (Exception e) {
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
actionResult.setMsg(e.getMessage());
return actionResult;
}
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
actionResult.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
return actionResult;
}
/**
* 生成用户补卡次数记录
* @param tenantId 租户id
* @param groupId 考勤组id
* @param userId 用id
* @param type 生成类型(1: 新增组成员, 2: 借调到新组)
* @return java.lang.Object
*/
@GetMapping(value = "/generateRepairNum/{tenantId}")
@NoDataSourceBind
public Object generateRepairNumForUser(@PathVariable(value = "tenantId") String tenantId, @RequestParam String groupId, @RequestParam String userId, @RequestParam Integer type) {
customTenantUtil.checkOutTenant(tenantId);
boolean b = attendanceClockInService.generateRepairNumForUser(groupId, userId, type);
return ActionResult.success(b);
}
/**
* 扫描是否有未执行的班次变更任务
* @return java.lang.Object
*/
@GetMapping(value = "/daily-rule-change/execute")
public Boolean dailyRuleChangeExecute() {
return dailyRuleChangeService.dailyRuleChangeExecute();
}
/**
* 补卡
* @param applyId 审批id
* @param passed 是否通过(0: 否, 1: 是, 2: 撤回)
*/
@GetMapping(value = "/repair")
public ActionResult<Void> repairClockIn(@RequestParam(value = "taskId") String applyId, @RequestParam(value = "passed") String passed) {
log.error("补卡入参 -> applyId: {}, passed: {}", applyId, passed);
UserInfo userInfo = userProvider.get();
String approveUserId = userInfo.getUserId();
ActionResult<Void> actionResult = new ActionResult<>();
try {
attendanceClockInService.repairClockIn(applyId, passed, approveUserId, userInfo.getTenantId());
} catch (Exception e) {
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_REFUSE.getCode());
actionResult.setMsg(e.getMessage());
return actionResult;
}
actionResult.setCode(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getCode());
actionResult.setMsg(FtbPersonnelsCheckStatusCodeEnum.CHECK_PASS.getMsg());
return actionResult;
}
/**
* 出勤变更
* @param tenantId 租户id
* @param applyId 申请id
* @param passed 是否通过(0: 否, 1: 是, 2: 撤回)
*/
@GetMapping(value = "/change")
@NoDataSourceBind
public void attendanceChange(@RequestParam(value = "tenantId") String tenantId, @RequestParam(value = "applyId") String applyId,
@RequestParam(value = "passed") String passed, @RequestParam(value = "approveUserId") String approveUserId) throws Exception {
log.error("出勤变更 -> tenantId: {}, applyId: {}, passed: {}", tenantId, applyId, passed);
customTenantUtil.checkOutTenant(tenantId, approveUserId);
attendanceClockInService.attendanceChange(applyId, passed, approveUserId,tenantId);
}
/**
* 出勤变更(不审批)
* @param noApprovalDto 出勤变更无需审批dto
* @return java.lang.Object
*/
@PutMapping(value = "/change/noApproval")
public Object attendanceChangeNoApproval(@RequestBody @Valid NoApprovalDto noApprovalDto) throws Exception {
attendanceClockInService.attendanceChangeNoApproval(noApprovalDto.getClockInResultId(), noApprovalDto.getChangeType());
return ActionResult.success();
}
/**
* 可选择的补卡列表
* @param userId 用户id
* @return jnpf.base.ActionResult<java.util.List<jnpf.model.attendance.vo.AbnormalClockInVo>>
*/
@GetMapping(value = "/repair/list/{userId}")
public ActionResult<List<GroupRepairVo>> getRepairList(@PathVariable(value = "userId") String userId) throws Exception {
List<GroupRepairVo> list = attendanceClockInService.getRepairList(userId);
return ActionResult.success(list);
}
/**
* 执行打卡旷工逻辑
* @return java.lang.Boolean
*/
@Override
@PostMapping(value = "/absenceRecord")
public Boolean generateFtbAbsenceRecord(@RequestParam(value = "tenantId") String tenantId) {
return attendanceClockInService.generateFtbAbsenceRecord(tenantId);
}
/**
* 生成打卡旷工任务
* @return java.lang.Boolean
*/
@Override
@PostMapping(value = "/absenceTask")
public Boolean generateAbsenceTask(@RequestParam(value = "tenantId") String tenantId) {
return attendanceClockInService.generateAbsenceTask(ConstantUtil.NUM_TRUE, tenantId);
}
/**
* 生成打卡提醒记录
* @return java.lang.Boolean
*/
@Override
@PostMapping(value = "/remindRecord")
public Boolean generateBeforeWorkRemind(@RequestParam(value = "tenantId") String tenantId) {
return attendanceClockInService.generateBeforeWorkRemind(tenantId);
}
/**
* 生成补卡次数
* @param tenantId 租户id
* @return java.lang.Boolean
*/
@Override
@PostMapping(value = "/repairNum")
public Boolean generateRepairNum(@RequestParam(value = "tenantId") String tenantId) {
return attendanceClockInService.generateRepairNum();
}
/**
* 判定连续动作(排班/旷工)
* @param tenantId 租户id
* @return java.lang.Boolean
*/
@Override
@PostMapping(value = "/continuousCheck")
public Boolean continuousCheck(@RequestParam(value = "tenantId") String tenantId) {
return attendanceClockInService.continuousCheck(tenantId);
}
/**
* 临时调用
* @return java.lang.Boolean
*/
@NoDataSourceBind
@PostMapping(value = "/repairNum/all")
public Boolean generateRepairNumAll(@RequestParam String tenantId) {
customTenantUtil.checkOutTenant(tenantId);
return attendanceClockInService.generateRepairNumAll();
}
/**
* 临时调用
*/
@PostMapping(value = "/test/{tenantId}")
public void test(@PathVariable(value = "tenantId") String tenantId) {
attendanceClockInService.generateFtbAbsenceRecord(tenantId);
}
/**
* 查询每日出勤及打卡记录
* @param userId 用户id
* @param queryDate 查询日期
* @param currentGroupId 当前考勤组
* @return jnpf.base.ActionResult<java.util.List<jnpf.model.attendance.vo.MiniGroupVo>>
*/
@GetMapping(value = "/dailyClockInRecord")
public ActionResult<List<MiniGroupVo>> getDailyClockInRecord(@RequestParam(value = "userId") String userId,
@RequestParam(value = "queryDate") String queryDate,
@RequestParam(value = "currentGroupId") String currentGroupId) {
List<MiniGroupVo> list = attendanceClockInService.getDailyClockInRecord(userId, queryDate, currentGroupId);
return ActionResult.success(list);
}
/**
* 查询每日出勤及打卡记录 - v2
* @param userId 用户id
* @param queryDate 查询日期
* @param currentGroupId 当前考勤组
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.DailyInfoVo>
*/
@GetMapping(value = "/dailyClockInRecord/v2")
public ActionResult<DailyInfoVo> getDailyClockInRecordV2(@RequestParam(value = "userId") String userId,
@RequestParam(value = "queryDate") String queryDate,
@RequestParam(value = "currentGroupId") String currentGroupId,
@RequestParam(value = "queryOldData", required = false) Integer queryOldData) throws QueryException {
queryOldData = null == queryOldData ? 0 : queryOldData;
DailyInfoVo dailyInfoVo = attendanceClockInService.getDailyClockInRecordV2(userId, queryDate, currentGroupId, queryOldData);
return ActionResult.success(dailyInfoVo);
}
/**
* 查询考勤组补卡规则
* @param groupId 考勤组id
* @return jnpf.base.ActionResult<java.lang.Boolean>
*/
@GetMapping(value = "/clockInRepair/rule/{groupId}")
public ActionResult<RepairRuleVo> getClockInRepairRule(@PathVariable(value = "groupId") String groupId) {
RepairRuleVo rule = attendanceClockInService.getClockInRepairCheck(groupId);
return ActionResult.success(rule);
}
}

View File

@@ -0,0 +1,42 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AttendanceClockInPicService;
import jnpf.base.ActionResult;
import jnpf.entity.attendance.AttendanceClockInPic;
import jnpf.model.attendance.vo.ClockInPicVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 打卡图片控制器
*
* @author shitou
* @create 2024-10-30
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/clockIn/pic")
public class AttendanceClockInPicController {
@Resource
private AttendanceClockInPicService attendanceClockInPicService;
@Operation(summary = "查询外勤打卡图片列表")
@GetMapping(value = "/list/{clockInId}")
public ActionResult<List<ClockInPicVo>> getClockInPicList(@Valid @PathVariable("clockInId") String clockInId) {
log.info("查询外勤打卡图片列表,入参=>{}", clockInId);
long st = System.currentTimeMillis();
List<ClockInPicVo> clockInPicList = attendanceClockInPicService.getClockInPicList(clockInId);
long ent = System.currentTimeMillis();
log.info("查询外勤打卡图片列表,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(clockInPicList);
}
}

View File

@@ -0,0 +1,61 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AttendanceCloudAlbumService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.CloudAlbumBatchSaveDto;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤云相册
*
* @Description: 考勤云相册控制类
* @Author: shiTou(他是小石头)
* @Date: 2024-10-30 10:26
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/cloudAlbum")
public class AttendanceCloudAlbumController {
@Resource
private AttendanceCloudAlbumService attendanceCloudAlbumService;
@SneakyThrows
@Operation(summary = "列表查询")
@GetMapping("/getDataList")
public ActionResult<List<String>> getDataList() {
long st = System.currentTimeMillis();
List<String> dataList = attendanceCloudAlbumService.getDataList();
long ent = System.currentTimeMillis();
log.info("列表查询,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(dataList);
}
@SneakyThrows
@Operation(summary = "批量保存图片")
@PostMapping("/batchSave")
public ActionResult<Boolean> batchSave(@Valid @RequestBody CloudAlbumBatchSaveDto dto) {
long st = System.currentTimeMillis();
attendanceCloudAlbumService.batchSave(dto.getPicUrlList());
long ent = System.currentTimeMillis();
log.info("批量保存图片,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success();
}
@SneakyThrows
@Operation(summary = "获取登录用户水印")
@GetMapping("/getUserWatermark")
public ActionResult<String> getUserWatermark() {
long st = System.currentTimeMillis();
String watermark = attendanceCloudAlbumService.getUserWatermark();
long ent = System.currentTimeMillis();
log.info("获取登录用户水印,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success("获取成功",watermark);
}
}

View File

@@ -0,0 +1,192 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.AttendanceConfirmApi;
import jnpf.attendance.service.AttendanceConfirmService;
import jnpf.attendance.service.AttendanceConfirmSettingService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.ConfirmPageListDto;
import jnpf.model.attendance.dto.ConfirmSettingSubmitDto;
import jnpf.model.attendance.dto.ConfirmStatisticsDto;
import jnpf.model.attendance.vo.attendance.ConfirmDetailsVo;
import jnpf.model.attendance.vo.attendance.ConfirmPageListVo;
import jnpf.model.attendance.vo.attendance.ConfirmSettingInfoVo;
import jnpf.model.attendance.vo.attendance.ConfirmStatisticsVo;
import jnpf.model.thousandsfaces.TodayWorkVo;
import jnpf.util.FtbUtil;
import jnpf.util.NoDataSourceBind;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤确认
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/confirm")
public class AttendanceConfirmController implements AttendanceConfirmApi {
@Resource
private AttendanceConfirmService attendanceConfirmService;
@Resource
private AttendanceConfirmSettingService confirmSettingService;
@SneakyThrows
@Operation(summary = "获取考勤确认设置")
@GetMapping(value = "/getConfirmSetting")
public ActionResult<ConfirmSettingInfoVo> getConfirmSetting() {
long st = System.currentTimeMillis();
ConfirmSettingInfoVo result = confirmSettingService.getConfirmSetting(Boolean.TRUE);
long ent = System.currentTimeMillis();
log.info("获取考勤确认设置,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "考勤确认设置提交")
@PostMapping(value = "/confirmSettingSubmit")
public ActionResult<Boolean> confirmSettingSubmit(@Valid @RequestBody ConfirmSettingSubmitDto dto) {
log.info("考勤确认设置提交,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
Boolean result = confirmSettingService.confirmSettingSubmit(dto);
long ent = System.currentTimeMillis();
log.info("考勤确认设置提交,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "获取考勤月份")
@GetMapping(value = "/getConfirmMonth")
public ActionResult<String> getConfirmMonth() {
long st = System.currentTimeMillis();
String result = attendanceConfirmService.getConfirmMonth();
long ent = System.currentTimeMillis();
log.info("获取考勤月份,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success("获取成功", result);
}
@SneakyThrows
@Operation(summary = "考勤确认聚合统计")
@PostMapping(value = "/getStatistics")
public ActionResult<ConfirmStatisticsVo> getStatistics(@Valid @RequestBody ConfirmStatisticsDto dto) {
log.info("考勤确认聚合统计,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
ConfirmStatisticsVo result = attendanceConfirmService.getStatistics(dto);
long ent = System.currentTimeMillis();
log.info("考勤确认聚合统计,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "考勤确认列表展示")
@PostMapping(value = "/getPageList")
public ActionResult<PageListVO<ConfirmPageListVo>> getPageList(@Valid @RequestBody ConfirmPageListDto dto) {
log.info("考勤确认列表展示,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
PageInfo<ConfirmPageListVo> page = attendanceConfirmService.getPageList(dto);
long ent = System.currentTimeMillis();
log.info("考勤确认列表展示,耗时=>{}", ent - st + " 毫秒");
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@SneakyThrows
@Operation(summary = "获取考勤确认详情")
@GetMapping(value = "/getConfirmDetails/{id}")
public ActionResult<ConfirmDetailsVo> getConfirmDetails(@PathVariable("id") String id) {
log.info("获取考勤确认详情,入参=>{}", id);
long st = System.currentTimeMillis();
ConfirmDetailsVo result = attendanceConfirmService.getConfirmDetails(id);
long ent = System.currentTimeMillis();
log.info("获取考勤确认详情,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@Override
@SneakyThrows
@Operation(summary = "自动生成考勤确认数据")
@GetMapping(value = "/autoConfirm")
public ActionResult<Boolean> autoCreateConfirm() {
long st = System.currentTimeMillis();
boolean result = attendanceConfirmService.autoCreateConfirm();
long ent = System.currentTimeMillis();
log.info("自动生成考勤确认数据,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@Override
@SneakyThrows
@Operation(summary = "逾期自动确认")
@GetMapping(value = "/confirmAutoSlippage")
public ActionResult<Boolean> confirmAutoSlippage() {
long st = System.currentTimeMillis();
boolean result = attendanceConfirmService.confirmAutoSlippage();
long ent = System.currentTimeMillis();
log.info("逾期自动确认,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@Override
@Operation(summary = "今日工作-考勤确认列表(0-待确认 1-已确认 2-已逾期)")
@GetMapping(value = "/getTodayWorkConfirmList")
public List<TodayWorkVo> getTodayWorkConfirmList() {
long st = System.currentTimeMillis();
List<TodayWorkVo> result = attendanceConfirmService.getTodayWorkConfirmList();
long ent = System.currentTimeMillis();
log.info("获取App考勤确认详情耗时=>{}", ent - st + " 毫秒");
return result;
}
@SneakyThrows
@Operation(summary = "获取App考勤确认详情")
@GetMapping(value = "/getAppConfirmDetails")
public ActionResult<ConfirmDetailsVo> getAppConfirmDetails(@RequestParam(value = "id", required = false) String id) {
long st = System.currentTimeMillis();
ConfirmDetailsVo result = attendanceConfirmService.getAppConfirmDetails(id);
long ent = System.currentTimeMillis();
log.info("获取App考勤确认详情耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "App温馨提示关闭")
@GetMapping(value = "/tipsClos/{id}")
public ActionResult<Boolean> tipsClos(@PathVariable("id") String id) {
log.info("温馨提示关闭,入参=>{}", id);
long st = System.currentTimeMillis();
Boolean result = attendanceConfirmService.tipsClos(id);
long ent = System.currentTimeMillis();
log.info("温馨提示关闭,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "App已查看")
@GetMapping(value = "/look/{id}")
public ActionResult<Boolean> look(@PathVariable("id") String id) {
log.info("App已查看入参=>{}", id);
long st = System.currentTimeMillis();
Boolean result = attendanceConfirmService.look(id);
long ent = System.currentTimeMillis();
log.info("App已查看耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
@SneakyThrows
@Operation(summary = "App考勤确认提交")
@GetMapping(value = "/confirmDetailsSubmit/{id}")
public ActionResult<Boolean> confirmDetailsSubmit(@PathVariable("id") String id) {
log.info("App考勤确认提交入参=>{}", id);
long st = System.currentTimeMillis();
Boolean result = attendanceConfirmService.confirmDetailsSubmit(id);
long ent = System.currentTimeMillis();
log.info("App考勤确认提交耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(result);
}
}

View File

@@ -0,0 +1,43 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AttendanceCustomizeTableService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.vo.attendance.AttendanceCustomizeTableVo;
import jnpf.model.attendance.vo.attendance.CustomizeTableUpdateVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤统计WEB
*
* @author ahua
* @since 2024-09-03
*/
@RestController
@RequestMapping("/attendance/customizeTable")
public class AttendanceCustomizeTableController {
@Autowired
private AttendanceCustomizeTableService tableService;
@GetMapping
@Operation(summary = "自定义报表设置-列表查询")
public ActionResult<List<AttendanceCustomizeTableVo>> findList(@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer status,
@RequestParam(required = false, defaultValue = "1") Integer type) {
return ActionResult.success(tableService.findList(keyword, status, type));
}
@PutMapping
@Operation(summary = "自定义报表设置-设置")
public ActionResult<Void> update(@Valid @RequestBody CustomizeTableUpdateVo tableVos) {
tableService.update(tableVos);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,399 @@
package jnpf.attendance.controller;
import cn.hutool.core.collection.CollUtil;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.AttendanceDailyRuleApi;
import jnpf.attendance.service.AttendanceDailyRuleService;
import jnpf.attendance.service.AttendanceLeaveRulesService;
import jnpf.base.ActionResult;
import jnpf.entity.attendance.ApplyParam;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.v2.ClockOutHandleParam;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.LineDrawingSchedulesConfigDto;
import jnpf.model.attendance.dto.SchedulesImportDto;
import jnpf.model.attendance.dto.SchedulesSetDto;
import jnpf.model.attendance.dto.UnifiedSchedulesDto;
import jnpf.model.attendance.dto.UserDayShiftQueryDto;
import jnpf.model.attendance.vo.DayReceivableRevenueVo;
import jnpf.model.attendance.vo.DayShiftRevenueStatVo;
import jnpf.model.attendance.vo.ScheduleRuleDetailVo;
import jnpf.model.attendance.vo.SchedulesV2Vo;
import jnpf.model.attendance.vo.attendance.OvertimeRuleDetailVo;
import jnpf.model.attendance.vo.LineSchedulesVo;
import jnpf.model.attendance.vo.UserDayShiftInfoVo;
import jnpf.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 排班规则
*
* @author ahua
* @since 2023-11-22
*/
@RestController
@RequestMapping("/arranging/work")
@Slf4j
public class AttendanceDailyRuleController implements AttendanceDailyRuleApi {
@Resource
private CustomTenantUtil customTenantUtil;
@Autowired
private AttendanceDailyRuleService attendanceDailyRuleService;
@Autowired
private AttendanceLeaveRulesService attendanceLeaveRulesService;
/**
* 根据指定条件查询某个群体在特定月份的调度情况
*
* @param groupId 群体ID用于标识特定的群体
* @param realName 真实姓名,用于筛选特定的个人
* @param month 月份格式为YYYY-MM用于指定查询的时间范围
* @param userIds 用户ID列表用于限定查询的用户范围
* @param isSchedules 是否仅查询已调度的记录true表示仅查询已调度记录false表示查询所有记录
* @return 返回根据条件查询到的调度情况结果
*/
@GetMapping(value = "/schedulesExport")
public ActionResult<Boolean> schedulesExport(@RequestParam String groupId, @RequestParam(required = false) String workGroupId, @RequestParam(required = false) String realName, @RequestParam String month, @RequestParam(required = false) List<String> userIds, @RequestParam(required = false, defaultValue = "1") Integer isSchedules) {
attendanceDailyRuleService.schedulesExport(groupId, workGroupId, realName, month, userIds, isSchedules);
return ActionResult.success();
}
/**
* 划线排班导出
*
* @param groupId 考勤组id
* @param workGroupId 班组ID
* @param month 月份
* @param userIdList 用户ID列表
* @return
*/
@GetMapping(value = "/lineSchedulesExport")
public ActionResult<Boolean> lineSchedulesExport(@RequestParam String groupId, @RequestParam(required = false) String workGroupId, @RequestParam String month, @RequestParam(required = false) List<String> userIdList) {
attendanceDailyRuleService.lineSchedulesExport(groupId, workGroupId, month, userIdList);
return ActionResult.success();
}
/**
* 排班导入
*
* @param schedulesImportDto 参数
* @return
*/
@PostMapping(value = "/schedulesImport")
public ActionResult<List<SchedulesV2Vo>> schedulesImport(@RequestBody SchedulesImportDto schedulesImportDto) throws IOException {
return ActionResult.success(attendanceDailyRuleService.schedulesImport(schedulesImportDto));
}
/**
* 划线排班导入
*
* @param schedulesImportDto 参数
* @return
*/
@PostMapping(value = "/lineSchedulesImport")
public ActionResult lineSchedulesImport(@RequestBody SchedulesImportDto schedulesImportDto) throws IOException {
attendanceDailyRuleService.lineSchedulesImport(schedulesImportDto);
return ActionResult.success();
}
/**
* 晚走晚到
*
* @return
*/
@GetMapping(value = "/clockOutHandle")
public ActionResult clockOutHandle(@RequestParam String userId, @RequestParam String tenantId, @RequestParam String clockOut, @RequestParam String clockInTime) {
ClockOutHandleParam param = new ClockOutHandleParam();
param.setUserId(userId);
param.setTenantId(tenantId);
param.setAttendanceType(AttendanceTypeEnum.ORDINARY.getCode());
param.setStart(DateUtil.stringToDate(clockOut));
param.setEnd(DateUtil.stringToDate(clockInTime));
param.setApplyId("");
param.setRuleId("");
param.setDay(new Date());
OvertimeRuleDetailVo overtimeRuleDetailVo = new OvertimeRuleDetailVo();
overtimeRuleDetailVo.setOvertimeType(1);
overtimeRuleDetailVo.setMinOvertimeMinute(30);
overtimeRuleDetailVo.setEnabled(1);
overtimeRuleDetailVo.setCalcMethod(3);
overtimeRuleDetailVo.setTimeUnit(1);
overtimeRuleDetailVo.setCompensateRatio(BigDecimal.ONE);
overtimeRuleDetailVo.setCompensateType(1);
param.setRuleDetail(overtimeRuleDetailVo);
attendanceDailyRuleService.workOvertimeNotApprove(param);
return ActionResult.success();
}
/**
* 获取排班列表V2版本。
*
* @param groupId 考勤组id
* @param workGroupId 班组ID
* @param realName 真实姓名(可选)
* @param month 月份
* @param userIds 用户ID列表可选
* @param isSchedules 是否排班标识可选默认值为1
* @return 排班列表V2版本视图对象
*/
@GetMapping("v2")
public ActionResult<List<SchedulesV2Vo>> getSchedulesListV2(@RequestParam String groupId, @RequestParam(required = false) String workGroupId, @RequestParam(required = false) String realName, @RequestParam String month, @RequestParam(required = false) List<String> userIds, @RequestParam(required = false, defaultValue = "1") Integer isSchedules) {
return ActionResult.success(attendanceDailyRuleService.getSchedulesListV2(groupId, workGroupId, realName, month, userIds, isSchedules));
}
/**
* 获取排班列表 V2按开始日期、结束日期其它参数与 v2 一致)
*/
@GetMapping("v2/range")
public ActionResult<List<SchedulesV2Vo>> getSchedulesListV2ByDateRange(@RequestParam String groupId, @RequestParam(required = false) String workGroupId, @RequestParam(required = false) String realName, @RequestParam String startDate, @RequestParam String endDate, @RequestParam(required = false) List<String> userIds, @RequestParam(required = false, defaultValue = "1") Integer isSchedules) {
return ActionResult.success(attendanceDailyRuleService.getSchedulesListV2ByDateRange(groupId, workGroupId, realName, startDate, endDate, userIds, isSchedules));
}
/**
* 预排班草稿转排班列表 V2按员工 + 日期区间);草稿 Redis 不存在时返回空列表。
*/
@GetMapping("v2/preSchedule/draft")
public ActionResult<List<SchedulesV2Vo>> getSchedulesListV2ByPreScheduleDraft(
@RequestParam String groupId,
@RequestParam String startDate,
@RequestParam String endDate,
@RequestParam String draftId) {
return ActionResult.success(
attendanceDailyRuleService.getSchedulesListV2ByPreScheduleDraft(
groupId, startDate, endDate, draftId));
}
/**
* 根据ID获取排班规则详情。
*
* @param id 排班规则ID
* @return 排班规则详情视图对象
* @throws HandleException 处理异常
*/
@GetMapping("{id}")
public ActionResult<ScheduleRuleDetailVo> getSchedulesList(@PathVariable String id) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.getDetail(id));
}
/**
* 设置排班。
*
* @param schedulesSets 排班设置列表
* @return 处理结果字符串
* @throws HandleException 处理异常
*/
@PostMapping
public ActionResult<String> setSchedules(@RequestBody List<SchedulesSetDto> schedulesSets) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.setSchedules(schedulesSets));
}
/**
* 自己排班调整排班
*
* @param shiftId 班次id
* @return
*/
@GetMapping("/selfSchedules")
public ActionResult<String> setSchedulesForSelfSchedules(@RequestParam("shiftId") String shiftId) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.setSchedulesForSelfSchedules(shiftId));
}
/**
* 定时执行初始化下个月固定排班。
*/
@PostMapping("initFixedScheduleRule")
public ActionResult initFixedScheduleRule(@RequestParam("tenantId") String tenantId) {
attendanceDailyRuleService.initFixedScheduleRule(tenantId);
return ActionResult.success();
}
/**
* 处理日常规则申请。
*
* @param applyParam 申请参数类
* @return 处理结果字符串
* @throws HandleException 处理异常
*/
@PostMapping("applyDailyRuleHandle")
public ActionResult applyDailyRuleHandle(@RequestBody ApplyParam applyParam) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.applyDailyRuleHandle(applyParam));
}
/**
* 处理借调申请日规则。
*
* @param userId 借调用户ID
* @param fromGroupId 原考勤组ID
* @param toGroupId 借调考勤组ID
* @param start 开始时间
* @param end 结束时间
* @param departureTime 离岗时间
* @param backTime 回岗时间
* @return 处理结果视图对象
* @throws HandleException 处理异常
*/
@PostMapping("secondmentDailyRuleHandle")
public ActionResult secondmentDailyRuleHandle(@RequestParam String userId, @RequestParam String fromGroupId, @RequestParam String toGroupId, @RequestParam String start, @RequestParam String end, @RequestParam String departureTime, @RequestParam String backTime) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.secondmentDailyRuleHandle(CollUtil.newArrayList(userId), fromGroupId, toGroupId, DateDetail.getStr2DateTime(start), DateDetail.getStr2DateTime(end), DateDetail.getStr2DateTime(departureTime), DateDetail.getStr2DateTime(backTime), "yawentest2"));
}
/**
* 自动授予假期余额。
*
* @param tenantId 租户ID
* @return 处理结果视图对象
*/
@PostMapping("autoGrantBalance")
public ActionResult autoGrantBalance(@RequestParam("tenantId") String tenantId) {
// 2.0 节日不发劵了
// attendanceFestivalSettingService.autoGrantBalance();
// 需要修改
// attendanceHolidaySettingService.autoGrantBalance(tenantId);
attendanceLeaveRulesService.autoGrantBalance(tenantId);
return ActionResult.success();
}
/**
* 用户是否排班
*
* @param tenantId 租户ID
* @param userId 用户ID
* @return
*/
@Override
@NoDataSourceBind
@GetMapping("userIsScheduling")
public ActionResult<Boolean> userIsScheduling(@RequestParam("tenantId") String tenantId, @RequestParam("userId") String userId) {
customTenantUtil.checkOutTenant(tenantId);
return ActionResult.success(attendanceDailyRuleService.userIsScheduling(userId));
}
/**
* 用户是否排班
*
* @return
*/
@PostMapping("userIsSchedulingOrdinary")
@Override
public ActionResult<List<String>> userIsSchedulingOrdinary(@RequestBody List<String> organizeIds) {
return ActionResult.success(attendanceDailyRuleService.userIsSchedulingOrdinary(organizeIds));
}
/**
*
* @param userId
* @param start
* @param end
* @return
*/
@GetMapping("hasRuleByUserIdAndTime")
@Override
public ActionResult<Boolean> hasRuleByUserIdAndTime(@RequestParam String userId, @RequestParam Date start, @RequestParam Date end) {
return ActionResult.success(attendanceDailyRuleService.hasRuleByUserIdAndTime(userId, start, end));
}
/**
* 设置划线排班
*
* @param configDto 划线排班配置DTO
* @return 处理结果
* @throws HandleException 处理异常
*/
@PostMapping("lineDrawingSchedules")
public ActionResult<String> setLineDrawingSchedules(@RequestBody LineDrawingSchedulesConfigDto configDto) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.setLineDrawingSchedules(configDto));
}
/**
* 统一排班接口(支持固定排班和划线排班)
*
* @param dto 统一排班DTO
* @return 处理结果
* @throws HandleException 处理异常
*/
@PostMapping("unifiedSchedules")
public ActionResult<String> setUnifiedSchedules(@RequestBody UnifiedSchedulesDto dto) throws HandleException {
return ActionResult.success(attendanceDailyRuleService.setUnifiedSchedules(dto));
}
/**
* 指定用户日期是否存在划线排班
*
* @param configDto 划线排班配置DTO
* @return 处理结果
*/
@PostMapping("queryLineSchedulingExist")
public ActionResult<Boolean> queryLineSchedulingExist(@RequestBody LineDrawingSchedulesConfigDto configDto) {
return ActionResult.success(attendanceDailyRuleService.queryLineSchedulingExist(configDto));
}
/**
* 获取划线排班列表
*
* @param groupId 考勤组id
* @param workGroupId 班组ID
* @param dayList 日期列表
* @param finalUserIdList 用户ID列表
* @return 划线排班列表
*/
@GetMapping("getLineSchedulesList")
public ActionResult<List<LineSchedulesVo>> getLineSchedulesList(@RequestParam String groupId, @RequestParam(required = false) String workGroupId, @RequestParam(required = false) String dayList, @RequestParam(required = false) List<String> finalUserIdList) {
return ActionResult.success(attendanceDailyRuleService.getLineSchedulesList(groupId, workGroupId, Arrays.asList(StringUtil.split(dayList, ",")), finalUserIdList));
}
/**
* 查询指定用户指定日期的班次信息,包含有效请假及普班
*
* @param queryDto 查询参数用户ID、查询日期
* @return 用户指定日期的班次信息
*/
/**
* 按自然日查询考勤组所属门店营业额预估
*
* @param groupId 考勤组ID
* @param startTime 开始日期yyyy-MM-dd
* @param endTime 结束日期yyyy-MM-dd
*/
@Operation(summary = "按自然日查询考勤组营业额预估")
@GetMapping("/group-receivable-revenue-by-day")
public ActionResult<List<DayReceivableRevenueVo>> listReceivableRevenueByDay(
@RequestParam String groupId,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) {
return ActionResult.success(attendanceDailyRuleService.listReceivableRevenueByDay(groupId, startTime, endTime));
}
@Operation(summary = "查询指定用户指定日期的班次信息")
@PostMapping("/user-day-shift-info")
public ActionResult<UserDayShiftInfoVo> getUserDayShiftInfo(@Valid @RequestBody UserDayShiftQueryDto queryDto) {
try {
log.info("查询用户指定日期班次信息userId=>{}, queryDate=>{}",
queryDto.getUserId(), DateUtil.dateToString(queryDto.getQueryDate(), "yyyy-MM-dd"));
long st = System.currentTimeMillis();
UserDayShiftInfoVo result = attendanceDailyRuleService.getUserDayShiftInfo(
queryDto.getUserId(),
queryDto.getQueryDate());
long ent = System.currentTimeMillis();
log.info("查询用户指定日期班次信息完成,耗时=>{} 毫秒", ent - st);
return ActionResult.success(result);
} catch (Exception e) {
log.error("查询用户指定日期班次信息异常", e);
return ActionResult.fail("查询失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,103 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.AttendanceFestivalRulesService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.AttendanceFestivalRulesDto;
import jnpf.model.attendance.dto.FestivalRulesQueryDto;
import jnpf.model.attendance.vo.attendance.AttendanceFestivalRulesVo;
import jnpf.util.FtbUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 节日规则
*
* @author 盼盼
* @create 2025-09-18
*/
@RestController
@RequestMapping(value = "/attendance/festival-rules")
@Slf4j
public class AttendanceFestivalRulesController {
@Autowired
private AttendanceFestivalRulesService attendanceFestivalRulesService;
/**
* 获取考勤节日管理列表
* @param queryDto 节日名称模糊查询非必传及年份筛选必传
*/
@PostMapping("/list")
public ActionResult<PageListVO<AttendanceFestivalRulesVo>> list(@RequestBody FestivalRulesQueryDto queryDto) {
PageInfo<AttendanceFestivalRulesVo> vo = attendanceFestivalRulesService.getPageList(queryDto);
return ActionResult.page(vo.getList(), FtbUtil.getPagination(vo));
}
/**
* 新增节日
* @param festivalRulesDto 节日
*/
@PostMapping()
public ActionResult<Void> add(@RequestBody AttendanceFestivalRulesDto festivalRulesDto) throws Exception {
attendanceFestivalRulesService.add(festivalRulesDto);
return ActionResult.success();
}
/**
* 更新法定节假日信息。
*
* @param year 年份
* @return 处理结果视图对象
*/
@PutMapping("statutoryUpdate")
public ActionResult statutoryUpdate(@RequestParam String year) {
attendanceFestivalRulesService.statutoryUpdate(year);
return ActionResult.success();
}
/**
* 修改节日
* @param festivalRulesDto 节日
*/
@PutMapping("/{id}")
public ActionResult<Void> put(@PathVariable("id") String id, @RequestBody AttendanceFestivalRulesDto festivalRulesDto) throws Exception {
festivalRulesDto.setId( id);
attendanceFestivalRulesService.update(festivalRulesDto);
return ActionResult.success();
}
/**
* 获取节日详情
* @param id 节日 ID
*/
@GetMapping("/{id}")
public ActionResult<AttendanceFestivalRulesVo> detail(@PathVariable("id") String id) {
return ActionResult.success(attendanceFestivalRulesService.detail(id));
}
/**
* 删除节日
* @param id 节日 ID
*/
@DeleteMapping("/{id}")
public ActionResult<Void> delete(@PathVariable("id") String id) {
attendanceFestivalRulesService.delete(id);
return ActionResult.success();
}
/**
* 启用、停用节日
* @param festivalRulesDto 节日
*/
@PutMapping("/updateState")
public ActionResult<Void> updateState( @RequestBody AttendanceFestivalRulesDto festivalRulesDto) {
attendanceFestivalRulesService.updateState(festivalRulesDto);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,122 @@
package jnpf.attendance.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import jnpf.attendance.service.AttendanceFestivalSettingService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceFestivalSettingDto;
import jnpf.model.attendance.dto.EnableUpdateDto;
import jnpf.model.attendance.vo.AttendanceFestivalSettingVo;
import jnpf.model.attendance.vo.HolidayOptionVo;
import jnpf.util.PageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* <p>
* 考勤配置-节日配置 前端控制器
* </p>
*
* @author ahua
* @since 2023-11-29
*/
@RestController
@RequestMapping("/festival/setting")
public class AttendanceFestivalSettingController {
@Autowired
private AttendanceFestivalSettingService attendanceFestivalSettingService;
/**
* 保存节假日设置信息。
*
* @param attendanceFestivalSettingDto 节假日设置数据传输对象
* @throws HandleException 处理异常
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult save(@RequestBody AttendanceFestivalSettingDto attendanceFestivalSettingDto) throws HandleException {
attendanceFestivalSettingService.save(attendanceFestivalSettingDto);
return ActionResult.success();
}
/**
* 分页查询节假日设置信息。
*
* @param groupId 考勤组ID
* @param year 年份(可选)
* @param currentPage 当前页码
* @param pageSize 每页大小
* @return 分页的节假日设置视图对象列表
* @throws HandleException 处理异常
*/
@GetMapping
public ActionResult<PageListVO<AttendanceFestivalSettingVo>> page(@RequestParam String groupId, @RequestParam(required = false) String year, @RequestParam Integer currentPage, @RequestParam Integer pageSize) throws HandleException {
PageDTO<AttendanceFestivalSettingVo> page1 = attendanceFestivalSettingService.page(groupId, year, currentPage, pageSize);
return ActionResult.page(page1.getRecords(), PageUtil.page(page1));
}
/**
* 根据ID获取单个节假日设置信息。
*
* @param id 节假日设置ID
* @return 节假日设置视图对象
*/
@GetMapping("{id}")
public ActionResult<AttendanceFestivalSettingVo> getOne(@PathVariable String id) {
return ActionResult.success(attendanceFestivalSettingService.getOne(id));
}
/**
* 删除指定的节假日设置信息。
*
* @param id 节假日设置ID
* @return 处理结果视图对象
*/
@DeleteMapping("{id}")
public ActionResult del(@PathVariable String id) {
attendanceFestivalSettingService.del(id);
return ActionResult.success();
}
/**
* 更改节假日设置信息的启用状态。
*
* @param enableUpdateDto 启用状态更新数据传输对象
* @throws HandleException 处理异常
* @return 处理结果视图对象
*/
@PutMapping("updateStatus")
public ActionResult updateStatus(@RequestBody EnableUpdateDto enableUpdateDto) throws HandleException {
attendanceFestivalSettingService.updateStatus(enableUpdateDto.getId(), enableUpdateDto.getEnable());
return ActionResult.success();
}
/**
* 更新法定节假日信息。
*
* @param groupId 考勤组ID
* @param year 年份
* @return 处理结果视图对象
*/
@PutMapping("statutoryUpdate")
public ActionResult statutoryUpdate(@RequestParam String groupId, @RequestParam String year) {
attendanceFestivalSettingService.statutoryUpdate(groupId, year);
return ActionResult.success();
}
/**
* 获取节假日选项列表。
*
* @param groupId 考勤组ID
* @return 节假日选项视图对象列表
*/
@GetMapping("options")
public ActionResult<List<HolidayOptionVo>> getHolidayOptions(@RequestParam String groupId) {
return ActionResult.success(attendanceFestivalSettingService.getHolidayOptions(groupId));
}
}

View File

@@ -0,0 +1,615 @@
package jnpf.attendance.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.attendance.AttendanceGroupApi;
import jnpf.attendance.dto.AttendanceUserGroupVo;
import jnpf.attendance.dto.AttendanceUserListGroupVO;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.base.ActionResult;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AppointPermission;
import jnpf.enums.attendance.GroupUserTypeEnum;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.GroupLockVo;
import jnpf.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
/**
* 考勤组控制器
*
* @author yier
* @create 2023-11-21
*/
@RestController
@RequestMapping(value = "/group")
public class AttendanceGroupController implements AttendanceGroupApi {
@Resource
private AttendanceUserService attendanceUserService;
@Resource
private AttendanceGroupService attendanceGroupService;
/**
* 查询考勤组管理列表
*
* @param groupQueryDto 考勤组名称查询实体
* @return List<AttendanceGroupVo>
*/
@GetMapping
public ActionResult<List<AttendanceGroupVo>> list(GroupQueryDto groupQueryDto) {
return ActionResult.success(attendanceGroupService.groupManagerList(groupQueryDto));
}
/**
* 新增考勤组
*
* @param saveAttendanceGroupDto 考勤组保存实体
* @return ActionResult<Void>
*/
@PostMapping
public ActionResult<Void> add(@RequestBody SaveAttendanceGroupDto saveAttendanceGroupDto) throws Exception {
ParamUtil.checkParam(saveAttendanceGroupDto);
int num = attendanceGroupService.save(saveAttendanceGroupDto);
if (num < 0) {
return ActionResult.fail("考勤组名称重复,请换一个名称!");
}
return ActionResult.success();
}
/**
* 修改考勤组信息
*
* @param saveAttendanceGroupDto 考勤组保存实体
* @return Void
*/
@PutMapping
public ActionResult<Void> update(@RequestBody SaveAttendanceGroupDto saveAttendanceGroupDto) throws Exception {
ParamUtil.checkParam(saveAttendanceGroupDto);
int num = attendanceGroupService.save(saveAttendanceGroupDto);
if (num < 0) {
return ActionResult.fail("考勤组名称重复,请换一个名称!");
}
return ActionResult.success();
}
/**
* 删除考勤组
*
* @param saveAttendanceGroupDto 考勤组保存实体
* @return Void
*/
@DeleteMapping("/deleteGroup")
public ActionResult<Void> delete(@RequestBody SaveAttendanceGroupDto saveAttendanceGroupDto) throws HandleException {
/*移动考勤组*/
String parentId = saveAttendanceGroupDto.getParentId();
if (StrUtil.isNotBlank(parentId)) {
AttendanceGroup moveGroup = attendanceGroupService.getById(parentId);
if (moveGroup.getLevelCode().contains(saveAttendanceGroupDto.getId())) {
throw new HandleException("不能将删除考勤组移动到本组获取子组!");
}
}
String id = saveAttendanceGroupDto.getId();
AttendanceGroup group = attendanceGroupService.getById(id);
if (group.getDeleteMark() == 1) {
throw new HandleException("该考勤组已删除,请勿频繁操作!");
}
attendanceGroupService.delete(saveAttendanceGroupDto.getId(), saveAttendanceGroupDto.getParentId());
return ActionResult.success();
}
/**
* 借调时展示我能查看到的考勤组列表
*
* @return ActionResult<List < AttendanceGroupVo>>
*/
@GetMapping("/secondedQueryGroup")
public ActionResult<List<AttendanceGroupVo>> queryManagerGroupList(String keyword) {
List<AttendanceGroupVo> attendanceGroupVoList = attendanceGroupService.queryManagerGroupList(keyword);
return ActionResult.success(attendanceGroupVoList);
}
/**
* 查询所有考勤组
*/
@GetMapping("/getAllGroup")
public ActionResult<List<AttendanceGroupVo>> getAllGroup(String keyword) {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.queryList(keyword);
return ActionResult.success(groupVoList);
}
/**
* 根据考勤组名称模糊查询考勤组列表(带权限控制)
* 返回用户有管理权限的考勤组,支持名称模糊查询
*
* @param groupName 考勤组名称(支持模糊查询)
* @return ActionResult<List<AttendanceGroupVo>>
*/
@GetMapping("/searchByName")
public ActionResult<List<AttendanceGroupVo>> searchGroupByName(@RequestParam String groupName) {
return ActionResult.success(attendanceGroupService.searchGroupByName(groupName));
}
/**
* 查询我所在的考勤组列表(In:app/考勤规则)
*
* @return ActionResult<List < AttendanceGroupVo>>
*/
@GetMapping("/queryMyGroupList")
public ActionResult<List<AttendanceGroup>> queryMyGroupList() {
List<AttendanceGroup> groupList = attendanceGroupService.queryMyGroups();
return ActionResult.success(groupList);
}
/**
* 查询考勤组下成员
*
* @param id 考勤组id
* @return ActionResult<List < AttendanceGroupUserVo>>
*/
@GetMapping("/{id}/userList")
public ActionResult<List<AttendanceGroupUserVo>> queryUserList(@PathVariable String id, String name, Integer type, String month) {
Date start = DateDetail.getMonthBeginDate(month);
Date end = DateDetail.getMonthEndDate(month);
return ActionResult.success(attendanceUserService.queryUsersByGroupId(id, name, type, null, start,end));
}
/**
* 借调查询考勤组下成员
*
* @param id 考勤组id
* @return ActionResult<List < AttendanceGroupUserVo>>
*/
@GetMapping("/userList")
public ActionResult<List<AttendanceGroupUserVo>> queryUserListOfSecondment(@RequestParam String id, @RequestParam(required = false) String name, @RequestParam(required = false) Integer type, @RequestParam(required = false) String time) {
Assert.isFalse(StringUtil.isBlank(time), "未选择借调时间");
String[] split = StringUtil.split(time, ",");
Assert.isFalse(split.length < 2, "未选择借调时间");
Date secondedStartTime = new Date(Long.parseLong(split[0]));
Date secondedEndTime = new Date(Long.parseLong(split[1]));
List<AttendanceGroupUserVo> listActionResult = attendanceUserService.queryUsersByGroupId(id, name, type,null, secondedStartTime, secondedEndTime);
if (listActionResult!= null) {
listActionResult.forEach(v -> {
v.setRealName(v.getRealName() + "" + v.getPositionName() + "");
});
}
return ActionResult.success(listActionResult);
}
/**
* 考勤组下拉列表
*
* @return ActionResult<List < AttendanceGroup>>
*/
@GetMapping("/dropList")
public ActionResult<List<AttendanceGroup>> dropList() {
List<AttendanceGroup> dropList = attendanceGroupService.queryDropList();
return ActionResult.success(dropList);
}
/**
* 借调开始
*
* @param attendanceSecondedDto 借调参数数据
* @return ActionResult<Void>
*/
@PostMapping("/secondedStart")
public ActionResult<Void> secondedStart(@RequestBody AttendanceSecondedDto attendanceSecondedDto) throws HandleException {
attendanceGroupService.secondedStart(attendanceSecondedDto);
return ActionResult.success();
}
/**
* 切换考勤组配置状态
*
* @param dto 考勤组配置状态参数
* @return ActionResult<Void>
*/
@PutMapping("/exchangeGroupStatus")
public ActionResult<Void> exchangeGroupStatus(@RequestBody AttendanceGroupStatusDto dto) {
attendanceGroupService.exchangeGroupStatus(dto);
return ActionResult.success();
}
/**
* 获取考勤组配置状态
*
* @param groupId 考勤组id
* @return ActionResult<Void>
*/
@GetMapping("groupSettingStatus")
public ActionResult<AttendanceGroupStatusDto> groupSettingStatus(@RequestParam String groupId) {
return ActionResult.success(attendanceGroupService.groupSettingStatus(groupId, Boolean.FALSE));
}
/**
* 获取默认考勤组
*
* @return ActionResult<AttendanceGroup>
*/
@GetMapping("/getDefaultGroup")
public ActionResult<AttendanceGroupVo> getDefaultGroup() {
AttendanceGroupVo defaultGroup = attendanceGroupService.getDefaultGroup();
return ActionResult.success(defaultGroup);
}
/**
* 切换考勤组
*
* @param groupId 考勤组Id
* @return ActionResult<Void>
*/
@GetMapping("/handoffGroup")
public ActionResult<Void> handoffGroup(String groupId) {
attendanceGroupService.handoffGroup(groupId);
return ActionResult.success();
}
/**
* 排班考勤组列表
*
* @return ActionResult<AttendanceGroupVo>
*/
@GetMapping("/schedulingGroupList")
public ActionResult<List<AttendanceGroupVo>> schedulingGroupList() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.schedulingGroupList();
return ActionResult.success(groupVoList);
}
/**
* 排班考勤组列表过滤固定班
*
* @return ActionResult<AttendanceGroupVo>
*/
@GetMapping("/schedulingGroupListByNotFixed")
public ActionResult<List<AttendanceGroupVo>> schedulingGroupListByNotFixed() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.schedulingGroupListByNotFixed();
return ActionResult.success(groupVoList);
}
/**
* 余额考勤组列表
*
* @return ActionResult<AttendanceGroupVo>
*/
@GetMapping("/balanceManagementGroupList")
public ActionResult<List<AttendanceGroupVo>> balanceManagementGroupList() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.balanceManagement();
return ActionResult.success(groupVoList);
}
/**
* 查询我有借调权限的考勤组管理列表
*
*/
@GetMapping("/querySecondedApprovalGroupList")
public ActionResult<List<AttendanceGroupVo>> querySecondedApprovalGroupList() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.secondedApprovalGroupList();
return ActionResult.success(groupVoList);
}
/**
* 切换考勤组查询我管理的考勤组
*
*/
@GetMapping("/handoffGroupTreeList")
public ActionResult<List<AttendanceGroupVo>> handoffGroupTreeList() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.handoffGroupTreeList();
return ActionResult.success(groupVoList);
}
/**
* 通过组织Id找到组织下的考勤组
*
* @param orgId 组织Id
*/
@GetMapping("/groupListByOrgId/{orgId}")
public ActionResult<List<AttendanceGroupVo>> groupListByOrgId(@PathVariable("orgId") String orgId) {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.groupListByOrgId(orgId);
return ActionResult.success(groupVoList);
}
/**
* 通过组织Id找到组织下的考勤组
*
* @param orgId 组织Id
*/
@GetMapping("/groupListByOrgId")
public ActionResult<List<AttendanceGroupVo>> groupListByOrgIds(@RequestParam("orgId") String orgId) {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.groupListByOrgId(orgId);
return ActionResult.success(groupVoList);
}
/**
* 通过考勤组Id获取绑定的组织名称拼接好的及考勤组人数
*
* @param groupId 考勤组Id
*/
@GetMapping("/getGroupBindingOrg/{groupId}")
public ActionResult<AttendanceGroupVo> getGroupBindingOrg(@PathVariable("groupId") String groupId,@RequestParam(value = "workGroupId",required = false) String workGroupId) {
return ActionResult.success(attendanceGroupService.getGroupBindingOrg(groupId, workGroupId));
}
/**
* 划线排班-通过考勤组Id获取绑定的组织名称拼接好的及考勤组人数
*
* @param groupId 考勤组Id
*/
@GetMapping("/line/getGroupBindingOrg/{groupId}")
public ActionResult<AttendanceGroupVo> getGroupBindingOrgForLine(@PathVariable("groupId") String groupId,@RequestParam(value = "workGroupId",required = false) String workGroupId) {
return ActionResult.success(attendanceGroupService.getGroupBindingOrgForLine(groupId, workGroupId));
}
/**
* 绑定考勤组的组织
*
* @param attendanceGroupOrgDto 考勤组和组织信息
*/
@PutMapping("/updateGroupOrg")
public ActionResult<Void> updateGroupOrg(@RequestBody AttendanceGroupOrgDto attendanceGroupOrgDto) {
attendanceGroupService.updateGroupOrg(attendanceGroupOrgDto);
return ActionResult.success();
}
/**
* 获取考勤组名称
*
* @param groupId 考勤组Id
*/
@Override
@GetMapping("/queryTheNameOfTheAttendanceGroup")
public AttendanceGroup queryTheNameOfTheAttendanceGroup(@RequestParam(value = "groupId") String groupId) {
LambdaQueryWrapper<AttendanceGroup> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(AttendanceGroup::getId, groupId);
queryWrapper.eq(AttendanceGroup::getDeleteMark, 0);
return attendanceGroupService.getOne(queryWrapper);
}
/**
* 获取用户考勤组
*
* @param userIds 用户ids
*/
@Override
@GetMapping("/attendanceUserGroup")
public List<AttendanceUserGroupVo> getAttendanceUserGroup(@RequestParam("userIds") List<String> userIds) {
return attendanceGroupService.getAttendanceUserGroup(userIds);
}
/**
* 批量获取用户绑定的考勤组 ,包含全名称 xxx/xxx/xxx
*
* @param userIds 用户ids
*/
@Override
@PostMapping("/list/user_bound")
public ActionResult<List<AttendanceUserListGroupVO>> getAttendanceUserListGroupVO(@RequestBody List<String> userIds) {
return ActionResult.success(attendanceGroupService.getAttendanceUserListGroupVO(userIds));
}
/**
* 获取组织下的考勤组
*
* @param organizeId 组织id
* @return List<AttendanceGroup>
*/
@GetMapping("/getGroupListByOrgId")
@Override
public List<AttendanceGroup> getGroupListByOrgId(@RequestParam("organizeId") String organizeId) {
QueryWrapper<AttendanceGroup> groupQuery = new QueryWrapper<>();
groupQuery.lambda()
.eq(AttendanceGroup::getOrgId, organizeId);
return attendanceGroupService.getByOrgId(organizeId);
}
/**
* 详情---获取一个考勤组
*
* @param organizeName 组织名称
* @param groupName 考勤组名称
* @return 一个考勤组
*/
@Override
@GetMapping("/info/organize_group/name")
public ActionResult<AttendanceGroupVo> getAttendanceGroupByName(@RequestParam("organizeName") String organizeName, @RequestParam("groupName") String groupName) {
return ActionResult.success(attendanceGroupService.getAttendanceGroupByNameOrganizeAndGroup(organizeName, groupName));
}
/**
* 锁定考勤组
*
* @param groupId 考勤组id
*/
@PutMapping(value = "/lock/{groupId}")
public Object lockGroup(@PathVariable(value = "groupId") String groupId) {
attendanceGroupService.lockGroup(groupId);
return ActionResult.success();
}
/**
* 考勤组2.0历史数据处理
*
* @param tenantId 考勤组id
*/
@GetMapping(value = "/oldDataProcessing/{tenantId}")
@NoDataSourceBind
public Object oldDataProcessing(@PathVariable(value = "tenantId") String tenantId) {
attendanceGroupService.oldDataProcessing(tenantId);
return ActionResult.success();
}
/**
* 查询用户当前考勤组锁定信息
*
* @param userId 用户id
* @return java.lang.Object
*/
@GetMapping(value = "/lockInfo/{userId}")
public Object getUserCurrentGroupLockInfo(@PathVariable(value = "userId") String userId) throws Exception {
GroupLockVo groupLockVo = attendanceGroupService.getUserCurrentGroupLockInfo(userId);
return ActionResult.success(groupLockVo);
}
/**
* 查询当前考勤组锁定信息
*
* @param groupId 考勤组id
* @return java.lang.Object
*/
@GetMapping(value = "/groupLockInfo/{groupId}")
public Object getGroupLockInfo(@PathVariable(value = "groupId") String groupId) throws Exception {
GroupLockVo groupLockVo = attendanceGroupService.getGroupLockInfo(groupId);
return ActionResult.success(groupLockVo);
}
/**
* 查询用户所在考勤组是否有指定权限
* @param appointPermission 参数
*/
@GetMapping("/appointPermission")
public ActionResult<Boolean> appointPermission(AppointPermission appointPermission) {
return ActionResult.success(attendanceGroupService.appointPermission(appointPermission));
}
//*************************************考勤V1.9***************************************************/
/**
* V1.9 查询考勤组管理列表
* @param groupQueryDto 考勤组名称查询实体
* @return List<AttendanceGroupVo>
*/
@GetMapping("/list")
public ActionResult<List<AttendanceGroupVo>> groupList(GroupQueryDto groupQueryDto) {
return ActionResult.success(attendanceGroupService.groupMyManagerList(groupQueryDto));
}
/**
* V1.9 排班考勤组列表过滤固定班
* @return ActionResult<AttendanceGroupVo>
*/
@GetMapping("/schedulingGroupListByNotFixedNew")
public ActionResult<List<AttendanceGroupVo>> schedulingGroupListByNotFixedNew() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.schedulingGroupListByNotFixedNew();
return ActionResult.success(groupVoList);
}
/**
* V1.9 切换考勤组查询我管理的考勤组
*/
@GetMapping("/handoffGroupTreeListNew")
public ActionResult<List<AttendanceGroupVo>> handoffGroupTreeListNew() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.handoffGroupTreeListNew();
return ActionResult.success(groupVoList);
}
/**
* v1.9获取默认考勤组
*
* @return ActionResult<AttendanceGroup>
*/
@GetMapping("/getDefaultGroupNew")
public ActionResult<AttendanceGroupVo> getDefaultGroupNew() {
AttendanceGroupVo defaultGroup = attendanceGroupService.getDefaultGroupNew();
return ActionResult.success(defaultGroup);
}
/**
* v1.9排班考勤组列表
*/
@GetMapping("/schedulingGroupListNew")
public ActionResult<List<AttendanceGroupVo>> schedulingGroupListNew() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.schedulingGroupListNew();
return ActionResult.success(groupVoList);
}
/**
* v1.9余额考勤组列表
*/
@GetMapping("/balanceManagementGroupListNew")
public ActionResult<List<AttendanceGroupVo>> balanceManagementGroupListNew() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.balanceManagementNew();
return ActionResult.success(groupVoList);
}
/**
* V2.0 出勤变更查询考勤组列表
*/
@GetMapping("/querySecondedApprovalGroupListNew")
public ActionResult<List<AttendanceGroupVo>> querySecondedApprovalGroupListNew() {
List<AttendanceGroupVo> groupVoList = attendanceGroupService.secondedApprovalGroupListNew();
return ActionResult.success(groupVoList);
}
/**
* 是否有考勤组排班权限排班
*/
@Override
@GetMapping("/checkScheduling")
public boolean checkScheduling() {
return attendanceGroupService.checkScheduling();
}
/**
* 获取有指定模块的权限的考勤组管理列表
*
* @param positionModuleName 权限模块名称
* @return List<AttendanceGroupVo>
*/
@GetMapping("/getPermissionsGroupList")
public ActionResult<List<AttendanceGroupVo>> getPermissionsGroupList(@RequestParam("positionModuleName") String positionModuleName) {
return ActionResult.success(attendanceGroupService.getPermissionsGroupList(positionModuleName));
}
/**
* 获取端考勤组列表(左侧选择组织||考勤组)
*/
@GetMapping("/getOrgOrGroupList")
public ActionResult<List<AttendanceOrgOrGroupVo>> getOrgOrGroupList(String keyword) {
return ActionResult.success(attendanceGroupService.getOrgOrGroupList(keyword));
}
/**
* 设置考勤组负责人
*
* @param dto 考勤组负责人信息
* @return Boolean
*/
@PutMapping(value = "/setGroupCharge")
public ActionResult<Boolean> setGroupCharge(@Valid @RequestBody AttendanceGroupChargeDto dto) {
return ActionResult.success(attendanceGroupService.setGroupCharge(dto));
}
/**
* 获取考勤组负责人
*
* @param groupId 考勤组id
* @return List<AttendanceGroupVo>
*/
@GetMapping(value = "/getGroupChargeInfo/{groupId}")
public ActionResult<AttendanceGroupChargeVo> getGroupChargeInfo(@PathVariable(value = "groupId") String groupId) {
return ActionResult.success(attendanceGroupService.getGroupChargeInfo(groupId));
}
}

View File

@@ -0,0 +1,228 @@
package jnpf.attendance.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.AttendanceUserApi;
import jnpf.attendance.dto.GroupUpdateByUserDTO;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.base.ActionResult;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.ExportUserTemplateDto;
import jnpf.model.attendance.dto.GroupUserQueryDto;
import jnpf.model.attendance.dto.JoinUserDto;
import jnpf.model.attendance.dto.UserSortModel;
import jnpf.model.attendance.vo.AttendanceGroupUserVo;
import jnpf.model.attendance.vo.attendance.JoinGroupVo;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.util.CustomTenantUtil;
import jnpf.util.EasyExcelUtil;
import jnpf.util.NoDataSourceBind;
import jnpf.util.ParamUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
/**
* 考勤组/成员管理
*
* @author yier
* @Time 2023-11-23
*/
@Slf4j
@RestController
@RequestMapping(value = "/user")
public class AttendanceGroupUserController implements AttendanceUserApi {
@Resource
private AttendanceUserService attendanceUserService;
@Resource
private AttendanceGroupService attendanceGroupService;
@Resource
private AttendanceDayStatisticsService dayStaService;
@Resource
private CustomTenantUtil customTenantUtil;
/**
* 成员管理-查询系统用户(附带考勤组名称)
*
* @param orgId 部门id
* @param name 用户姓名 字段模糊搜索
* @return ActionResult<AttendanceGroupUserVo>
*/
@GetMapping("/getSysUsers")
public ActionResult<List<AttendanceGroupUserVo>> get(String orgId, String name) {
List<AttendanceGroupUserVo> voList = attendanceUserService.querySysUserList(orgId, name);
return ActionResult.success(voList);
}
/**
* 用户排序
*
* @param userSortModel
*/
@PutMapping("/sort")
public ActionResult sort(@RequestBody UserSortModel userSortModel) {
Assert.isFalse(CollUtil.isEmpty(userSortModel.getUserSortList()), "未传入用户数据");
attendanceUserService.sort(userSortModel);
return ActionResult.success();
}
/**
* 用户加入考勤组
*
* @param joinUserDto
* @return ActionResult<Void>
* @throws Exception
*/
@PostMapping
public ActionResult<Void> addUsers(@RequestBody JoinUserDto joinUserDto) throws Exception {
ParamUtil.checkParam(joinUserDto);
attendanceUserService.joinUsers(joinUserDto);
return ActionResult.success();
}
/**
* 批量移除考勤组成员
*
* @param joinUserDto
* @return
*/
@DeleteMapping
public ActionResult<Void> batchRemove(@RequestBody JoinUserDto joinUserDto) {
attendanceUserService.batchDeleteUsersForSendNotice(joinUserDto);
return ActionResult.success();
}
/**
* 花名册考勤组变更
*
* @param groupUpdateByUserDTO
* @return
*/
@PostMapping("groupUpdateByPersonnel")
@NoDataSourceBind
@Deprecated
public ActionResult<Void> groupUpdateByPersonnel(@RequestBody GroupUpdateByUserDTO groupUpdateByUserDTO) {
customTenantUtil.checkOutTenant(groupUpdateByUserDTO.getTenantId());
attendanceUserService.groupUpdateByPersonnel(groupUpdateByUserDTO);
return ActionResult.success();
}
/**
* 检查用户是否已经加入
*
* @param userIds
* @return ActionResult<List < String>>
*/
@GetMapping("/checkUserJoinGroup")
public ActionResult<List<String>> checkUserJoinGroup(String userIds) throws HandleException {
List<String> userNameList = attendanceUserService.checkUserGroup(userIds);
return ActionResult.success(userNameList);
}
/**
* 检查用户是否已经加入1.9
* @param userIds 用户ids
* @return ActionResult<List < String>>
*/
@PostMapping("/checkUserJoinGroupToObject")
public ActionResult<List<JoinGroupVo>> checkUserJoinGroupToObject(@RequestBody String userIds) throws HandleException {
List<JoinGroupVo> userNameList = attendanceUserService.checkUserJoinGroupToObject(userIds);
return ActionResult.success(userNameList);
}
/**
* 导出用户模版
*/
@GetMapping("/downloadTemplate")
@NoDataSourceBind
public void downloadTemplate(HttpServletResponse response, @RequestParam String tenantId) throws Exception {
customTenantUtil.checkOutTenant(tenantId);
EasyExcelUtil.export(response, "考勤组成员.xlsx", "考勤组成员", ExportUserTemplateDto.class, null);
}
/**
* 筛选考勤组下成员去重入参考勤组ids 出参用户集合 借调用户也要查询出来
*
* @param groupUserQueryDto 考勤组信息
* @return
*/
@PostMapping("/getUsersByGroupIds")
public ActionResult<List<AttendanceGroupUserVo>> getUsersByGroupIds(@RequestBody @Valid GroupUserQueryDto groupUserQueryDto) {
List<UserBoundVO> voList = dayStaService.getUserIdArr(groupUserQueryDto.getFilterList(),
groupUserQueryDto.getStartTime(), groupUserQueryDto.getEndTime(), groupUserQueryDto.getUserIds(), "0");
List<AttendanceGroupUserVo> userVoList = CollectionUtil.isEmpty(voList) ? new ArrayList<>() :
voList.stream().map(vo -> {
AttendanceGroupUserVo userVo = new AttendanceGroupUserVo();
userVo.setUserId(vo.getId());
userVo.setRealName(vo.getUserName());
userVo.setNickname(vo.getNickname());
return userVo;
}).collect(Collectors.toList());
return ActionResult.success(userVoList);
}
@Operation(summary = "借调考勤组变动通知")
@GetMapping(value = "/userGroupUpdateBySecondNotice")
public ActionResult<Boolean> userGroupUpdateBySecondNotice(@RequestParam(value = "tenantId") String tenantId) {
try {
log.info("执行借调考勤组变动通知");
return ActionResult.success(attendanceUserService.userGroupUpdateBySecondNotice(tenantId));
} catch (Exception e) {
e.printStackTrace();
return ActionResult.success(Boolean.FALSE);
}
}
/**
* 指定考勤组成员列表
*
* @param id 考勤组Id
* @param isPermissions 是否权限控制
*/
@GetMapping("/getGroupUserList")
public ActionResult<List<AttendanceGroupUserVo>> getGroupUserList(@RequestParam(value = "id") String id,
@RequestParam(value = "isPermissions") Boolean isPermissions) {
List<AttendanceGroupUserVo> voList = attendanceUserService.getGroupUserList(id, isPermissions);
return ActionResult.success(voList);
}
/**
* 获取现在考勤组成员列表包含借调不含权限
* @param id 考勤组Id
*/
@GetMapping("/getGroupNowAllUserList")
public ActionResult<List<AttendanceGroupUserVo>> getGroupNowAllUserList(@RequestParam(value = "id") String id) {
List<AttendanceGroupUserVo> attendanceGroupUsersOfSecondment = attendanceUserService.getGroupNowAllUserList(id);
return ActionResult.success(attendanceGroupUsersOfSecondment);
}
/**
* 根据用户ID查询当前时间所在考勤组包含借调
*
* @param userId 用户ID
* @return ActionResult<List<AttendanceGroup>>
*/
@GetMapping("/getUserCurrentGroup")
public ActionResult<List<AttendanceGroup>> getUserCurrentGroup(@RequestParam String userId) {
Assert.isFalse(StrUtil.isBlank(userId), "用户ID不能为空");
List<AttendanceGroup> groups = attendanceGroupService.getUserCurrentGroups(userId);
return ActionResult.success(groups);
}
}

View File

@@ -0,0 +1,98 @@
package jnpf.attendance.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import jnpf.attendance.service.AttendanceHolidaySettingService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.AttendanceHolidaySettingDto;
import jnpf.model.attendance.dto.EnableUpdateDto;
import jnpf.model.attendance.vo.AttendanceHolidaySettingVo;
import jnpf.util.PageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 考勤配置-假日设置 前端控制器
* </p>
*
* @author ahua
* @since 2023-11-29
*/
@RestController
@RequestMapping("/holiday/setting")
public class AttendanceHolidaySettingController {
@Autowired
private AttendanceHolidaySettingService attendanceHolidaySettingService;
/**
* 分页查询节假日设置信息。
*
* @param groupId 考勤组ID
* @param name 节假日名称(可选)
* @param paidSalaryEnable 是否计入薪酬标识(可选)
* @param currentPage 当前页码
* @param pageSize 每页大小
* @return 分页的节假日设置视图对象列表
*/
@GetMapping
public ActionResult<PageListVO<AttendanceHolidaySettingVo>> page(@RequestParam String groupId,
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer paidSalaryEnable,
@RequestParam Integer currentPage,
@RequestParam Integer pageSize){
PageDTO<AttendanceHolidaySettingVo> page1 = attendanceHolidaySettingService.page(groupId, name, paidSalaryEnable, currentPage, pageSize);
return ActionResult.page(page1.getRecords(), PageUtil.page(page1));
}
/**
* 根据ID获取单个节假日设置信息。
*
* @param id 节假日设置ID
* @return 节假日设置视图对象
*/
@GetMapping("{id}")
public ActionResult<AttendanceHolidaySettingVo> getOne(@PathVariable String id){
return ActionResult.success(attendanceHolidaySettingService.getOne(id));
}
/**
* 删除指定的节假日设置信息。
*
* @param id 节假日设置ID
* @return 处理结果视图对象
*/
@DeleteMapping("{id}")
public ActionResult del(@PathVariable String id){
attendanceHolidaySettingService.del(id);
return ActionResult.success();
}
/**
* 保存节假日设置信息。
*
* @param dto 节假日设置数据传输对象
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult save(@RequestBody AttendanceHolidaySettingDto dto){
attendanceHolidaySettingService.save(dto);
return ActionResult.success();
}
/**
* 更改节假日设置信息的启用状态。
*
* @param enableUpdateDto 启用状态更新数据传输对象
* @return 处理结果视图对象
*/
@PutMapping("updateStatus")
public ActionResult changeStatus(@RequestBody EnableUpdateDto enableUpdateDto){
attendanceHolidaySettingService.changeStatus(enableUpdateDto.getId(), enableUpdateDto.getEnable());
return ActionResult.success();
}
}

View File

@@ -0,0 +1,191 @@
package jnpf.attendance.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.AttendanceLeaveRulesService;
import jnpf.attendance.service.AttendanceLeaveTypeService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.entity.attendance.AttendanceLeaveType;
import jnpf.enums.attendance.LeaveTypeEnum;
import jnpf.enums.attendance.LeaveUnitEnum;
import jnpf.exception.ApproveException;
import jnpf.model.attendance.dto.AttendanceLeaveRulesDto;
import jnpf.model.attendance.dto.AttendanceLeaveRulesQueryDto;
import jnpf.model.attendance.vo.attendance.AttendanceLeaveRulesVo;
import jnpf.model.attendance.vo.attendance.GroupMiniVo;
import jnpf.util.FtbUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 假期规则
*
* @author panpan
* @create 2025-09-18
*/
@RestController
@RequestMapping(value = "/attendance/leave-rules")
@Slf4j
public class AttendanceLeaveRulesController {
@Autowired
private AttendanceLeaveRulesService attendanceLeaveRulesService;
@Resource
private AttendanceLeaveTypeService attendanceLeaveTypeService;
/**
* 获取考勤假期管理列表
* @param attendanceLeaveRulesQueryDto 查询参数
*/
@GetMapping("/list")
public ActionResult<PageListVO<AttendanceLeaveRulesVo>> list(AttendanceLeaveRulesQueryDto attendanceLeaveRulesQueryDto) {
PageInfo<AttendanceLeaveRulesVo> vo = attendanceLeaveRulesService.list(attendanceLeaveRulesQueryDto);
return ActionResult.page(vo.getList(), FtbUtil.getPagination(vo));
}
/**
* 新增假期
* @param attendanceLeaveRulesDto 假期
*/
@PostMapping()
public ActionResult<Void> add(@RequestBody AttendanceLeaveRulesDto attendanceLeaveRulesDto) throws Exception {
attendanceLeaveRulesService.create(attendanceLeaveRulesDto);
return ActionResult.success();
}
/**
* 修改假期
* @param attendanceLeaveRulesDto 假期
*/
@PutMapping("/{id}")
public ActionResult<Void> put(@PathVariable("id") String id, @RequestBody AttendanceLeaveRulesDto attendanceLeaveRulesDto) throws Exception {
attendanceLeaveRulesDto.setId(id);
attendanceLeaveRulesService.update(attendanceLeaveRulesDto);
return ActionResult.success();
}
/**
* 获取假期详情
* @param id 假期 ID
*/
@GetMapping("/{id}")
public ActionResult<AttendanceLeaveRulesVo> detail(@PathVariable("id") String id) {
return ActionResult.success(attendanceLeaveRulesService.detail(id));
}
/**
* 删除假期
* @param id 假期 ID
*/
@DeleteMapping("/{id}")
public ActionResult<Void> delete(@PathVariable("id") String id) {
attendanceLeaveRulesService.delete(id);
return ActionResult.success();
}
/**
* 启用、停用假期
* @param attendanceLeaveRulesDto 假期
*/
@PutMapping("/updateState")
public ActionResult<Void> updateState( @RequestBody AttendanceLeaveRulesDto attendanceLeaveRulesDto) {
attendanceLeaveRulesService.updateState(attendanceLeaveRulesDto);
return ActionResult.success();
}
/**
* 获取用户请假列表
*/
@GetMapping("/getUserLeaveList")
public ActionResult<List<AttendanceLeaveRulesVo>> getUserLeaveList(@RequestParam(required = false) boolean isFromOaCondition) {
if (isFromOaCondition){
List<AttendanceLeaveType> list = attendanceLeaveTypeService.list(new LambdaQueryWrapper<AttendanceLeaveType>()
.eq(AttendanceLeaveType::getDeleteMark, 0)
.orderByAsc(AttendanceLeaveType::getCreatorTime));
return ActionResult.success(list.stream().map(vo->{
AttendanceLeaveRulesVo attendanceLeaveRulesVo = new AttendanceLeaveRulesVo();
attendanceLeaveRulesVo.setLeaveTypeId(vo.getId());
attendanceLeaveRulesVo.setLeaveTypeName(vo.getName());
return attendanceLeaveRulesVo;
}).collect(Collectors.toList()));
}
return ActionResult.success(attendanceLeaveTypeService.getUserLeaveList());
}
/**
* 获取用户请假详情
*/
@GetMapping("/getUserLeaveDetail")
public ActionResult<List<AttendanceLeaveRulesVo>> getUserLeaveDetail(AttendanceLeaveType attendanceLeaveType ,@RequestParam(required = false) boolean isFromOaCondition) throws ApproveException {
if (isFromOaCondition) {
List<AttendanceLeaveRulesVo> list = new ArrayList<>();
for (LeaveTypeEnum value : LeaveTypeEnum.values()) {
AttendanceLeaveRulesVo attendanceLeaveRulesVo = new AttendanceLeaveRulesVo();
attendanceLeaveRulesVo.setUnit(value.getCode());
attendanceLeaveRulesVo.setUnitName(value.getMsg());
list.add(attendanceLeaveRulesVo);
}
return ActionResult.success(list);
}
AttendanceLeaveRulesVo vo = attendanceLeaveTypeService.getUserLeaveDetail(attendanceLeaveType.getId(),null);
List<AttendanceLeaveRulesVo> list = new ArrayList<>();
list.add(vo);
return ActionResult.success(list);
}
/**
* 获取用户请假详情
*/
@GetMapping("/getLeaveDetailForOa")
public ActionResult<AttendanceLeaveRulesVo> getLeaveDetailForOa(AttendanceLeaveType attendanceLeaveType) throws ApproveException {
return ActionResult.success(attendanceLeaveTypeService.getUserLeaveDetail(attendanceLeaveType.getId(),null));
}
/**
* 获取请假类型列表
*/
@GetMapping("/type/list")
public ActionResult<List<AttendanceLeaveType>> leaveTypeList(@RequestParam(value = "keyword", required = false) String keyword) {
List<AttendanceLeaveType> vo = attendanceLeaveTypeService.list(new LambdaQueryWrapper<AttendanceLeaveType>()
.eq(AttendanceLeaveType::getDeleteMark, 0)
.like(StringUtil.isNotEmpty(keyword), AttendanceLeaveType::getName, keyword)
.orderByAsc(AttendanceLeaveType::getCreatorTime));
return ActionResult.success(vo);
}
/**
* 新增/修改假期类型
* @param attendanceLeaveType 假期类型
*/
@PostMapping("/type")
public ActionResult<Void> typeSaveOrUpdate(@RequestBody AttendanceLeaveType attendanceLeaveType) {
attendanceLeaveTypeService.typeSaveOrUpdate(attendanceLeaveType);
return ActionResult.success();
}
/**
* 删除假期类型
* @param id 假期类型Id
*/
@DeleteMapping("/type/{id}")
public ActionResult<Void> deleteType(@PathVariable("id") String id) {
attendanceLeaveTypeService.deleteType(id);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,107 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.AttendanceLineSchedulingConfigService;
import jnpf.base.ActionResult;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.attendance.FtbAttendanceLineSchedulingConfig;
import jnpf.permission.V2PositionApi;
import jnpf.permission.dto.v2.position.QueryBasePositionBatchDTO;
import jnpf.permission.vo.v2.position.PositionBaseInfoVO;
import jnpf.util.StringUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* 划线排班配置控制器
*
* @author ahua
* @version 2.1
* @copyright 引迈信息技术有限公司https://www.jnpfsoft.com
* @date 2025-12-30
*/
@RestController
@RequestMapping(value = "/arranging/line/scheduling/config")
public class AttendanceLineSchedulingConfigController {
@Resource
private AttendanceLineSchedulingConfigService attendanceLineSchedulingConfigService;
@Resource
private V2PositionApi v2PositionApi;
@Resource
private AttendanceGroupService attendanceGroupService;
/**
* 根据考勤组ID获取划线排班配置详情
*
* @param groupId 考勤组ID
* @return 划线排班配置详情
*/
@GetMapping("detail")
public ActionResult<FtbAttendanceLineSchedulingConfig> detail(@RequestParam(value = "groupId") String groupId) {
FtbAttendanceLineSchedulingConfig config = attendanceLineSchedulingConfigService.getByGroupId(groupId);
return ActionResult.success(config);
}
/**
* 划线排班未排班通知
*
* @param tenantId 租户id
* @return 划线排班配置详情
*/
@GetMapping("noticeLineScheduling")
public ActionResult noticeLineScheduling(@RequestParam(value = "tenantId") String tenantId) {
attendanceLineSchedulingConfigService.noticeLineScheduling(tenantId);
return ActionResult.success();
}
/**
* 保存或更新划线排班配置
*
* @param config 划线排班配置
* @return 保存结果
*/
@PostMapping("saveOrUpdate")
public ActionResult saveOrUpdate(@RequestBody FtbAttendanceLineSchedulingConfig config) {
boolean result = attendanceLineSchedulingConfigService.saveOrUpdateLineSchedulingConfig(config);
return ActionResult.success(result);
}
/**
* 根据考勤组ID获取岗位基础信息列表
*
* @param groupId 考勤组ID
* @return 岗位基础信息列表
*/
@Operation(summary = "[列表]根据考勤组ID获取岗位基础信息列表")
@GetMapping("/position/base/list")
public ActionResult<List<PositionBaseInfoVO>> listPositionBaseInfo(@RequestParam(value = "groupId") String groupId) {
// 验证groupId
if (StringUtil.isBlank(groupId)) {
return ActionResult.fail("考勤组ID不能为空");
}
// 查询AttendanceGroup
AttendanceGroup attendanceGroup = attendanceGroupService.queryByGroupId(groupId);
if (attendanceGroup == null) {
return ActionResult.fail("未找到指定的考勤组");
}
// 获取orgId
String orgId = attendanceGroup.getOrgId();
if (StringUtil.isBlank(orgId)) {
return ActionResult.fail("考勤组未绑定组织");
}
// 创建DTO并设置参数
QueryBasePositionBatchDTO dto = new QueryBasePositionBatchDTO();
dto.setOrganizeIds(Collections.singletonList(orgId));
// 调用Feign客户端获取岗位基础信息
return v2PositionApi.listPositionBaseInfo(dto);
}
}

View File

@@ -0,0 +1,122 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceLocationSettingService;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceLocationSettingDto;
import jnpf.model.attendance.dto.SaveForStoreDto;
import jnpf.model.attendance.vo.AttendanceGroupVo;
import jnpf.model.attendance.vo.AttendanceLocationSettingVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* <p>
* 考勤组-考勤点配置表 前端控制器
* </p>
*
* @author ahua
* @since 2023-11-29
*/
@RestController
@RequestMapping("/location/setting")
public class AttendanceLocationSettingController {
@Autowired
private AttendanceLocationSettingService attendanceLocationSettingService;
/**
* 根据考勤组ID和类型查询考勤地点设置列表。
*
* @param groupId 考勤组ID
* @param type 类型(用于筛选设置)
* @return 考勤地点设置视图对象列表
*/
@GetMapping
public ActionResult<List<AttendanceLocationSettingVo>> findList(@RequestParam String groupId, @RequestParam Integer type) {
return ActionResult.success(attendanceLocationSettingService.findList(groupId, type));
}
/**
* 根据门店信息保存考勤点数据
* @param saveForStore
*/
@PostMapping("saveForStore")
public ActionResult saveForStore(@RequestBody SaveForStoreDto saveForStore){
attendanceLocationSettingService.saveForStore(saveForStore);
return ActionResult.success();
}
/**
* 根据门店id集合删除考勤点信息
* @param saveForStore
*/
@DeleteMapping("delForStore")
public ActionResult delForStore(@RequestBody SaveForStoreDto saveForStore){
attendanceLocationSettingService.delForStore(saveForStore);
return ActionResult.success();
}
/**
* 获取当前考勤组考勤点关联门店id
* @param groupId
* @return
*/
@GetMapping("getStoreIds")
public ActionResult<List<String>> getStoreIds(@RequestParam String groupId){
return ActionResult.success(attendanceLocationSettingService.getStoreIds(groupId));
}
/**
* 获取启用的考勤组信息。
*
* @param groupId 考勤组ID
* @return 启用的考勤组视图对象列表
*/
@GetMapping("getEnableGroups")
public ActionResult<List<AttendanceGroupVo>> getEnableGroups(@RequestParam String groupId) {
return ActionResult.success(attendanceLocationSettingService.getEnableGroup(groupId));
}
/**
* 保存考勤地点设置信息。
*
* @param dto 考勤地点设置数据传输对象
* @throws HandleException 处理异常
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult save(@RequestBody AttendanceLocationSettingDto dto) throws HandleException {
attendanceLocationSettingService.save(dto);
return ActionResult.success();
}
/**
* 删除指定的考勤地点设置信息。
*
* @param id 考勤地点设置ID
* @return 处理结果视图对象
*/
@DeleteMapping("{id}")
public ActionResult del(@PathVariable String id) {
attendanceLocationSettingService.del(id);
return ActionResult.success();
}
/**
* 更改考勤地点设置的启用状态。
*
* @param groupId 考勤组ID
* @param type 类型(用于区分设置类型)
* @param enable 启用状态0禁用1启用
* @return 处理结果视图对象
*/
@PutMapping("changeStatus")
public ActionResult changeStatus(@RequestParam String groupId, @RequestParam Integer type, @RequestParam Integer enable) {
attendanceLocationSettingService.changeStatus(groupId, type, enable);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,163 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.AttendanceMachineManageService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.dto.PageDto;
import jnpf.model.attendance.dto.MachineDto;
import jnpf.model.attendance.dto.MachineQueryDto;
import jnpf.model.attendance.dto.MachineUpdateDto;
import jnpf.model.attendance.vo.AttendanceMachineManageVo;
import jnpf.model.attendance.vo.MachineScopeVo;
import jnpf.model.attendance.vo.attendance.GroupMiniVo;
import jnpf.model.attendance.vo.attendance.GroupUserMiniVo;
import jnpf.model.attendance.vo.attendance.LogMiniVo;
import jnpf.util.FtbUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤机管理
*
* @author yanwenfu
* @create 2024-09-10
*/
@RestController
@RequestMapping(value = "/machineManage")
public class AttendanceMachineManageController {
@Resource
private AttendanceMachineManageService attendanceMachineManageService;
/**
* 考勤机管理 - 考勤组列表
* @param machineId 考勤机id
* @return java.lang.Object
*/
@GetMapping(value = "/group/list")
public Object getGroupList(@RequestParam(required = false) String machineId) {
List<GroupMiniVo> list = attendanceMachineManageService.getGroupList(machineId);
return ActionResult.success(list);
}
/**
* 考勤机管理 - 考勤机列表
* @param queryDto 查询条件
* @return java.lang.Object
*/
@GetMapping(value = "/machine/list")
public Object getMachineList(MachineQueryDto queryDto) {
PageListVO<AttendanceMachineManageVo> page = attendanceMachineManageService.getMachineList(queryDto);
return ActionResult.page(page.getList(), page.getPagination());
}
/**
* 考勤机管理 - 查询考勤机更新信息
* @param id 考勤机id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.MachineScopeVo>
*/
@GetMapping(value = "/update-detail/{id}")
public ActionResult<MachineScopeVo> getUpdateDetail(@PathVariable(value = "id") String id) {
MachineScopeVo machineScope = attendanceMachineManageService.getUpdateDetail(id);
return ActionResult.success(machineScope);
}
/**
* 考勤机管理 - 添加考勤机
* @param machineDto 考勤机信息
* @return java.lang.Object
*/
@PostMapping(value = "/machine")
public Object addMachine(@RequestBody @Valid MachineDto machineDto) throws Exception {
attendanceMachineManageService.addMachine(machineDto);
return ActionResult.success();
}
/**
* 考勤机管理 - 编辑考勤机
* @param id 考勤机id
* @param machineUpdateDto 考勤机信息
* @return java.lang.Object
*/
@PutMapping(value = "/machine/{id}")
public Object updateMachine(@PathVariable(value = "id") String id, @RequestBody @Valid MachineUpdateDto machineUpdateDto) throws Exception {
attendanceMachineManageService.updateMachine(id, machineUpdateDto);
return ActionResult.success();
}
/**
* 考勤机管理 - 移除
* @param id 考勤机id
* @return java.lang.Object
*/
@DeleteMapping(value = "/machine/{id}")
public Object deleteMachine(@PathVariable(value = "id") String id) throws Exception {
attendanceMachineManageService.deleteMachine(id);
return ActionResult.success();
}
/**
* 考勤机管理 - 考勤组成员列表
* @param groupId 考勤组id
* @return java.lang.Object
*/
@GetMapping(value = "/group/user/{groupId}")
public Object getGroupUserList(@PathVariable(value = "groupId") String groupId) {
List<GroupUserMiniVo> list = attendanceMachineManageService.getGroupUserList(groupId);
return ActionResult.success(list);
}
@GetMapping(value = "/group/user")
public Object getGroupUserList2(@RequestParam(value = "groupId") String groupId) {
List<GroupUserMiniVo> list = attendanceMachineManageService.getGroupUserList(groupId);
return ActionResult.success(list);
}
/**
* 同步考勤机成员
* @param id 考勤机id
* @return java.lang.Object
*/
@PutMapping("/sync/{id}")
public Object syncMachineMemberData(@PathVariable(value = "id") String id) {
List<GroupUserMiniVo> list = attendanceMachineManageService.syncMachineMemberData(id);
return ActionResult.success(list);
}
/**
* 考勤机管理 - 人脸库
* @param id 考勤机id
* @return java.lang.Object
*/
@GetMapping(value = "/member/list/{id}")
public Object getMachineMemberList(@PathVariable(value = "id") String id) {
List<GroupUserMiniVo> list = attendanceMachineManageService.getMachineMemberList(id);
return ActionResult.success(list);
}
/**
* 查询考勤机打卡记录(查往前3个月的数据)
* @param id 考勤机id
* @return java.lang.Object
*/
@GetMapping(value = "/record/list/{id}")
public ActionResult<PageListVO<LogMiniVo>> getLogList(@PathVariable(value = "id") String id, PageDto pageQuery) {
PageInfo<LogMiniVo> page = attendanceMachineManageService.getLogList(id, pageQuery);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
}

View File

@@ -0,0 +1,64 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.service.AttendanceNoticeService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.NoticeContentInfoDto;
import jnpf.model.attendance.vo.NoticeConfirmListVo;
import jnpf.model.attendance.vo.NoticeContentInfoVo;
import jnpf.util.CustomTenantUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.websocket.server.PathParam;
/**
* 考勤消息通知
*
* @author shitou
* @email shitou@niujiekeji.com
* @date 2024-08-08 10:49:41
*/
@Slf4j
@RestController
@RequestMapping("/attendance/notice")
public class AttendanceNoticeController {
@Resource
private CustomTenantUtil customTenantUtil;
@Autowired
private AttendanceNoticeService attendanceNoticeService;
@Operation(summary = "内容详情")
@GetMapping(value = "/getContentInfo")
public ActionResult<NoticeContentInfoVo> getContentInfo(@Valid NoticeContentInfoDto req) {
try {
log.info("内容详情,入参=>{}", JSONObject.toJSON(req));
long st = System.currentTimeMillis();
NoticeContentInfoVo data = attendanceNoticeService.getContentInfo(req);
long ent = System.currentTimeMillis();
log.info("内容详情,耗时=>{}", ent - st + " 毫秒");
return ActionResult.success(data);
} catch (Exception e) {
e.printStackTrace();
return ActionResult.fail(e.getMessage());
}
}
@Operation(summary = "考勤通知-确认")
@PostMapping(value = "/confirm/{id}")
public ActionResult noticeConfirm(@PathVariable("id") String id) {
attendanceNoticeService.noticeConfirm(id);
return ActionResult.success();
}
@Operation(summary = "考勤通知-确认列表")
@GetMapping(value = "confirmList/{id}")
public ActionResult<NoticeConfirmListVo> getNoticeConfirmList(@PathVariable("id") String id) {
return ActionResult.success(attendanceNoticeService.getNoticeConfirmList(id));
}
}

View File

@@ -0,0 +1,71 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendancePermissionDictService;
import jnpf.base.ActionResult;
import jnpf.entity.PermissionDict;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.SavePermissionDto;
import jnpf.model.attendance.vo.PermissionDictVo;
import jnpf.util.ParamUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 权限字典管理
*/
@RestController
@RequestMapping("/dict")
public class AttendancePermissionDictController {
@Resource
private AttendancePermissionDictService attendancePermissionDictService;
/**
* 保存权限字典信息
* @param savePermissionDto
* @return ActionResult<Void>
* @throws Exception
*/
@PostMapping
public ActionResult<Void> save(@RequestBody SavePermissionDto savePermissionDto) throws Exception {
ParamUtil.checkParam(savePermissionDto);
attendancePermissionDictService.save(savePermissionDto);
return ActionResult.success();
}
/**
* 权限字典查询列表
* @param moduleType
* @return ActionResult<List<PermissionDictVo>>
*/
@GetMapping
public ActionResult<List<PermissionDictVo>> query(Integer moduleType) {
List<PermissionDictVo> result = attendancePermissionDictService.queryPermissionDictList(moduleType);
return ActionResult.success(result);
}
/**
* 根据id删除权限字典
* @param id 权限字典id
* @return ActionResult<Void>
*/
@DeleteMapping("/{id}")
public ActionResult<Void> delete(@PathVariable String id) {
attendancePermissionDictService.delete(id);
return ActionResult.success();
}
/**
* 权限详情
* @param id
* @return ActionResult<PermissionDict>
*/
@GetMapping("/{id}")
public ActionResult<PermissionDict> detail(@PathVariable String id) {
PermissionDict permissionDict = attendancePermissionDictService.detail(id);
return ActionResult.success(permissionDict);
}
}

View File

@@ -0,0 +1,100 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceQuickTemplateService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.FixedClassGroupDto;
import jnpf.model.attendance.dto.QuickTemDto;
import jnpf.model.attendance.dto.QuickTemplateDto;
import jnpf.model.attendance.vo.AttendanceQuickTemplateVo;
import jnpf.model.attendance.vo.attendance.QuickTemVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* <p>
* 考勤配置-快速模板 前端控制器
* </p>
*
* @author ahua
* @since 2023-11-28
*/
@RestController
@RequestMapping("/quick/template")
public class AttendanceQuickTemplateController {
@Autowired
private AttendanceQuickTemplateService attendanceQuickTemplateService;
/**
* 根据考勤组ID查询快速模板列表。
*
* @param groupId 考勤组ID
* @return 快速模板视图对象列表
*/
@GetMapping
public ActionResult<List<AttendanceQuickTemplateVo>> findList(@RequestParam String groupId) {
List<AttendanceQuickTemplateVo> list = attendanceQuickTemplateService.findList(groupId);
return ActionResult.success(list);
}
/**
* 根据快速模板ID查询单个快速模板。
*
* @param id 快速模板ID
* @return 快速模板视图对象
*/
@GetMapping("{id}")
public ActionResult<AttendanceQuickTemplateVo> findOne(@PathVariable String id) {
return ActionResult.success(attendanceQuickTemplateService.findOne(id));
}
/**
* 保存快速模板信息。
*
* @param quickTemplateDto 快速模板数据传输对象
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult save(@RequestBody QuickTemplateDto quickTemplateDto) {
attendanceQuickTemplateService.save(quickTemplateDto);
return ActionResult.success();
}
/**
* 删除指定的快速模板信息。
*
* @param id 快速模板ID
* @return 处理结果视图对象
*/
@DeleteMapping("{id}")
public ActionResult del(@PathVariable String id) {
attendanceQuickTemplateService.del(id);
return ActionResult.success();
}
/**
* 新版考勤快速排班模板
* @param dto 快速排班信息
*/
@PostMapping("/new")
public ActionResult saveNew(@RequestBody @Valid QuickTemDto dto) {
attendanceQuickTemplateService.saveOrUpdateNew(dto);
return ActionResult.success();
}
/**
* 新版考勤快速排班列表
* @param groupId 考勤组Id
*/
@GetMapping("/new")
public ActionResult<List<QuickTemVo>> findNewList(@RequestParam String groupId) {
List<QuickTemVo> list = attendanceQuickTemplateService.findNewList(groupId);
return ActionResult.success(list);
}
}

View File

@@ -0,0 +1,89 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.AttendanceShiftNameSettingService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.FixedClassChangeStatusDto;
import jnpf.model.attendance.dto.FixedClassGroupDto;
import jnpf.model.attendance.dto.ShiftNameDto;
import jnpf.model.attendance.dto.ShiftNameQueryDto;
import jnpf.model.attendance.vo.attendance.ShiftNameListVo;
import jnpf.model.attendance.vo.attendance.ShiftNameVo;
import jnpf.util.FtbUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 考勤组配置-考勤班制 前端控制器
* @Author huanglinpan
* @Date 2024/5/9 10:01
* @Version 1.0 (版本号)
*/
@RestController
@RequestMapping("/shifts/shiftName")
public class AttendanceShiftNameSettingController {
@Resource
private AttendanceShiftNameSettingService attendanceShiftNameSettingService;
/**
* 新增/修改班次名称
* @param dto 班次名称信息
*/
@PostMapping
public ActionResult save(@RequestBody @Valid ShiftNameDto dto) throws HandleException {
return attendanceShiftNameSettingService.save(dto);
}
/**
* 起停班次
* @param statusDto 班次状态修改dto
*/
@PutMapping("/changeStatus")
public ActionResult changeStatus(@RequestBody FixedClassChangeStatusDto statusDto) {
return attendanceShiftNameSettingService.changeStatus(statusDto.getShiftNameId(), statusDto.getEnable(), statusDto.getUpdateShift());
}
/**
* 删除班次
* @param shiftNameId 班次名称Id
*/
@DeleteMapping("/{shiftNameId}")
public ActionResult delete(@PathVariable("shiftNameId") String shiftNameId) {
return attendanceShiftNameSettingService.delete(shiftNameId);
}
/**
* 获取班次详情
* @param shiftNameId 班次名称Id
*/
@GetMapping("{shiftNameId}")
public ActionResult<ShiftNameVo> getDetail(@PathVariable("shiftNameId") String shiftNameId) {
return ActionResult.success(attendanceShiftNameSettingService.getDetail(shiftNameId));
}
/**
* 班次列表
* @param queryDto 查询条件
* @return
*/
@GetMapping("/list")
public ActionResult<PageListVO<ShiftNameListVo>> getList(@Valid ShiftNameQueryDto queryDto) throws HandleException {
PageInfo<ShiftNameListVo> list = attendanceShiftNameSettingService.getList(queryDto);
return ActionResult.page(list.getList(), FtbUtil.getPagination(list));
}
/**
* 保存考勤组固定排班
* @param dto 固定排班对象
*/
@PutMapping("/fixedClass")
public ActionResult saveOrUpdateFixedClass(@RequestBody @Valid FixedClassGroupDto dto) {
return attendanceShiftNameSettingService.saveOrUpdateFixedClass(dto);
}
}

View File

@@ -0,0 +1,99 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceShiftSettingService;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceShiftDto;
import jnpf.model.attendance.vo.AttendanceShiftSettingVo;
import jnpf.model.attendance.vo.ShiftPeriodVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* <p>
* 考勤组配置-考勤配置 前端控制器
* </p>
*
* @author ahua
* @since 2023-11-22
*/
@RestController
@RequestMapping("/shifts/setting")
public class AttendanceShiftSettingController {
@Autowired
private AttendanceShiftSettingService attendanceShiftSettingService;
/**
* 根据考勤组ID查询班次设置。
*
* @param groupId 考勤组ID
* @return 班次设置视图对象
*/
@GetMapping("{groupId}")
public ActionResult<AttendanceShiftSettingVo> findByGroupId(@PathVariable("groupId") String groupId) {
return ActionResult.success(attendanceShiftSettingService.findByGroupId(groupId));
}
/**
* 保存班次设置信息。
*
* @param dto 班次设置数据传输对象
* @throws HandleException 处理异常
* @return 处理结果视图对象
*/
@PostMapping
public ActionResult save(@RequestBody AttendanceShiftDto dto) throws HandleException {
attendanceShiftSettingService.save(dto);
return ActionResult.success();
}
/**
* 删除指定的班次周期。
*
* @param periodId 班次周期ID
* @return 处理结果视图对象
*/
@DeleteMapping("/period/{periodId}")
public ActionResult delPeriod(@PathVariable("periodId") String periodId) {
attendanceShiftSettingService.delPeriod(periodId);
return ActionResult.success();
}
/**
* 更改班次设置的启用状态。
*
* @param groupId 考勤组ID
* @param enable 启用状态0禁用1启用
* @return 处理结果视图对象
*/
@PutMapping("changeStatus")
public ActionResult changeStatus(@RequestParam String groupId, @RequestParam Integer enable) {
attendanceShiftSettingService.changeStatus(groupId, enable, null);
return ActionResult.success();
}
/**
* 根据考勤组ID获取班次时段列表。
*
* @param groupId 考勤组ID
* @return 班次周期视图对象列表
*/
@GetMapping("/period/list")
public ActionResult<List<ShiftPeriodVo>> periodList(@RequestParam("groupId") String groupId) {
return ActionResult.success(attendanceShiftSettingService.periodList(groupId));
}
/**
* 自我排班的班次列表
*
* @return
*/
@GetMapping("/self/period/list")
public ActionResult<List<ShiftPeriodVo>> periodListForSelfScheduling() {
return ActionResult.success(attendanceShiftSettingService.periodListForSelfScheduling());
}
}

View File

@@ -0,0 +1,76 @@
package jnpf.attendance.controller;
import cn.hutool.core.collection.CollUtil;
import jnpf.attendance.AttendanceSimulateDataApi;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.attendance.service.UserFaceService;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AttendanceMachineManage;
import jnpf.util.NoDataSourceBind;
import jnpf.util.UserProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 考勤打卡控制器
*
* @author yanwenfu
* @create 2023-11-21
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/simulateData")
public class AttendanceSimulatedDataController implements AttendanceSimulateDataApi {
@Resource
private AttenceMachineService attendanceClockInService;
@Autowired
private AttendanceUserService attendanceUserService;
@Autowired
private UserFaceService userFaceService;
/**
* 打卡
* @return java.lang.Object
*/
@GetMapping(value = "/clockIn")
@NoDataSourceBind
public void clockIn(@RequestParam(value = "tenantId") String tenantId) throws Exception {
Assert.notNull(tenantId, "租户ID不能为空");
TenantDataSourceUtil.switchTenant(tenantId);
List<AttendanceGroupUser> attendanceGroupUsers = attendanceUserService.queryByUsersAndGroupFilterSecondment(new Date(), new Date(), null, null);
UserProvider.getUser().setTenantId(tenantId);
Map<String, List<AttendanceMachineManage>> userFaceList = userFaceService.getUserFaceList(attendanceGroupUsers.stream().map(AttendanceGroupUser::getUserId).distinct().collect(Collectors.toList()), tenantId);
attendanceGroupUsers.forEach(attendanceGroupUser -> {
try {
List<AttendanceMachineManage> attendanceMachineManages = userFaceList.get(attendanceGroupUser.getUserId());
if(CollUtil.isEmpty(attendanceMachineManages)){
return;
}
AttendanceMachineManage attendanceMachineManage = attendanceMachineManages.stream().findFirst().orElse(null);
if(Objects.isNull(attendanceMachineManage)){
return;
}
attendanceClockInService.clockIn(attendanceMachineManage.getMac(), attendanceGroupUser.getUserId(), tenantId);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}

View File

@@ -0,0 +1,242 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceApprovalSettingService;
import jnpf.attendance.service.AttendanceSuperAdminService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.AttendanceManagerDetailVo;
import jnpf.model.attendance.vo.AttendanceUserVo;
import jnpf.model.attendance.vo.CurUserPermissionVo;
import jnpf.model.attendance.vo.permission.ActionPermissionVo;
import jnpf.model.attendance.vo.permission.ApprovalSettingVo;
import jnpf.model.attendance.vo.permission.AttendanceTeamSetVo;
import jnpf.model.attendance.vo.permission.app.ManagerPermissionVo;
import jnpf.model.authority.vo.role.FtbPermissionPositionMenuVO;
import jnpf.util.ParamUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* APP全局设置
* @author yier
* @Time 2023-11-23
*/
@RestController
@RequestMapping("/permission")
public class AttendanceSuperAdminController {
@Resource
private AttendanceSuperAdminService attendanceSuperAdminService;
@Resource
private AttendanceApprovalSettingService attendanceApprovalSettingService;
/**
* 添加考勤组超级管理员
* @param saveSuperAdminDto 保存信息
* @return Void
* @throws Exception 抛出异常
*/
@PostMapping
public ActionResult<Void> add(@RequestBody SaveSuperAdminDto saveSuperAdminDto) throws Exception {
ParamUtil.checkParam(saveSuperAdminDto);
attendanceSuperAdminService.add(saveSuperAdminDto);
return ActionResult.success();
}
/**
* 删除超级管理员
* @param userIds 用户id
* @return ActionResult<Void>
*/
@DeleteMapping
public ActionResult<Void> delete(@RequestParam List<String> userIds) {
attendanceSuperAdminService.delete(userIds);
return ActionResult.success();
}
/**
* 获取考勤超级管理员
* @return ActionResult<List<UserEntity>>
*/
@GetMapping
public ActionResult<List<AttendanceUserVo>> getSuperAdmin(String name) {
return ActionResult.success(attendanceSuperAdminService.querySuperAdmin(name));
}
/**
* 添加考勤组管理员
* @param saveGroupAdmin
* @return ActionResult<Void>
*/
@PostMapping("/addGroupAdmin")
public ActionResult<Void> addGroupAdmin(@RequestBody SaveGroupAdmin saveGroupAdmin) throws Exception {
ParamUtil.checkParam(saveGroupAdmin);
attendanceSuperAdminService.addGroupAdmin(saveGroupAdmin);
return ActionResult.success();
}
/**
* 批量添加考勤组管理员
* @param groupAdminList
* @return ActionResult<Void>
*/
@PostMapping("/batchAddGroupAdmin")
public ActionResult<Void> batchAddGroupAdmin(@RequestBody BatchSaveGroupAdmin groupAdminList) throws Exception {
ParamUtil.checkParam(groupAdminList);
attendanceSuperAdminService.batchAddGroupAdmin(groupAdminList);
return ActionResult.success();
}
/**
* 修改考勤组管理员权限
* @param saveGroupAdmin
* @return
* @throws Exception
*/
@PutMapping("/updateGroupAdmin")
public ActionResult<Void> updateGroupAdmin(@RequestBody SaveGroupAdmin saveGroupAdmin) throws Exception {
ParamUtil.checkParam(saveGroupAdmin);
attendanceSuperAdminService.updateGroupAdmin(saveGroupAdmin);
return ActionResult.success();
}
/**
* 删除考勤组管理员
* @param saveGroupAdmin
* @return ActionResult<Void>
*/
@DeleteMapping("/deleteGroupAdmin")
public ActionResult<Void> deleteGroupAdmin(@RequestBody SaveGroupAdmin saveGroupAdmin) {
attendanceSuperAdminService.deleteGroupAdmin(saveGroupAdmin);
return ActionResult.success();
}
/**
* 查询考勤组管理员列表
* @param groupId 考勤组id
* @return ActionResult<List<AttendanceGroupAdminVo>>
*/
@GetMapping("/listGroupAdmin")
public ActionResult<Map<String, Object>> listGroupAdmin(String groupId) {
Map<String, Object> result = attendanceSuperAdminService.listGroupAdmin(groupId);
return ActionResult.success(result);
}
/**
* 获取当前登录用户权限
*/
@GetMapping("/getCurPermission")
public ActionResult<CurUserPermissionVo> getCurPermission(String groupId) {
CurUserPermissionVo curUserPermissionVo = attendanceSuperAdminService.getByUserId(groupId);
return ActionResult.success(curUserPermissionVo);
}
/**
* 修改审批设置
* @param attendanceApprovalSettingDto
* @return ActionResult<Void>
*/
@PostMapping("/updateApprovalSetting")
public ActionResult<Void> updateApprovalSetting(@RequestBody AttendanceApprovalSettingDto attendanceApprovalSettingDto) {
attendanceApprovalSettingService.update(attendanceApprovalSettingDto);
return ActionResult.success();
}
/**
* 管理员权限详情
* @param groupId
* @param userId
* @return ActionResult<List<PermissionDictVo>>
*/
@GetMapping("/adminDetail")
public ActionResult<AttendanceManagerDetailVo> adminDetail(String groupId, String userId) {
AttendanceManagerDetailVo dictVos = attendanceSuperAdminService.adminDetail(groupId, userId);
return ActionResult.success(dictVos);
}
/**
* 是否有查看权限
* @return ActionResult
*/
@GetMapping("/isView")
public ActionResult<Boolean> isView(String groupId) {
Boolean viewPermission = attendanceSuperAdminService.isViewPermission(groupId);
return ActionResult.success(viewPermission);
}
/**
* 获取操作权限
* @return ActionResult<ActionPermissionVo>
*/
@GetMapping("/actionPermission")
public ActionResult<ActionPermissionVo> actionPermission(String groupId) {
ActionPermissionVo actionPermission = attendanceSuperAdminService.getActionPermission(groupId);
return ActionResult.success(actionPermission);
}
/**
* 是否有全局设置权限
* @return ActionResult<Boolean>
*/
@GetMapping("/isGlobal")
public ActionResult<Boolean> isGlobal() {
Boolean globalSetting = attendanceSuperAdminService.isGlobalSetting();
return ActionResult.success(globalSetting);
}
/**
* 是否是考勤组管理员
* @return ActionResult<Boolean>
*/
@GetMapping("/isManager")
public ActionResult<ManagerPermissionVo> isManager() {
ManagerPermissionVo manager = attendanceSuperAdminService.isManager();
return ActionResult.success(manager);
}
/**
* 获取考勤组审批设置
* @return ActionResult<ApprovalSettingVo>
*/
@GetMapping("/getGroupApprovalPermission")
public ActionResult<ApprovalSettingVo> getGroupApprovalPermission(@RequestParam("groupId") String groupId,
@RequestParam("type") Integer type) {
ApprovalSettingVo approvalSettingInfo = attendanceSuperAdminService.getApprovalSettingInfo(groupId, type);
return ActionResult.success(approvalSettingInfo);
}
/**
* 根据用户id获取所属权限集合
* @param userIds 用户id
* @return FtbPermissionPositionMenuInnerVO
*/
@PostMapping("/queryPermissionListByUserIds")
public ActionResult<List<FtbPermissionPositionMenuVO.FtbPermissionPositionMenuInnerVO>> queryPermissionListByUserIds(@RequestBody List<String> userIds) {
List<FtbPermissionPositionMenuVO.FtbPermissionPositionMenuInnerVO> voList = attendanceSuperAdminService.queryPermissionListByUserIds(userIds);
return ActionResult.success(voList);
}
/**
* 获取考勤组团队设置
* @return ActionResult<AttendanceTeamSetVo>
*/
@GetMapping("/getTeamSet")
public ActionResult<AttendanceTeamSetVo> getTeamSet(@Valid GroupFilterDto dto) {
return ActionResult.success(attendanceSuperAdminService.getTeamSet(dto));
}
/**
* 考勤组月报通知开启/关闭
* @return ActionResult<Boolean>
*/
@PutMapping("/setMonthNotice")
public ActionResult<Boolean> setMonthNotice(@Valid @RequestBody GroupFilterDto dto) {
return ActionResult.success(attendanceSuperAdminService.setMonthNotice(dto));
}
}

View File

@@ -0,0 +1,71 @@
package jnpf.attendance.controller;
import jnpf.attendance.service.AttendanceUserBalanceService;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceUserBalanceDto;
import jnpf.model.attendance.dto.AttendanceUserBalanceListQueryDto;
import jnpf.model.attendance.vo.attendance.AttendanceUserBalanceListVo;
import jnpf.model.attendance.vo.attendance.AttendanceUserBalanceVo;
import jnpf.model.attendance.vo.attendance.AttendanceUserTitleVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* 用户余额
* @author 盼盼
* @create 2025-09-18
*/
@RestController
@RequestMapping(value = "/attendance/user-balance")
@Slf4j
public class AttendanceUserBalanceController {
@Autowired
private AttendanceUserBalanceService attendanceUserBalanceService;
/**
* 获取考勤余额管理列表
* @param queryDto 余额名称模糊查询非必传
*/
@PostMapping("/list")
public ActionResult<AttendanceUserBalanceListVo> list(@RequestBody @Valid AttendanceUserBalanceListQueryDto queryDto) throws HandleException {
AttendanceUserBalanceListVo vo = attendanceUserBalanceService.list(queryDto);
return ActionResult.success(vo);
}
/**
* 编辑、批量编辑余额
* @param userBalanceDto 余额
*/
@PutMapping()
public ActionResult<Void> updateUserBalance(@RequestBody AttendanceUserBalanceDto userBalanceDto) {
attendanceUserBalanceService.updateUserBalance(userBalanceDto);
return ActionResult.success();
}
/**
* 获取余额详情
* @param userBalanceDto 用户Id及类型Id类型Id没有默认查调休
*/
@GetMapping("/detail")
public ActionResult<AttendanceUserBalanceVo> getDetail(AttendanceUserBalanceDto userBalanceDto) {
return ActionResult.success(attendanceUserBalanceService.getDetail(userBalanceDto));
}
/**
* 获取余额详情页-顶部用户相关及动态假期表头
* @param userBalanceDto 用户Id及类型Id类型Id没有默认查调休
*/
@GetMapping("/title")
public ActionResult<AttendanceUserTitleVo> getTitle(AttendanceUserBalanceDto userBalanceDto) {
return ActionResult.success(attendanceUserBalanceService.getTitle(userBalanceDto));
}
}

View File

@@ -0,0 +1,88 @@
package jnpf.attendance.controller;
import cn.hutool.core.collection.CollUtil;
import jnpf.attendance.service.AttendanceUserSettingService;
import jnpf.base.ActionResult;
import jnpf.enums.attendance.UserSettingEnum;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AppUserSettingQueryDto;
import jnpf.model.attendance.dto.AttendanceAppUserSettingDto;
import jnpf.model.attendance.vo.UserSettingVo;
import jnpf.util.UserProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* app用户个人及考勤组设置
* @Author huanglinpan
* @Date 2024/8/8 10:53
* @Version 1.0 (版本号)
*/
@RestController
@RequestMapping(value = "/attendance/app")
@Slf4j
public class AttendanceUserSettingController {
@Resource
private AttendanceUserSettingService attendanceUserSettingService;
@Resource
private UserProvider userProvider;
/**
* 修改设置
* @param attendanceAppUserSettingDto 设置信息
*/
@PutMapping("/setting")
public ActionResult saveOrUpdate(@RequestBody AttendanceAppUserSettingDto attendanceAppUserSettingDto) {
attendanceUserSettingService.saveOrUpdate(attendanceAppUserSettingDto);
return ActionResult.success();
}
/**
* 批量保存设置
* @param attendanceAppUserSettingDto 保存的对象
*/
@PutMapping("/saveOrUpdateList")
public ActionResult saveOrUpdateList(@RequestBody List<AttendanceAppUserSettingDto> attendanceAppUserSettingDto) {
attendanceUserSettingService.saveOrUpdateList(attendanceAppUserSettingDto);
return ActionResult.success();
}
/**
* 查询设置列表
* @param appUserSettingQueryDto 查询信息
*/
@GetMapping("/setting")
public ActionResult<List<UserSettingVo>> getList(AppUserSettingQueryDto appUserSettingQueryDto) {
return ActionResult.success(attendanceUserSettingService.getList(appUserSettingQueryDto));
}
/**
* 获取用户上下班极速打卡配置
*/
@GetMapping("/userSetting")
public ActionResult<List<UserSettingVo>> getUserSettingList() {
return ActionResult.success(attendanceUserSettingService.getSettingList(CollUtil.newArrayList(userProvider.get().getUserId()),1, Stream.of(UserSettingEnum.ATTENDANCE_SETTING_START_SPEED_CHECK.getCode(),
UserSettingEnum.ATTENDANCE_SETTING_END_SPEED_CHECK.getCode())
.collect(Collectors.toList())));
}
/*****************************************考勤V1.9************************************************/
/**
* v1.9查询设置列表
*
* 因考虑兼容,导致原本的优化接口变为赋值接口并优化逻辑
* @param appUserSettingQueryDto 查询信息
*/
@GetMapping("/settingNew")
public ActionResult<List<UserSettingVo>> getListNew(AppUserSettingQueryDto appUserSettingQueryDto) {
return ActionResult.success(attendanceUserSettingService.getListNew(appUserSettingQueryDto));
}
}

View File

@@ -0,0 +1,92 @@
package jnpf.attendance.controller;
import jnpf.util.CustomTenantUtil;
import jnpf.util.NoDataSourceBind;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* 英泰斯达考勤机控制器
*
* @author yanwenfu
* @create 2024-03-28
*/
@RestController
@Slf4j
@RequestMapping(value = "/ffi")
public class FfiMachineController {
@Autowired
private CustomTenantUtil customTenantUtil;
/**
* 设备录入自定义编号判断
* @return java.util.Map<java.lang.String,java.lang.Object>
*/
@PostMapping(consumes = "application/octet-stream")
@NoDataSourceBind
public Map<String, Object> addFace(HttpServletRequest request) {
String requestCode = request.getHeader("request_code");
String devId = request.getHeader("dev_id");
String contentLength = request.getHeader("Content-Length");
log.error("requestCode: {}, devId: {}, contentLength: {}", requestCode, devId, contentLength);
int contentLength2 = request.getContentLength();
log.error("contentLength2: {}", contentLength2);
// 获取请求体的ServletInputStream
String body;
try {
// 读取二进制流(这里需要自己实现)
ServletInputStream inputStream = request.getInputStream();
byte[] buffer = new byte[contentLength2];
int bytesRead = 0;
while (bytesRead < contentLength2) {
int bytes = inputStream.read(buffer, bytesRead, contentLength2 - bytesRead);
if (bytes == -1) {
break;
}
bytesRead += bytes;
}
log.error("buffer bytes: {}", Arrays.toString(buffer));
body = getJsonBlock(buffer);
} catch (IOException e) {
log.error(e.getMessage());
return null;
}
log.error("body: {}", body);
Map<String, Object> map = new HashMap<>();
map.put("Result", 0);
map.put("Msg", "进入方法...");
return map;
}
private String getJsonBlock(byte[] buffer) {
String str = "";
if (buffer.length < 4) {
return str;
}
int lenText = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (lenText > buffer.length - 4 || lenText == 0) {
return str;
}
str = new String(buffer, 4, lenText, StandardCharsets.UTF_8);
if (buffer[4 + lenText - 1] == 0) {
str = new String(buffer, 4, lenText - 1, StandardCharsets.UTF_8);
}
return str;
}
}

View File

@@ -0,0 +1,54 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.InitializationService;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.config.ConfigValueUtil;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.exception.ApproveException;
import jnpf.exception.LoginException;
import jnpf.util.NoDataSourceBind;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import jnpf.util.data.DataSourceContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 初始化控制器
* @Author huanglinpan
* @Date 2024/7/1 9:14
* @Version 1.0 (版本号)
*/
@RestController
@RequestMapping("/config")
@Tag(name = "初始化", description = "initialization")
public class InitializationController {
@Autowired
private ConfigValueUtil configValueUtil;
@Resource
private InitializationService initializationService;
/**
* 初始化存休 有效期考勤v1.3版本
*/
@GetMapping(value = "/storageRest")
@Deprecated
public ActionResult storageRest() {
Integer i = initializationService.storageRest();
return ActionResult.success("初始化成功"+i+"条数据");
}
}

View File

@@ -0,0 +1,190 @@
package jnpf.attendance.controller;
import cn.hutool.json.JSONObject;
import jnpf.attendance.annotation.Machine;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.IsPerfMachineService;
import jnpf.attendance.service.MachineStrategy;
import jnpf.base.ActionResult;
import jnpf.base.UserInfo;
import jnpf.enums.attendance.ActionEnum;
import jnpf.enums.attendance.MachineEnum;
import jnpf.model.attendance.dto.SecondCheckDto;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.util.ConstantUtil;
import jnpf.util.CustomTenantUtil;
import jnpf.util.NoDataSourceBind;
import jnpf.util.RedisUtil;
import jnpf.util.TenantUtil;
import jnpf.util.attendance.MachineStrategyFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 开架易控制器
*
* @author yanwenfu
* @create 2024-04-01
*/
@RestController
@Slf4j
public class IsPerfMachineController {
@Resource
private IsPerfMachineService isPerfMachineService;
@Resource
private AttenceMachineService attenceMachineService;
@Resource
private MachineStrategyFactory machineStrategyFactory;
@Autowired
private CustomTenantUtil tenantUtil;
@Autowired
private RedisUtil redisUtil;
@GetMapping(value = "/main")
public Object welcome() {
// 初始化netty服务器, 设备登录在 应用设置--其他设置--通信设置 填入服务器 ip:port
isPerfMachineService.welcome();
return ActionResult.success("netty绑定成功");
}
@PostMapping(value = "/callback")
@NoDataSourceBind
public Object callback(@RequestBody Map<String, Object> params) {
log.error("callback init ...");
/*String userNo = params.get("userId").toString();
log.info("callback in : {}", params.toString());
JSONObject json = new JSONObject(params.get("extra"));
String tenantId = json.get("tenantId").toString();
String userId = json.get("userId").toString();
tenantUtil.checkOutTenant(tenantId, userId);
String sn = params.get("mac").toString();
String b = attenceMachineService.clockIn(sn, userId, tenantId);
// log.error(b);
json.clear();
// 将结果保存到redis 0: 打卡失败, 1: 正常, 2: 迟到, 3: 早退
redisUtil.insert(ConstantUtil.MACHINE_KEY + sn + "-" + userNo, "locked", 5L);*/
Map<String, Object> map = new HashMap<>();
map.put("code", 1);
map.put("flag", 1);
map.put("tips", "");
return map;
}
/**
* 开架易 - 二次校验
* @param params 二次校验dto
* @return java.lang.Object
*/
@PostMapping(value = "/kips/secondCheck")
@NoDataSourceBind
@Machine(dealAction = ActionEnum.DA_KA, factory = MachineEnum.KAI_JIA_YI)
public Object getClockInResult(@RequestBody Map<String, Object> params) {
int code = 0;
int flag = 0;
log.error("callback in : {}", params.toString());
Map<String, Object> map = new HashMap<>();
// 解析入参
String userNo = params.get("vipID").toString();
JSONObject json = new JSONObject(params.get("extra"));
String tenantId = json.get("tenantId").toString();
String userId = json.get("userId").toString();
String sn = params.get("mac").toString();
// 防止重复提交, 查看redis是否还在限制打卡
String key = ConstantUtil.MACHINE_KEY + sn + "-" + userNo;
if (redisUtil.exists(key)) {
map.put("code", code);
map.put("flag", flag);
map.put("tips", "");
return map;
}
tenantUtil.checkOutTenant(tenantId, userId);
String b = attenceMachineService.clockIn(sn, userId, tenantId);
json.clear();
// 防重复提交
redisUtil.insert(key, "locked", 5L);
String tips = "";
switch (b) {
case "0":
tips = "打卡失败";
break;
case "1":
tips = "打卡成功";
break;
case "2":
tips = "迟到";
break;
case "3":
tips = "早退";
break;
default:
tips = b;
break;
}
map.put("code", code);
map.put("flag", flag);
map.put("tips", tips);
return map;
}
/**
* 版本升级
* @param params 参数
*/
@PostMapping(value = "/updateApp")
public void updateApp(@RequestBody Map<String, Object> params) {
isPerfMachineService.updateApp(params);
}
/**
* 查看netty所有考勤机在线用户
* @return java.lang.Object
*/
@GetMapping(value = "/allOnlineClient")
public Object getAllOnlineClient() {
List<String> list = isPerfMachineService.getAllOnlineClient();
return ActionResult.success(list);
}
@GetMapping("/kyj/sendMqtt/{type}")
@NoDataSourceBind
public String kyjSendMqtt(@PathVariable(value = "type") Integer type) {
tenantUtil.checkOutTenant("ftb_dev");
MachineEnum machineEnum = MachineEnum.getMachineEnum(MachineEnum.KAI_JIA_YI.getValue());
MachineStrategy machineStrategy = machineStrategyFactory.getMachineStrategy(machineEnum);
UserInfo userInfo = new UserInfo();
userInfo.setTenantId("ftb_dev");
PartUserInfoVo user = new PartUserInfoVo();
user.setRealName("小鄢");
user.setUserId("471608060058616325");
user.setUserNo(123);
user.setGender(1);
String sn = "B48F62EEAC7D";
if (type == 1) {
machineStrategy.addUserToMachine(userInfo, user, sn);
} else {
machineStrategy.deleteUserList(userInfo, Stream.of(user.getUserNo().toString()).collect(Collectors.toList()), sn);
}
return "发送结束";
}
}

View File

@@ -0,0 +1,88 @@
package jnpf.attendance.controller;
import cn.hutool.json.JSONUtil;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.model.attendance.vo.attendance.UserTenantVo;
import jnpf.permission.UserApi;
import jnpf.permission.V2UserApi;
import jnpf.permission.model.user.UserNoInfoVo;
import jnpf.permission.vo.v2.user.UserBoundVO;
import jnpf.util.Base64Util;
import jnpf.util.CustomTenantUtil;
import jnpf.util.NoDataSourceBind;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* 科密考勤机
*
* @author yanwenfu
* @create 2025-08-22
*/
@Slf4j
@RestController
@RequestMapping(value = "/kemi")
public class KeMiController {
@Resource
private CustomTenantUtil customTenantUtil;
@Resource
private AttenceMachineService attenceMachineService;
@Autowired
private Base64Util base64Util;
@Autowired
private V2UserApi v2UserApi;
@NoDataSourceBind
@PostMapping(value = "/{tenantId}")
public ResponseEntity<String> messageFromMachine(@PathVariable(value = "tenantId") String tenantId, @RequestBody Map<String, Object> body, HttpServletRequest request) {
customTenantUtil.checkOutTenant(tenantId);
// log.error("科密推送 - {}", JSONUtil.toJsonStr(body));
// 获取命令内容
String requestCode = request.getHeader("request_code");
String transId = request.getHeader("trans_id");
String devId = request.getHeader("dev_id");
String token = request.getHeader("token");
String userId = body.get("userId").toString();
if (requestCode.equals("realtime_glog")) {
// 实时打卡记录推送
if (StringUtil.isNotEmpty(userId)) {
List<UserBoundVO> list = v2UserApi.userListAndCopy(List.of(userId), null, tenantId);
String userName = "";
if (null != list && !list.isEmpty()) {
userName = list.get(0).getUserName();
}
UserTenantVo userTenant = new UserTenantVo(userId, userId, userName, tenantId, devId);
attenceMachineService.KeMiClockIn(userTenant, devId, tenantId);
} else {
log.error("科密: 陌生人打卡, {}", userId);
}
} else if (requestCode.equals("realtime_enroll_data")) {
// 实时登记数据传输
Object photo = body.get("photo");
if (null != photo) {
String photoUrl = base64Util.convertAndUpload(body.get("photo").toString());
attenceMachineService.updateKeMiPhoto(userId, photoUrl, tenantId);
} else {
log.error("未获取到base64图片, 用户id: {}", userId);
}
}
return ResponseEntity.ok()
.headers(header -> {
header.add("response_code", "OK");
header.add("trans_id", transId);
header.add("token", token);
})
.contentType(MediaType.APPLICATION_JSON).body(null);
}
}

View File

@@ -0,0 +1,116 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.OvertimeRuleService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.OvertimeRuleDto;
import jnpf.model.attendance.dto.OvertimeRuleQueryDto;
import jnpf.model.attendance.vo.attendance.OvertimeRulePageVo;
import jnpf.model.attendance.vo.attendance.OvertimeRuleVo;
import jnpf.util.DateDetail;
import jnpf.util.FtbUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 加班规则
*
* @author yanwenfu
* @create 2025-09-17
*/
@RestController
@RequestMapping(value = "/overtime-rule")
public class OvertimeRuleController {
@Resource
private OvertimeRuleService overtimeRuleService;
/**
* 新增加班规则
* @param overtimeRuleDto 加班规则dto
* @return java.lang.Object
*/
@PostMapping
public Object addOvertimeRule(@RequestBody @Valid OvertimeRuleDto overtimeRuleDto) throws Exception {
overtimeRuleService.addOvertimeRule(overtimeRuleDto);
return ActionResult.success();
}
/**
* 编辑加班规则
* @param id 加班规则id
* @param overtimeRuleDto 加班规则dto
* @return java.lang.Object
*/
@PutMapping(value = "/{id}")
public Object updateOvertimeRule(@PathVariable(value = "id") String id, @RequestBody @Valid OvertimeRuleDto overtimeRuleDto) throws Exception {
overtimeRuleService.updateOvertimeRule(id, overtimeRuleDto);
return ActionResult.success();
}
/**
* 删除加班规则
* @param id 加班规则id
* @return java.lang.Object
*/
@DeleteMapping(value = "/{id}")
public Object deleteOvertimeRule(@PathVariable(value = "id") String id) {
overtimeRuleService.deleteOvertimeRule(id);
return ActionResult.success();
}
/**
* 禁用/启用加班规则
* @param id 加班规则id
* @return java.lang.Object
*/
@PutMapping(value = "/enable/{id}")
public Object updateEnableStatus(@PathVariable(value = "id") String id) throws Exception {
overtimeRuleService.updateEnableStatus(id);
return ActionResult.success();
}
/**
* 查询加班规则详情
* @param id 加班规则id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.attendance.OvertimeRuleVo>
*/
@GetMapping(value = "/detail/{id}")
public ActionResult<OvertimeRuleVo> getDetail(@PathVariable(value = "id") String id) {
OvertimeRuleVo vo = overtimeRuleService.getDetail(id);
return ActionResult.success(vo);
}
/**
* 查询加班规则列表(分页)
* @param queryDto 查询条件
* @return jnpf.base.ActionResult<jnpf.base.vo.PageListVO<jnpf.model.attendance.vo.attendance.OvertimeRulePageVo>>
*/
@GetMapping(value = "/page")
public ActionResult<PageListVO<OvertimeRulePageVo>> getPage(OvertimeRuleQueryDto queryDto) {
PageInfo<OvertimeRulePageVo> page = overtimeRuleService.getPage(queryDto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
/**
* 查询用户加班规则
* @param userId 用户id
* @param queryDate 查询日期(yyyy-MM-dd)
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.attendance.OvertimeRuleVo>
*/
@GetMapping(value = "/user")
public ActionResult<OvertimeRuleVo> getUserOvertimeRule(@RequestParam String userId, @RequestParam String queryDate) {
OvertimeRuleVo effectDetail = overtimeRuleService.getEffectDetail(userId, null, DateDetail.getStr2Date(queryDate));
return ActionResult.success(effectDetail);
}
}

View File

@@ -0,0 +1,93 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.PublicHolidayRulesService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.AttendancePublicHolidayRulesDto;
import jnpf.model.attendance.vo.attendance.AttendancePublicHolidayRulesVo;
import jnpf.model.common.PageDto;
import jnpf.util.FtbUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 考勤公休规则
* @author panpan
* @version V2.0
*/
@RestController
@RequestMapping(value = "/attendance/public-holiday")
@Slf4j
public class PublicHolidayRulesController {
@Autowired
private PublicHolidayRulesService publicHolidayRulesService;
/**
* 获取考勤公休规则列表
* @param iText 规则名称模糊查询 非必传
*/
@GetMapping("/list")
public ActionResult<PageListVO<AttendancePublicHolidayRulesVo>> list(@RequestParam(value = "iText", required = false) String iText, PageDto pageDto) {
PageInfo<AttendancePublicHolidayRulesVo> vo = publicHolidayRulesService.list(iText,pageDto);
return ActionResult.page(vo.getList(), FtbUtil.getPagination(vo));
}
/**
* 新增公休规则
* @param publicHolidayRulesDto 公休规则
*/
@PostMapping()
public ActionResult<Void> add(@RequestBody AttendancePublicHolidayRulesDto publicHolidayRulesDto) throws Exception {
publicHolidayRulesService.add(publicHolidayRulesDto);
return ActionResult.success();
}
/**
* 修改公休规则
* @param publicHolidayRulesDto 公休规则
*/
@PutMapping("/{id}")
public ActionResult<Void> put(@PathVariable("id") String id, @RequestBody AttendancePublicHolidayRulesDto publicHolidayRulesDto) throws Exception{
publicHolidayRulesDto.setId(id);
publicHolidayRulesService.update(publicHolidayRulesDto);
return ActionResult.success();
}
/**
* 获取公休规则详情
* @param id 公休规则 ID
*/
@GetMapping("/{id}")
public ActionResult<AttendancePublicHolidayRulesVo> detail(@PathVariable("id") String id) {
return ActionResult.success(publicHolidayRulesService.selectOne(id));
}
/**
* 删除公休规则
* @param id 公休规则 ID
*/
@DeleteMapping("/{id}")
public ActionResult<Void> delete(@PathVariable("id") String id) {
publicHolidayRulesService.delete(id);
return ActionResult.success();
}
/**
* 启用、停用公休规则
* @param publicHolidayRulesDto 公休规则
*/
@PutMapping("/updateState")
public ActionResult<Void> updateState( @RequestBody AttendancePublicHolidayRulesDto publicHolidayRulesDto) {
publicHolidayRulesService.updateState(publicHolidayRulesDto);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,227 @@
package jnpf.attendance.controller;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jnpf.attendance.service.AttenceMachineService;
import jnpf.attendance.service.AttendanceUserFaceService;
import jnpf.attendance.service.MachineStrategy;
import jnpf.attendance.service.RV1109MachineService;
import jnpf.base.UserInfo;
import jnpf.config.MqttConfiguration;
import jnpf.enums.attendance.MachineEnum;
import jnpf.model.attendance.vo.UserFaceVo;
import jnpf.permission.entity.UserEntity;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.util.*;
import jnpf.util.attendance.MachineStrategyFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* RV1109考勤机控制器
*
* @author yanwenfu
* @create 2024-04-09
*/
@RestController
@Slf4j
@RequestMapping
public class RV1109MachineController {
@Autowired
private CustomTenantUtil tenantUtil;
@Autowired
private RV1109MachineService rv1109MachineService;
@Autowired
private AttendanceUserFaceService attendanceUserFaceService;
@Autowired
private AttenceMachineService attenceMachineService;
@Autowired
private MqttConfiguration mqttConfiguration;
@Autowired
private RedisUtil redisUtil;
@Resource
private MachineStrategyFactory machineStrategyFactory;
/**
* 设备登录
* @return java.lang.Object
*/
@PostMapping(value = "/device/login")
@NoDataSourceBind
public Object deviceLogin(@RequestBody Map<String, Object> params) {
String devSno = params.get("dev_sno").toString();
String token = redisUtil.getHashValues(ConstantUtil.ATTENDANCE_DEVICE, devSno);
if (StringUtils.isEmpty(token)) {
token = UUID.randomUUID().toString();
redisUtil.insertHash(ConstantUtil.ATTENDANCE_DEVICE, devSno, token);
}
JSONObject json1 = new JSONObject();
json1.set("code", 0);
json1.set("dev_sno", devSno);
json1.set("msg", "登陆成功");
json1.set("success", true);
json1.set("token", token);
JSONObject json2 = new JSONObject();
String broker = mqttConfiguration.getHost();
String[] split = broker.split(":");
String host = split[1].substring(2);
Integer port = Integer.parseInt(split[2]);
json2.set("host", host);
json2.set("keepalive", 60);
json2.set("login", mqttConfiguration.getUsername());
json2.set("password", mqttConfiguration.getPassword());
json2.set("port", port);
json2.set("qos", mqttConfiguration.getQos());
json2.set("topic", devSno);
json1.set("mqinfo", json2);
return json1;
}
/**
* 设备同步成员
* @return java.lang.Object
*/
@PostMapping(value = "/device/sync_person")
@NoDataSourceBind
@SuppressWarnings("unchecked")
public Object deviceSyncPerson(@RequestBody Map<String, Object> params) {
// 获取租户信息
Map<String, Object> pathParamMap = (Map<String, Object>) params.get("path_params");
JSONArray array = JSONUtil.parseArray(pathParamMap.get("person_list"));
String userIdStr = array.get(0).toString();
String tenantId = userIdStr.split("@")[0];
String userId = userIdStr.split("@")[1];
tenantUtil.checkOutTenant(tenantId, userId);
JSONObject json = new JSONObject();
json.set("code", 0);
json.set("msg", "OK");
json.set("success", true);
json.set("person_list", getJsonArray(userId, tenantId));
return json;
}
/**
* 返回结果至设备
* @return java.lang.Object
*/
@PostMapping(value = "/device/notify")
@NoDataSourceBind
public Object deviceNotify(@RequestBody Map<String, Object> params) {
// 获取租户信息
boolean b = (boolean) params.get("success");
JSONObject json = new JSONObject();
if (b) {
json.set("code", 0);
json.set("msg", "OK");
json.set("success", true);
} else {
json.set("code", 0);
json.set("msg", "NOT OK");
json.set("success", false);
}
return json;
}
private JSONArray getJsonArray(String userId, String tenantId) {
JSONArray array = new JSONArray();
UserEntity user = rv1109MachineService.getUserInfoById(userId, tenantId);
if (null == user) {
log.error("用户不存在, 用户ID{}", userId);
return array;
}
// 查询人脸
UserFaceVo userFace = attendanceUserFaceService.getUserFace(user.getId());
if (null == userFace) {
log.error("人脸数据不存在, 用户ID{}", userId);
return array;
}
JSONObject json = new JSONObject();
json.set("person_id", tenantId + "@" + user.getId());
json.set("person_name", user.getRealName());
json.set("person_type", "4");
json.set("sex", null == user.getGender() ? 0 : user.getGender());
JSONArray picArray = new JSONArray();
picArray.add(userFace.getFaceData());
json.set("templateImgUrl", picArray);
json.set("qr", "");
json.set("id_card", "");
json.set("birthday", null == user.getBirthday() ? "" : DateDetail.getDate2Str(user.getBirthday(), DateDetail.DF));
array.add(json);
return array;
}
/**
* 记录上传
* @return java.lang.Object
*/
@PostMapping(value = "/record/upload/online")
@NoDataSourceBind
public Object recordUploadOnline(@RequestBody Map<String, Object> params) {
// 获取租户信息
String personIdStr = params.get("person_id").toString();
String tenantId = personIdStr.split("@")[0];
String userId = personIdStr.split("@")[1];
tenantUtil.checkOutTenant(tenantId, userId);
// 获取设备号
String sn = params.get("dev_sno").toString();
// 获取对比结果
int captureStatus = Integer.parseInt(params.get("capture_status").toString());
if (ConstantUtil.NUM_TRUE == captureStatus) {
// 对比通过
String b = attenceMachineService.clockIn(sn, userId, tenantId);
if (StringUtils.isNotEmpty(b)) {
log.error("打卡失败");
}
}
JSONObject json = new JSONObject();
json.set("code", 0);
json.set("msg", "OK");
json.set("success", true);
return json;
}
/**
* 测试方法
* @return java.lang.String
*/
@GetMapping("/sendMqtt/{type}")
@NoDataSourceBind
public String sendMqtt(@PathVariable(value = "type") String type) {
MachineEnum machineEnum = MachineEnum.getMachineEnum(MachineEnum.YU_QUE.getValue());
MachineStrategy machineStrategy = machineStrategyFactory.getMachineStrategy(machineEnum);
UserInfo userInfo = new UserInfo();
userInfo.setTenantId("ftb_dev");
PartUserInfoVo user = new PartUserInfoVo();
user.setUserId("471608060058616325");
String sn = "0A:0C:E1:43:B0:37";
if (type.equals("add")) {
machineStrategy.addUserToMachine(userInfo, user, sn);
}
if (type.equals("delete")) {
machineStrategy.deleteUserList(userInfo, Stream.of(user.getUserId()).collect(Collectors.toList()), sn);
}
return "发送结束";
}
}

View File

@@ -0,0 +1,77 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.ScheduleGroupRuleConfigService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.dto.scheduling.ScheduleGroupRuleConfigDto;
import jnpf.model.attendance.vo.scheduling.ScheduleGroupRuleConfigVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
/**
* 智能排班:考勤组维度「排班规则配置」查询与保存(仅固定排班核心参数 + 划线排班参数)。
* 控制器只负责打印接口入参与耗时JNPF 日志规范)。
*
* @author xiaofeng
* @since 2026-05-13
*/
@Slf4j
@Validated
@RestController
@RequestMapping("/attendance/schedule")
@Tag(name = "排班规则配置", description = "考勤组固定排班与划线排班参数 GET/POST")
public class ScheduleGroupRuleConfigController {
@Resource
private ScheduleGroupRuleConfigService scheduleGroupRuleConfigService;
/**
* 获取排班规则配置。无表记录时各子块返回与表 DEFAULT 一致的默认结构。
*
* @param groupId 考勤组 ID
* @return 配置
*/
@SneakyThrows
@Operation(summary = "获取排班规则配置")
@GetMapping("/getRuleConfig")
public ActionResult<ScheduleGroupRuleConfigVo> getRuleConfig(
@RequestParam("groupId") @NotBlank(message = "考勤组ID不能为空") String groupId) {
log.info("获取排班规则配置,入参=>{}", groupId);
long st = System.currentTimeMillis();
ScheduleGroupRuleConfigVo data = scheduleGroupRuleConfigService.getRuleConfig(groupId);
log.info("获取排班规则配置,耗时=>{} 毫秒", System.currentTimeMillis() - st);
return ActionResult.success(data);
}
/**
* 保存排班规则配置。请求体根与各子块为 DTOGET/POST 成功响应根与各子块均为 VOJSON 字段名不变)。
* 服务端在事务内 upsert 固定排班与划线排班参数表。
*
* @param dto 配置({@code groupId}、{@code fixedScheduling}、{@code lineScheduling} 必填)
* @return 保存后的配置
*/
@SneakyThrows
@Operation(summary = "保存排班规则配置")
@PostMapping("/saveRuleConfig")
public ActionResult<ScheduleGroupRuleConfigVo> saveRuleConfig(
@RequestBody @Valid ScheduleGroupRuleConfigDto dto) {
log.info("保存排班规则配置,入参=>{}", JSONObject.toJSON(dto));
long st = System.currentTimeMillis();
ScheduleGroupRuleConfigVo data = scheduleGroupRuleConfigService.saveRuleConfig(dto.getGroupId(), dto);
log.info("保存排班规则配置,耗时=>{} 毫秒", System.currentTimeMillis() - st);
return ActionResult.success(data);
}
}

View File

@@ -0,0 +1,75 @@
package jnpf.attendance.controller;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.SmartPreScheduleService;
import jnpf.base.ActionResult;
import jnpf.exception.HandleException;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.scheduling.PreScheduleTableQueryDto;
import jnpf.model.attendance.vo.scheduling.PreScheduleTableVo;
import jnpf.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* AI 智能排班确认:预排班主表生成与提交保存接口层。
*
* @author xiaofeng
* @create 2026-05-13
*/
@Slf4j
@RestController
@RequestMapping("/attendance/smartSchedule")
@Tag(name = "智能排班预排班", description = "预排班主表生成与整包保存")
public class SmartPreScheduleController {
@Resource
private SmartPreScheduleService smartPreScheduleService;
/**
* 预排班生成主表:返回 rows每行含 posts、stations 预览。
*
* @param dto 日期范围、按日预估营业额、人效目标、考勤组
* @return 主表数据
*/
@SneakyThrows
@Operation(summary = "预排班功能")
@PostMapping("/preScheduling")
public ActionResult<PreScheduleTableVo> buildTable(@RequestBody @Valid PreScheduleTableQueryDto dto) {
log.error("预排班生成主表,入参=>{}", JSONObject.toJSON(dto));
long startMs = System.currentTimeMillis();
PreScheduleTableVo data = smartPreScheduleService.buildPreScheduleTable(dto);
log.error("预排班生成主表,耗时=>{}毫秒", System.currentTimeMillis() - startMs);
ActionResult<PreScheduleTableVo> result = ActionResult.success(data);
if (data != null && StringUtil.isNotBlank(data.getMsg())) {
result.setMsg(data.getMsg());
}
return result;
}
/**
* 预排班主表提交保存:按 rows 过滤 Redis 中 byEmployee 并写回,返回 redisKeySuffix。
*
* @param vo groupId + rows
* @return redisKeySuffix考勤组:日期区间,不含租户)
*/
@Operation(summary = "预排班主表提交保存")
@PostMapping("/saveSchedul")
public ActionResult<String> saveTable(@RequestBody @Valid PreScheduleTableVo vo)
throws HandleException, QueryException {
log.error("预排班主表提交保存,入参=>{}", JSONObject.toJSON(vo));
long startMs = System.currentTimeMillis();
String redisKeySuffix = smartPreScheduleService.savePreScheduleTable(vo);
log.error("预排班主表提交保存,耗时=>{}毫秒", System.currentTimeMillis() - startMs);
return ActionResult.success(redisKeySuffix);
}
}

View File

@@ -0,0 +1,46 @@
package jnpf.attendance.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.attendance.service.UserConfigService;
import jnpf.base.ActionResult;
import jnpf.model.attendance.vo.attendance.GroupShiftTimeVo;
import jnpf.model.attendance.vo.attendance.UserConfigVo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author huanglinpan
* @Date 2024/6/24 11:23
* @Version 1.0 (版本号)
*/
@RestController
@RequestMapping("/config")
@Tag(name = "APP/后台 - 用户配置", description = "group")
public class UserConfigController {
@Resource
private UserConfigService userConfigService;
/**
* 获取用户app考勤配置
*/
@GetMapping("/userConfig")
public ActionResult<UserConfigVo> getUserConfig() {
return ActionResult.success(userConfigService.getUserConfig());
}
/**
* 修改用户APP考勤配置
*/
@PutMapping("/userConfig")
public ActionResult updateUserConfig(@RequestBody UserConfigVo userConfigVo) {
userConfigService.updateUserConfig(userConfigVo);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,233 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.UserFaceService;
import jnpf.attendance.service.UserFaceTxService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.exception.LoginException;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.FaceChangeQueryDto;
import jnpf.model.attendance.dto.FaceQueryDto;
import jnpf.model.attendance.dto.UserDto;
import jnpf.model.attendance.dto.UserFaceDto;
import jnpf.model.attendance.vo.ChangeLogVo;
import jnpf.model.attendance.vo.FaceMiniVo;
import jnpf.model.attendance.vo.UserFaceDetailVo;
import jnpf.model.attendance.vo.UserFaceVo;
import jnpf.model.common.PageDto;
import jnpf.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* 人脸识别
*
* @author yanwenfu
* @create 2025-04-08
*/
@RestController
@Slf4j
@RequestMapping(value = "/attendance/user-face")
public class UserFaceController {
@Resource
private UserFaceService userFaceService;
@Resource
private UserFaceTxService userFaceTxService;
@Resource
private UserProvider userProvider;
/**
* 下发用户人脸至考勤机
* @param userId 用户id列表
* @return jnpf.base.ActionResult<java.lang.Object>
*/
@PutMapping(value = "/sync")
public ActionResult<Object> syncUserFaceToMachine(@RequestParam String userId) throws Exception {
Integer result = userFaceTxService.syncUserFaceToMachine(userId);
return ActionResult.success(result);
}
/**
* 人脸对比
* @param file 人脸图片
* @return jnpf.base.ActionResult<java.lang.Boolean>
*/
@PostMapping(value = "/compare")
public ActionResult<Boolean> getPhotoCheck(@RequestPart MultipartFile file) throws Exception {
if (!isImageFile(file)) {
return ActionResult.fail("请上传图片文件进行比对");
}
Boolean result = userFaceService.getPhotoCheck(file, userProvider.get());
return ActionResult.success(result);
}
/**
* 人脸对比2
* @return jnpf.base.ActionResult<java.lang.Boolean>
*/
@PostMapping(value = "/compare-sec")
public ActionResult<Boolean> getPhotoCheck2() throws Exception {
List<MultipartFile> list = UpUtil.getFileAll();
MultipartFile file = list.get(0);
if (!isImageFile(file)) {
return ActionResult.fail("请上传图片文件进行比对");
}
Boolean result = userFaceService.getPhotoCheck(file, userProvider.get());
return ActionResult.success(result);
}
private boolean isImageFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
return false;
}
String contentType = file.getContentType();
return contentType != null && ConstantUtil.IMAGE_CONTENT_TYPES.contains(contentType);
}
/**
* 录入人脸
* @param userId 用户id
* @return jnpf.base.ActionResult<java.lang.Object>
*/
@PostMapping
public ActionResult<Object> addUserFace(@RequestPart("file") MultipartFile file, @RequestPart("userId") String userId) throws Exception {
if (!isImageFile(file)) {
return ActionResult.fail("文件格式错误!");
}
Integer result = userFaceTxService.uploadUserFace(file, new UserDto(userId), userProvider.get());
return ActionResult.success(result);
}
/**
* 录入人脸 - add
* @param userId 用户id
* @return jnpf.base.ActionResult<java.lang.Object>
*/
@PostMapping(value = "/add/{userId}")
public ActionResult<Object> addUserFace2(@PathVariable("userId") String userId) throws Exception {
List<MultipartFile> list = UpUtil.getFileAll();
MultipartFile file = list.get(0);
if (!isImageFile(file)) {
return ActionResult.fail("文件格式错误!");
}
Integer result = userFaceTxService.uploadUserFace(file, new UserDto(userId), userProvider.get());
return ActionResult.success(result);
}
/**
* 清空人脸
* @param userId 用户id
* @return jnpf.base.ActionResult<java.lang.Object>
*/
@DeleteMapping(value = "/{userId}")
public ActionResult<Object> deleteUserFace(@PathVariable(value = "userId") String userId) throws Exception {
userFaceService.deleteUserFace(userId, userProvider.get());
return ActionResult.success();
}
/**
* 查看人脸
* @param userId 用户id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.UserFaceVo>
*/
@GetMapping(value = "/{userId}")
public ActionResult<UserFaceVo> getUserFace(@PathVariable(value = "userId") String userId) {
UserFaceVo userFace = userFaceService.getUserFace(userId);
return ActionResult.success(userFace);
}
/**
* 查看人脸详情
* @param id 人脸记录id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.UserFaceVo>
*/
@GetMapping(value = "/detail/{id}")
public ActionResult<UserFaceVo> getUserFaceDetail(@PathVariable(value = "id") String id) {
UserFaceVo userFace = userFaceService.getUserFaceDetail(id);
return ActionResult.success(userFace);
}
/**
* 用户人脸信息
* @param userId 用户id
* @return jnpf.base.ActionResult<jnpf.model.attendance.vo.UserFaceVo>
*/
@GetMapping(value = "/info/{userId}")
public ActionResult<FaceMiniVo> getUserFaceInfo(@PathVariable(value = "userId") String userId) {
FaceMiniVo userFace = userFaceService.getUserFaceInfo(userId);
return ActionResult.success(userFace);
}
/**
* 变动记录(分页)
* @param queryDto 查询条件
* @return jnpf.base.ActionResult<jnpf.base.vo.PageListVO<jnpf.model.attendance.vo.ChangeLogVo>>
*/
@GetMapping(value = "/change-log/page")
public ActionResult<PageListVO<ChangeLogVo>> getChangeLog(FaceChangeQueryDto queryDto) {
PageInfo<ChangeLogVo> page = userFaceService.getChangeLogPage(queryDto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
/**
* 人脸记录(分页)
* @param queryDto 查询条件
* @return jnpf.base.ActionResult<jnpf.base.vo.PageListVO<jnpf.model.attendance.vo.UserFaceDetailVo>>
*/
@PostMapping(value = "/page")
public ActionResult<PageListVO<UserFaceDetailVo>> getUserFacePage(@RequestBody @Valid FaceQueryDto queryDto) throws QueryException {
if (StringUtil.isEmpty(queryDto.getGroupId()) && StringUtil.isEmpty(queryDto.getTeamId())) {
throw new QueryException("请选择考勤组或班组");
}
PageInfo<UserFaceDetailVo> page = userFaceService.getUserFacePage(queryDto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
/**
* 判断用户是否上传人脸
* @param userId 用户id
* @return jnpf.base.ActionResult<java.lang.Boolean>
*/
@GetMapping(value = "/check/{userId}")
public ActionResult<Boolean> hasUserFace(@PathVariable(value = "userId") String userId) {
Boolean b = userFaceService.hasUserFace(userId);
return ActionResult.success(b);
}
/**
* V1.8.2初始化人脸缩略图
* @param tenantId 租户id
*/
@NoDataSourceBind
@GetMapping(value = "/initializationFace")
public ActionResult<Boolean> initializationFace(@RequestParam("tenantId") String tenantId) {
try {
TenantDataSourceUtil.switchTenant(tenantId);
} catch (LoginException e) {
throw new RuntimeException("切换租户失败");
}
userFaceService.initializationFace(tenantId);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,88 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.UsualPhoneService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.CancelPhoneDto;
import jnpf.model.attendance.dto.UsualPhoneQueryDto;
import jnpf.model.attendance.dto.UsualPhoneSettingDto;
import jnpf.model.attendance.vo.attendance.UsualPhonePageVo;
import jnpf.util.FtbUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 常用设备管理
*
* @author yanwenfu
* @create 2025-09-17
*/
@RestController
@RequestMapping(value = "/user-phone")
public class UserPhoneController {
@Resource
private UsualPhoneService usualPhoneService;
/**
* 批量取消绑定
* @param cancelPhoneDto 取消绑定参数
* @return java.lang.Object
*/
@PutMapping(value = "/cancel")
public Object cancelPhoneBatch(@RequestBody @Valid CancelPhoneDto cancelPhoneDto) {
usualPhoneService.cancelPhoneBatch(cancelPhoneDto);
return ActionResult.success();
}
/**
* 更新常用手机设置
* @param usualPhoneSettingDto 常用手机设置dto
* @return java.lang.Object
*/
@PutMapping(value = "/setting")
public Object updateUsualPhoneSetting(@RequestBody @Valid UsualPhoneSettingDto usualPhoneSettingDto) {
usualPhoneService.updateUsualPhoneSetting(usualPhoneSettingDto);
return ActionResult.success();
}
/**
* 查询常用手机列表(分页)
* @param queryDto 查询条件
* @return jnpf.base.ActionResult<jnpf.base.vo.PageListVO<jnpf.model.attendance.vo.attendance.UsualPhonePageVo>>
*/
@GetMapping(value = "/page")
public ActionResult<PageListVO<UsualPhonePageVo>> getUsualPhonePage(UsualPhoneQueryDto queryDto) {
PageInfo<UsualPhonePageVo> page = usualPhoneService.getUsualPhonePage(queryDto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
/**
* 检查常用设备是否异常
* @param phoneName 手机名称
* @return jnpf.base.ActionResult<java.lang.Boolean>
*/
@GetMapping(value = "/abnormal")
public ActionResult<Boolean> checkUsualPhone(@RequestParam(value = "phoneName") String phoneName, @RequestParam(value = "phoneCode") String phoneCode) {
Boolean flag = usualPhoneService.checkUsualPhone(phoneName, phoneCode);
return ActionResult.success(flag);
}
/**
* 查询常用手机配置
* @return jnpf.base.ActionResult<jnpf.model.attendance.dto.UsualPhoneSettingDto>
*/
@GetMapping(value = "/usual-phone/setting")
public ActionResult<UsualPhoneSettingDto> getUsualPhoneSetting() {
UsualPhoneSettingDto dto = usualPhoneService.getUsualPhoneSetting();
return ActionResult.success(dto);
}
}

View File

@@ -0,0 +1,489 @@
package jnpf.attendance.controller;
import cn.hutool.core.date.DateUtil;
import com.fantaibao.permission.annotation.FtbCheckPermission;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import jnpf.attendance.FtbStatisticsApi;
import jnpf.attendance.dto.*;
import jnpf.attendance.service.AttendanceCustomizeTableService;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.attendance.service.AttendanceSealSettingService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.enums.attendance.TriggerSceneEnum;
import jnpf.exception.LoginException;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.event.StatisticsBatchClearDto;
import jnpf.model.attendance.event.StatisticsSingleDto;
import jnpf.model.attendance.vo.attendance.*;
import jnpf.model.personnels.vo.analysis.PersonnelDataAnalysisListVO;
import jnpf.personnels.utils.PersonnelDataAnalysisUtil;
import jnpf.util.FtbUtil;
import jnpf.util.NoDataSourceBind;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 考勤统计WEB
*/
@Slf4j
@RestController
@RequestMapping(value = "/attendance/statistics")
public class WebStatisticsController implements FtbStatisticsApi {
@Resource
private RocketMQTemplate rocketMqTemplate;
@Resource
private AttendanceDayStatisticsService dayStaService;
@Autowired
private AttendanceCustomizeTableService tableService;
@Resource
private AttendanceSealSettingService sealSettingService;
@Override
@Operation(summary = "用户日统计数据初始化")
@GetMapping(value = "/userDayStatisticsInit")
public ActionResult<Boolean> userDayStatisticsInit(@RequestParam(value = "tenantId") String tenantId) {
return ActionResult.success(dayStaService.handleDataForJob(tenantId));
}
@Operation(summary = "日度统计-汇总")
@PostMapping("/dayStatistics")
public ActionResult<List<DayStatisticsDataVo>> dayStatistics(@Valid @RequestBody DayStatisticsDataDto req) {
return ActionResult.success(dayStaService.getDayStatisticsData(req));
}
@Operation(summary = "日度统计-分页列表")
@PostMapping(value = "/dayPageList")
public ActionResult<PageListVO<DayStatisticsPageListVo>> dayPageList(@Valid @RequestBody DayStatisticsPageListDto req) {
PageInfo<DayStatisticsPageListVo> page = dayStaService.getDayPageList(req);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@Operation(summary = "日度统计-导出")
@PostMapping(value = "/dayDataExport")
public ActionResult<Boolean> dayDataExport(@Valid @RequestBody DayStatisticsExportDto req) {
dayStaService.dayDataExport(req);
return ActionResult.success();
}
@Operation(summary = "月度统计-汇总")
@PostMapping("/monthStatistics")
public ActionResult<List<DayStatisticsDataVo>> monthStatistics(@Valid @RequestBody MouthStatisticsDataDto req) {
return ActionResult.success(dayStaService.getMonthStatisticsData(req));
}
@Operation(summary = "月度统计-分页列表")
@PostMapping(value = "/monthPageList")
public ActionResult<PageListVO<MonthStatisticsPageListVo>> monthPageList(@Valid @RequestBody MonthStatisticsPageListDto req) throws Exception {
PageInfo<MonthStatisticsPageListVo> page = dayStaService.getMonthPageList(req);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@Operation(summary = "月度统计-导出")
@PostMapping(value = "/monthDataExport")
public ActionResult<Boolean> monthDataExport(@Valid @RequestBody MonthStatisticsExportDto req) {
dayStaService.monthDataExport(req);
return ActionResult.success();
}
@Override
@Operation(summary = "个人考勤日报通知")
@GetMapping(value = "/dayStatisticsNotice")
public ActionResult<Boolean> dayStatisticsNotice(@RequestParam(value = "tenantId") String tenantId) {
return ActionResult.success(dayStaService.dayStatisticsNotice(tenantId));
}
@Override
@Operation(summary = "个人统计月报通知")
@GetMapping(value = "/monthStatisticsNotice")
public ActionResult<Boolean> monthStatisticsNotice(@RequestParam(value = "tenantId") String tenantId) {
return ActionResult.success(dayStaService.monthStatisticsNotice(tenantId));
}
@Override
@Operation(summary = "连续未排班通知")
@GetMapping(value = "/consentUnscheduledNotice")
public void consentUnscheduledNotice(@RequestParam(value = "tenantId") String tenantId) {
dayStaService.consentUnscheduledNotice(tenantId);
}
@Override
@Operation(summary = "团队统计月报通知")
@GetMapping(value = "/teamMonthStatisticsNotice")
public ActionResult<Boolean> teamMonthStatisticsNotice(@RequestParam(value = "tenantId") String tenantId) {
return ActionResult.success(dayStaService.teamMonthStatisticsNotice(tenantId));
}
@Override
@Operation(summary = "计算考勤组平均工时(实际出勤工时)")
@PostMapping(value = "/countAttendanceAvgHours")
public List<AttendanceCountAvgHoursVo> countAttendanceAvgHours(@Valid @RequestBody AttendanceCountAvgHoursDto dto) {
return dayStaService.countAttendanceAvgHours(dto);
}
@Override
@Operation(summary = "获取多考勤组月度统计数据")
@PostMapping(value = "/getAttendanceAvgHoursDetails")
public ActionResult<MonthStatsDetailsVo> getAttendanceAvgHoursDetails(@Valid @RequestBody MonthStatsDetailsDto dto) {
MonthStatsDetailsVo result = dayStaService.getAttendanceAvgHoursDetails(dto);
return ActionResult.success(result);
}
@Override
@Operation(summary = "获取多考勤组月度人均工时折线图")
@PostMapping(value = "/getAttendanceMonthPerCapita")
public ActionResult<List<MonthStatsPerCapitaVo>> getAttendanceMonthPerCapita(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceMonthPerCapita(dto));
}
@Override
@Operation(summary = "获取多考勤组月度日常情况")
@PostMapping(value = "/getAttendanceDailySituation")
public ActionResult<List<MonthStatsDailySituationVo>> getAttendanceDailySituation(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceDailySituation(dto));
}
@Override
@Operation(summary = "获取多考勤组月度考勤工时排行")
@PostMapping(value = "/getAttendanceHoursRanking")
public ActionResult<List<MonthStatsHoursRankingVo>> getAttendanceHoursRanking(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceHoursRanking(dto));
}
@Override
@Operation(summary = "获取多考勤组月度全勤情况")
@PostMapping(value = "/getAttendanceFullSituation")
public ActionResult<List<MonthStatsFullSituationVo>> getAttendanceFullSituation(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceFullSituation(dto));
}
@Override
@Operation(summary = "获取多考勤组月度异常情况")
@PostMapping(value = "/getAttendanceAbnormalCondition")
public ActionResult<MonthStatsAbnormalConditionVo> getAttendanceAbnormalCondition(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceAbnormalCondition(dto));
}
@Override
@Operation(summary = "获取多考勤组月度加班情况")
@PostMapping(value = "/getAttendanceOvertimeSituation")
public ActionResult<List<MonthStatsOvertimeSituationVo>> getAttendanceOvertimeSituation(@Valid @RequestBody MonthStatsDetailsDto dto) {
return ActionResult.success(dayStaService.getAttendanceOvertimeSituation(dto));
}
@Operation(summary = "考勤封账-自动封账设置详情")
@GetMapping(value = "/getAutoSealSettingInfo")
public ActionResult<MonthAutoSealSettingVo> getAutoSealSettingInfo() {
return ActionResult.success(sealSettingService.getAutoSealSettingInfo());
}
@Operation(summary = "考勤封账-自动封账设置")
@PutMapping(value = "/autoSealSetting")
public ActionResult<Boolean> autoSealSetting(@Valid @RequestBody MonthAutoSealSettingDto dto) {
return ActionResult.success(sealSettingService.autoSealSetting(dto));
}
@Override
@Operation(summary = "考勤封账-自动封账定时器")
@PutMapping(value = "/autoSealTimer")
public ActionResult<Boolean> autoSealTimer(@RequestParam(value = "tenantId") String tenantId) {
return ActionResult.success(sealSettingService.autoSealTimer(tenantId));
}
@Operation(summary = "考勤封账-分页列表")
@PostMapping(value = "/sealPageList")
public ActionResult<PageListVO<MonthSealPageListVo>> sealPageList(@Valid @RequestBody MonthSealPageListDto dto) {
PageInfo<MonthSealPageListVo> page = dayStaService.sealPageList(dto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@Operation(summary = "考勤封账-批量|单个封账")
@PutMapping(value = "/sealSubmit")
public ActionResult<Boolean> sealSubmit(@Valid @RequestBody MonthSealSubmitDto dto) {
return ActionResult.success(dayStaService.sealSubmit(dto));
}
@Operation(summary = "考勤封账-解封")
@PutMapping(value = "/unSealSubmit")
public ActionResult<Boolean> unSealSubmit(@Valid @RequestBody MonthUnSealSubmitDto dto) {
return ActionResult.success(dayStaService.unSealSubmit(dto));
}
@Operation(summary = "批量查询用户是否封账")
@PostMapping(value = "/selectUserIsSeal")
public ActionResult<Map<String, Boolean>> selectUserIsSeal(@Valid @RequestBody MonthSealSubmitDto dto) {
return ActionResult.success(dayStaService.selectUserIsSeal(dto.getUserIdList(), dto.getMonth()));
}
@Override
@Operation(summary = "薪酬考勤数据支持(薪酬)")
@PostMapping(value = "/salaryAttendanceSupport")
public Map<String, SalaryAttendanceSupportVo> salaryAttendanceSupport(@Valid @RequestBody SalaryAttendanceSupportDto dto) {
return dayStaService.salaryAttendanceSupport(dto);
}
@Override
@Operation(summary = "考勤统计数据日度列表表头(薪酬)")
@PostMapping(value = "/attendanceDayStaTable")
public List<AttendanceCustomizeTableVo> attendanceDayStaTable() {
return tableService.findList(null, null, 1);
}
@Override
@Operation(summary = "考勤统计数据日度列表(薪酬)")
@PostMapping(value = "/attendanceDayStaList")
public List<DayStatisticsPageListVo> attendanceDayStaList(@Valid @RequestBody SalaryAttendanceSupportDto dto) {
return dayStaService.attendanceStaList(dto);
}
@Override
@Operation(summary = "获取日出勤信息")
@NoDataSourceBind
@PostMapping(value = "/getAttendanceDayStaList")
public List<DayStatisticsVo> getAttendanceDayStaList(@Valid @RequestBody DayStatisticsDto dto) {
//切换成租户库
try {
TenantDataSourceUtil.switchTenant(dto.getTenantId());
} catch (LoginException e) {
throw new RuntimeException("切换租户失败");
}
return dayStaService.getAttendanceDayStaList(dto);
}
@NoDataSourceBind
@Operation(summary = "模拟统计数据消息推送")
@GetMapping("/mockStatisticsPush")
public ActionResult<Boolean> mockStatisticsPush(@RequestParam("tenantId") String tenantId,
@RequestParam("groupId") String groupId,
@RequestParam("userId") String userId,
@RequestParam("day") String day) {
StatisticsSingleDto courseEventDTO = StatisticsSingleDto.builder()
.tenantId(tenantId)
.groupId(groupId)
.userId(userId)
.triggerSceneEnum(TriggerSceneEnum.MANUAL_TRIGGER)
.day(DateUtil.parse(day))
.build();
Message<StatisticsSingleDto> message = MessageBuilder.withPayload(courseEventDTO).build();
rocketMqTemplate.syncSend(MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC, message, 3000L, 2);
return ActionResult.success();
}
@NoDataSourceBind
@Operation(summary = "日统计触发")
@GetMapping("/dayStatisticsTriggered")
public ActionResult<Boolean> dayStatisticsTriggered(@RequestParam("tenantId") String tenantId,
@RequestParam("groupId") String groupId,
@RequestParam("userId") String userId,
@RequestParam("day") String day) throws LoginException {
StatisticsSingleDto courseEventDTO = StatisticsSingleDto.builder()
.tenantId(tenantId)
.groupId(groupId)
.userId(userId)
.day(DateUtil.parse(day))
.triggerSceneEnum(TriggerSceneEnum.MANUAL_TRIGGER)
.build();
dayStaService.statisticDataChange(courseEventDTO);
return ActionResult.success();
}
@NoDataSourceBind
@Operation(summary = "日统计数据清除")
@GetMapping("/dayStatisticsClear")
public ActionResult<Boolean> dayStatisticsClear(@RequestParam("tenantId") String tenantId,
@RequestParam("groupId") String groupId,
@RequestParam("userId") List<String> userList,
@RequestParam("day") String day,
@RequestParam("startDay") String startDay) {
StatisticsBatchClearDto courseEventDTO = StatisticsBatchClearDto.builder()
.tenantId(tenantId)
.groupId(groupId)
.userIdList(userList)
.day(DateUtil.parse(day))
.startDay(DateUtil.parse(startDay))
.build();
dayStaService.batchStatisticDataClear(courseEventDTO);
return ActionResult.success();
}
@NoDataSourceBind
@Operation(summary = "重新生成日统计数据")
@GetMapping(value = "/regenerateDayData")
public ActionResult<Boolean> regenerateDayData(@RequestParam("tenantId") String tenantId,
@RequestParam("start") Date start) {
dayStaService.regenerateDayData(tenantId, start);
return ActionResult.success();
}
@NoDataSourceBind
@Operation(summary = "处理历史数据")
@GetMapping(value = "/processHistoricalData")
public ActionResult<Boolean> processHistoricalData(@RequestParam("tenantId") String tenantId,
@RequestParam("start") Date start,
@RequestParam("end") Date end) {
dayStaService.processHistoricalData(tenantId, start, end);
return ActionResult.success();
}
@Override
@Operation(summary = "获取各维度出勤人数")
@PostMapping(value = "/getDimensionsAttendanceCountMap")
public Map<String, List<DateDimensionsRangeVo>> getDimensionsAttendanceCountMap(@Valid @RequestBody DimensionsAttendanceCountDto dto) {
return dayStaService.getDimensionsAttendanceCountMap(dto);
}
@Override
@Operation(summary = "获取各维度出勤人数(天维度)")
@PostMapping(value = "/getDimensionsAttendanceDayCountMap")
public Map<String, Map<Date, Integer>> getDimensionsAttendanceDayCountMap(@Valid @RequestBody DimensionsAttendanceDayCountDto dto) {
return dayStaService.getDimensionsAttendanceDayCountMap(dto);
}
@Operation(summary = "日度计薪统计-分页列表")
@PostMapping(value = "/dayPayrollPageList")
public ActionResult<PageListVO<DayPayrollStatisticsPageListVo>> dayPayrollPageList(@Valid @RequestBody DayPayrollStatisticsPageListDto req) {
PageInfo<DayPayrollStatisticsPageListVo> page = dayStaService.getDayPayrollPageList(req);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@Operation(summary = "月度计薪统计-分页列表")
@PostMapping(value = "/monthPayrollPageList")
public ActionResult<PageListVO<MonthPayrollStatisticsPageListVo>> monthPayrollPageList(@Valid @RequestBody MonthPayrollStatisticsPageListDto req) throws Exception {
PageInfo<MonthPayrollStatisticsPageListVo> page = dayStaService.getMonthPayrollPageList(req);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
@Operation(summary = "考勤平均工时趋势-分页列表")
@PostMapping(value = "/averageWorkHoursTrend/pageList")
public ActionResult<PersonnelDataAnalysisListVO> averageWorkHoursTrendPageList(@Valid @RequestBody PersonnelApiInfoPageListDto dto) {
PageListVO<AverageTrendPageListVo> result = dayStaService.averageWorkHoursTrendPageList(dto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, AverageTrendPageListVo.class));
}
@Operation(summary = "考勤平均工时趋势-导出")
@PostMapping(value = "/averageWorkHoursTrend/export")
public void averageWorkHoursTrendExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoPageListDto pageDto) throws Exception {
dayStaService.averageWorkHoursTrendExport(response, pageDto);
}
@Operation(summary = "人均工时趋势-分页列表")
@PostMapping(value = "/personWorkHoursTrend/pageList")
public ActionResult<PersonnelDataAnalysisListVO> personWorkHoursTrendPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<PersonnelTrendPageListVo> result = dayStaService.personWorkHoursTrendPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, PersonnelTrendPageListVo.class));
}
@Operation(summary = "人均工时趋势-导出")
@PostMapping(value = "/personWorkHoursTrend/export")
public void personWorkHoursTrendExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.personWorkHoursTrendExport(response, pageDto);
}
@Operation(summary = "考勤组日常情况-分页列表")
@PostMapping(value = "/dailySituation/pageList")
public ActionResult<PersonnelDataAnalysisListVO> dailySituationPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<DailySituationPageListVo> result = dayStaService.dailySituationPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, DailySituationPageListVo.class));
}
@Operation(summary = "考勤组日常情况-导出")
@PostMapping(value = "/dailySituation/export")
public void dailySituationExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.dailySituationExport(response, pageDto);
}
@Operation(summary = "考勤工时排行-分页列表")
@PostMapping(value = "/workHoursRanking/pageList")
public ActionResult<PersonnelDataAnalysisListVO> workHoursRankingPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<WorkHoursRankingPageListVo> result = dayStaService.workHoursRankingPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, WorkHoursRankingPageListVo.class));
}
@Operation(summary = "考勤工时排行-导出")
@PostMapping(value = "/workHoursRanking/export")
public void workHoursRankingExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.workHoursRankingExport(response, pageDto);
}
@Operation(summary = "考勤组全勤情况-分页列表")
@PostMapping(value = "/fullAttendanceStatus/pageList")
public ActionResult<PersonnelDataAnalysisListVO> fullAttendanceStatusPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<FullAttendanceStatusPageListVo> result = dayStaService.fullAttendanceStatusPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, FullAttendanceStatusPageListVo.class));
}
@Operation(summary = "考勤组全勤情况-导出")
@PostMapping(value = "/fullAttendanceStatus/export")
public void fullAttendanceStatusExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.fullAttendanceStatusExport(response, pageDto);
}
@Operation(summary = "考勤异常情况-分页列表")
@PostMapping(value = "/exceptionSituation/pageList")
public ActionResult<PersonnelDataAnalysisListVO> exceptionSituationPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<ExceptionSituationPageListVo> result = dayStaService.exceptionSituationPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, ExceptionSituationPageListVo.class));
}
@Operation(summary = "考勤异常情况-导出")
@PostMapping(value = "/exceptionSituation/export")
public void exceptionSituationExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.exceptionSituationExport(response, pageDto);
}
@Operation(summary = "考勤组加班情况-分页列表")
@PostMapping(value = "/overtimeSituation/pageList")
public ActionResult<PersonnelDataAnalysisListVO> overtimeSituationPageList(@Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) {
PageListVO<OvertimeSituationPageListVo> result = dayStaService.overtimeSituationPageList(pageDto);
return ActionResult.success(PersonnelDataAnalysisUtil.encapsulatePageData(result, OvertimeSituationPageListVo.class));
}
@Operation(summary = "考勤组加班情况-导出")
@PostMapping(value = "/overtimeSituation/export")
public void overtimeSituationExport(HttpServletResponse response, @Valid @RequestBody PersonnelApiInfoSinglePageListDto pageDto) throws Exception {
dayStaService.overtimeSituationExport(response, pageDto);
}
@Operation(summary = "查询用户考勤统计信息")
@PostMapping(value = "/queryUserStatisticsInfo")
@FtbCheckPermission("attendance_2.0.statistics.monthlyStatistics")
public ActionResult<QueryStatisticsInfoVo> queryUserStatisticsInfo(@Valid @RequestBody QueryStatisticsInfoDto dto) {
return ActionResult.success(dayStaService.queryUserStatisticsInfo(dto));
}
@Operation(summary = "查询考勤组的考勤情况")
@PostMapping(value = "/queryGroupStatistics")
@FtbCheckPermission("attendance_2.0.statistics.monthlyStatistics")
public ActionResult<QueryGroupStatisticsVo> queryGroupStatistics(@Valid @RequestBody QueryGroupStatisticsDto dto) {
return ActionResult.success(dayStaService.queryGroupStatistics(dto));
}
@Operation(summary = "查询用户的加班情况")
@PostMapping(value = "/queryUserOvertime")
@FtbCheckPermission("attendance_2.0.statistics.monthlyStatistics")
public ActionResult<QueryUserOvertimeVo> queryUserOvertime(@Valid @RequestBody QueryStatisticsInfoDto dto) {
return ActionResult.success(dayStaService.queryUserOvertime(dto));
}
@Operation(summary = "查询用户的上班情况")
@PostMapping(value = "/queryUserWorkSituation")
public ActionResult<Map<String, UserWorkSituationVo>> queryUserWorkSituation(@Valid @RequestBody UserWorkSituationDto dto) {
return ActionResult.success(dayStaService.queryUserWorkSituation(dto));
}
}

View File

@@ -0,0 +1,146 @@
package jnpf.attendance.controller;
import com.github.pagehelper.PageInfo;
import jnpf.attendance.service.WorkstationService;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.model.attendance.dto.WorkstationQueryDto;
import jnpf.model.attendance.dto.WorkstationSaveDto;
import jnpf.model.attendance.dto.WorkstationUserAddDto;
import jnpf.model.attendance.dto.WorkstationUserRemoveDto;
import jnpf.model.attendance.vo.WorkstationDetailVo;
import jnpf.model.attendance.vo.WorkstationVo;
import jnpf.model.attendance.vo.WorkstationWithUsersVo;
import jnpf.util.FtbUtil;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
/**
* 考勤工作站控制器
*
* @author AI Generated
* @create 2026-05-11
*/
@RestController
@RequestMapping("/attendance/workstation")
public class WorkstationController {
@Resource
private WorkstationService workstationService;
/**
* 查询工作站列表(分页)
*
* @param dto 查询条件(含分页参数 currentPage/pageSize)
* @return 工作站分页列表
*/
@GetMapping("/list")
public ActionResult<PageListVO<WorkstationVo>> list(WorkstationQueryDto dto) {
PageInfo<WorkstationVo> page = workstationService.listWorkstations(dto);
return ActionResult.page(page.getList(), FtbUtil.getPagination(page));
}
/**
* 新增工作站
*
* @param dto 工作站保存DTO
* @return 操作结果
*/
@PostMapping
public ActionResult<Void> add(@RequestBody @Valid WorkstationSaveDto dto) {
workstationService.saveWorkstation(dto);
return ActionResult.success("新增成功");
}
/**
* 编辑工作站
*
* @param id 工作站ID
* @param dto 工作站保存DTO
* @return 操作结果
*/
@PutMapping("/{id}")
public ActionResult<Void> update(@PathVariable String id, @RequestBody @Valid WorkstationSaveDto dto) {
workstationService.updateWorkstation(id, dto);
return ActionResult.success("编辑成功");
}
/**
* 删除工作站
*
* @param id 工作站ID
* @return 操作结果
*/
@DeleteMapping("/{id}")
public ActionResult<Void> delete(@PathVariable String id) {
workstationService.deleteWorkstation(id);
return ActionResult.success("删除成功");
}
/**
* 添加人员
*
* @param dto 添加人员DTO
* @return 操作结果
*/
@PostMapping("/users/add")
public ActionResult<Void> addUsers(@RequestBody @Valid WorkstationUserAddDto dto) {
workstationService.addUsers(dto);
return ActionResult.success("添加成功");
}
/**
* 删除人员
*
* @param dto 删除人员DTO
* @return 操作结果
*/
@DeleteMapping("/users/remove")
public ActionResult<Void> removeUser(@RequestBody @Valid WorkstationUserRemoveDto dto) {
workstationService.removeUser(dto);
return ActionResult.success("删除成功");
}
/**
* 查询工作站详情
*
* @param id 工作站ID
* @return 工作站详情
*/
@GetMapping("/{id}/detail")
public ActionResult<WorkstationDetailVo> detail(
@PathVariable String id) {
return ActionResult.success(workstationService.getDetail(id));
}
/**
* 按考勤组查询工作站及其归属员工(含在组日、划线排班权限)
*
* @param groupId 考勤组ID
* @param startTime 查询开始日期yyyy-MM-dd
* @param endTime 查询结束日期yyyy-MM-dd同时作为成员快照截止日
*/
@GetMapping("/by-group/{groupId}")
public ActionResult<List<WorkstationWithUsersVo>> listByGroupId(
@PathVariable String groupId,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) {
return ActionResult.success(workstationService.listWorkstationsByGroupId(groupId, startTime, endTime));
}
/**
* 重置工作站人员:恢复为岗位默认归属人员,清除另加入的额外人员
*
* @param id 工作站ID
*/
@PostMapping("/{id}/reset")
public ActionResult<Void> reset(@PathVariable String id) {
workstationService.resetWorkstationUsers(id);
return ActionResult.success("重置成功");
}
}

View File

@@ -0,0 +1,32 @@
package jnpf.attendance.entity;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.entity.attendance.AttendanceBaseSetting;
import jnpf.entity.attendance.AttendanceFestivalRules;
import jnpf.model.attendance.vo.AttendanceShiftSettingVo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FixedHandleDTO {
private List<String> groupIds;
private Date start;
private Date end;
private List<AttendanceGroup> attendanceGroupVos;
private List<AttendanceGroupUser> users;
private Map<String, AttendanceBaseSetting> enableBaseSetting;
private Map<String, AttendanceShiftSettingVo> shiftSettingMap;
private Map<String, List<AttendanceFestivalRules>> festivalMap;
private String tenantId;
}

View File

@@ -0,0 +1,105 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceClockInService;
import jnpf.base.UserInfo;
import jnpf.constants.MessageTopicConstants;
import jnpf.constants.RedisConstant;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.model.attendance.vo.UserDayVo;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static jnpf.constants.RedisConstant.ATTENDANCE_NOTIFICATION_CLOCK_USER;
/**
* 考勤统计-单条消息监听,监听生成用户日统计消息
* @author 石头
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.ATTENDANCE_NOTIFICATION_CLOCK_TOPIC,
consumerGroup = MessageTopicConstants.ATTENDANCE_NOTIFICATION_CLOCK_CONSUMER_GROUP,
consumeMode = ConsumeMode.CONCURRENTLY,
maxReconsumeTimes = 5)
public class NotificationClockMQListener implements RocketMQListener<List<UserDayVo>>, RocketMQPushConsumerLifecycleListener {
@Resource
private RedissonClient redissonClient;
@Autowired
private AttendanceClockInService attendanceClockInService;
@Override
public void onMessage(List<UserDayVo> userDayList) {
if (CollUtil.isEmpty(userDayList)) {
return;
}
log.error("接受到排班通知打卡消息,{}", JSONObject.toJSONString(userDayList));
UserDayVo userDayVo1 = userDayList.get(0);
Assert.isFalse(StringUtil.isEmpty(userDayVo1.getTenantId()), "排班通知打卡tenantId不能为空");
List<RLock> locks = Lists.newArrayList();
try {
for (UserDayVo userDayVo : userDayList) {
String lockKey = String.format(ATTENDANCE_NOTIFICATION_CLOCK_USER, userDayVo.getUserId(), userDayVo.getDay());
RLock lock = redissonClient.getLock(lockKey);
RLock lock1 = redissonClient.getLock(String.format(RedisConstant.ATTENDANCE_USER_SET_SCHEDULES, userDayVo.getTenantId(), userDayVo.getUserId(), userDayVo.getUserId()));
// 立即尝试获取锁(不等待),失败直接抛异常
if (lock.isLocked() || lock1.isLocked() || !lock.tryLock(5, 60, TimeUnit.SECONDS)) {
throw new RuntimeException(userDayVo.getUserId() + "通知打卡获取锁失败");
}
locks.add(lock);
}
TenantDataSourceUtil.switchTenant(userDayVo1.getTenantId());
UserInfo user = new UserInfo();
user.setUserId(userDayVo1.getUserId());
user.setTenantId(userDayVo1.getTenantId());
// 执行业务(异常直接向上抛出)
attendanceClockInService.changeAttendanceRuleBatch(userDayList, user);
} catch (Exception ex) {
log.error("处理排班通知打卡消息失败,触发重试,消息 {} ", JSONObject.toJSONString(userDayList), ex);
throw new RuntimeException("处理排班通知打卡消息失败,触发重试", ex);
} finally {
// 安全释放:仅当前线程持有时释放,异常仅记录不抛出
locks.forEach(lock -> {
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
}
});
}
}
@Override
public void prepareStart(DefaultMQPushConsumer consumer) {
// 优化消费参数 每次拉取的消息数量
consumer.setPullBatchSize(32);
// 设置消费间隔,避免过于频繁拉取
consumer.setPullInterval(200);
// 设置消费超时时间(分钟)
consumer.setConsumeTimeout(10);
// 设置消费起始位置(从上次消费的位置继续)
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
// 调整消费线程池最小线程数
consumer.setConsumeThreadMin(10);
// 调整消费线程池最大线程数
consumer.setConsumeThreadMax(20);
}
}

View File

@@ -0,0 +1,82 @@
package jnpf.attendance.event;
import com.alibaba.fastjson.JSON;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.message.model.permission.PermissionRelationOrganizeUserListDTO;
import jnpf.model.attendance.model.OrganizeUserConsumerDTO;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static jnpf.constants.RedisConstant.GROUP_JOIN_USERS_KEY;
import static jnpf.constants.RedisConstant.GROUP_ONBOARDING_KEY;
/**
* 入职请求信息
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.PERSONNEL_ONBOARDING_TOPIC,
consumerGroup = MessageTopicConstants.ONBOARDING_GROUP,
replyTimeout = 600000,
maxReconsumeTimes = 3,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class OnboardingConsumerMQListener implements RocketMQListener<String> {
@Autowired
private AttendanceUserService attendanceUserService;
@Autowired
private RedissonClient redissonClient;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接收入职/变动请求信息{}", message);
OrganizeUserConsumerDTO messageList = JSON.parseObject(message, OrganizeUserConsumerDTO.class);
if (StringUtil.equals(messageList.getBeforeOrgId(), messageList.getOrganizeId())) {
return;
}
RLock lock = null;
try {
String key = String.format(GROUP_JOIN_USERS_KEY, messageList.getUserId());
lock = redissonClient.getLock(key);
if (!lock.tryLock(5, 10, TimeUnit.SECONDS)) {
log.error("接收组织人员添加通知,目前用户【{}】正在被变更!", messageList.getUserId());
throw new RuntimeException(String.format("接收组织人员添加通知,目前用户【%s】正在被变更触发重试", messageList.getUserId()));
}
PermissionRelationOrganizeUserListDTO permissionRelationOrganizeUserListDTO = new PermissionRelationOrganizeUserListDTO();
permissionRelationOrganizeUserListDTO.setUserId(messageList.getUserId());
permissionRelationOrganizeUserListDTO.setOldOrganizeId(messageList.getBeforeOrgId());
permissionRelationOrganizeUserListDTO.setOrganizeId(messageList.getOrganizeId());
permissionRelationOrganizeUserListDTO.setEntryDate(messageList.getEntryDate());
permissionRelationOrganizeUserListDTO.setTenantId(messageList.getTenantId());
TenantDataSourceUtil.switchTenant(messageList.getTenantId());
if (StringUtil.isNotEmpty(messageList.getBeforeOrgId())) {
attendanceUserService.removeUsers(List.of(permissionRelationOrganizeUserListDTO), false);
}
attendanceUserService.addUsers(List.of(permissionRelationOrganizeUserListDTO), true);
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
throw new RuntimeException(e.getMessage());
} finally{
if (Objects.nonNull(lock) && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}

View File

@@ -0,0 +1,58 @@
package jnpf.attendance.event;
import com.alibaba.fastjson.JSON;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.message.model.permission.PermissionRelationOrganizeUserListDTO;
import jnpf.model.attendance.model.TurnoverConsumerModel;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* 终止入职信息
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.PERSONNEL_SECONDMENT_FAIL_TOPIC,
consumerGroup = MessageTopicConstants.ONBOARDING_FAIL_GROUP,
replyTimeout = 600000,
maxReconsumeTimes = 3,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class OnboardingFailConsumerMQListener implements RocketMQListener<String> {
@Autowired
private AttendanceUserService attendanceUserService;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接受终止入职信息{}", message);
try {
TurnoverConsumerModel messageList = JSON.parseObject(message, TurnoverConsumerModel.class);
if (Objects.isNull(messageList)) {
return;
}
TenantDataSourceUtil.switchTenant(messageList.getTenantId());
PermissionRelationOrganizeUserListDTO permissionRelationOrganizeUserListDTO = new PermissionRelationOrganizeUserListDTO();
permissionRelationOrganizeUserListDTO.setOldOrganizeId(messageList.getCurrOrgId());
permissionRelationOrganizeUserListDTO.setUserId(messageList.getUserId());
//删
attendanceUserService.removeUsers(List.of(permissionRelationOrganizeUserListDTO),false);
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
}
}
}

View File

@@ -0,0 +1,58 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.message.enums.permission.v2.OperationTypeMessageEnums;
import jnpf.message.model.permission.v2.OrganizeUpdateMessageDTO;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 组织变更消费者
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.PERMISSION_TOPIC,
consumerGroup = MessageTopicConstants.JNPF_GROUP,
selectorExpression = MessageTopicConstants.TAG_ORGANIZE,
replyTimeout = 600000,
maxReconsumeTimes = 3,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class OrganizeConsumerMQListener implements RocketMQListener<String> {
@Autowired
private AttendanceGroupService attendanceGroupService;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接受组织变更信息{}", message);
try {
List<OrganizeUpdateMessageDTO> messageList = JSON.parseArray(message, OrganizeUpdateMessageDTO.class);
if (CollUtil.isEmpty(messageList) || StringUtil.isEmpty(messageList.get(0).getId())) {
return;
}
TenantDataSourceUtil.switchTenant(messageList.get(0).getTenantId());
List<OrganizeUpdateMessageDTO> collect = messageList.stream().filter(vo -> Objects.equals(vo.getOperationTypeEnum(), OperationTypeMessageEnums.ADD) || Objects.equals(vo.getOperationTypeEnum(), OperationTypeMessageEnums.UPDATE)).collect(Collectors.toList());
attendanceGroupService.batchSaveGroupByOrgIds(collect.get(0).getTenantId(), collect.stream().map(OrganizeUpdateMessageDTO::getId).collect(Collectors.toList()));
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
}
}
}

View File

@@ -0,0 +1,54 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.constants.MessageTopicConstants;
import jnpf.message.model.permission.PermissionRelationOrganizeUserListDTO;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 组织下人员变更
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.PERMISSION_TOPIC,
consumerGroup = MessageTopicConstants.JNPF_USER_GROUP,
selectorExpression = MessageTopicConstants.ORGANIZE_RELATION_USER,
replyTimeout = 600000,
maxReconsumeTimes = 3,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class OrganizeUserConsumerMQListener implements RocketMQListener<String> {
@Autowired
private AttendanceUserService attendanceUserService;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接受组织下人员(批量入职/批量删除/预入职/离职)变更信息{}", message);
try {
List<PermissionRelationOrganizeUserListDTO> messageList = JSON.parseArray(message, PermissionRelationOrganizeUserListDTO.class);
if (CollUtil.isEmpty(messageList) || StringUtil.isEmpty(messageList.get(0).getUserId())) {
log.error("接受组织下人员无效数据过滤{}", message);
return;
}
//增删改
attendanceUserService.orgUpdateHandle(messageList);
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
}
}
}

View File

@@ -0,0 +1,86 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import jnpf.attendance.service.AttendanceDailyRuleService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.exception.ApproveException;
import jnpf.exception.HandleException;
import jnpf.model.attendance.dto.AttendanceSecondedDto;
import jnpf.model.personnels.dto.secondment.msg.SecondmentMsgUserInfo;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 借调申请消息
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.PERSONNEL_SECONDMENT_TOPIC,
consumerGroup = MessageTopicConstants.SECONDMENT_GROUP,
replyTimeout = 600000,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class SecondmentConsumerMQListener implements RocketMQListener<String> {
@Resource
private AttendanceDailyRuleService attendanceDailyRuleService;
@Autowired
private AttendanceGroupService attendanceGroupService;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接受借调申请信息{}", message);
try {
SecondmentMsgUserInfo messageList = JSONUtil.toBean(message, SecondmentMsgUserInfo.class);
if (Objects.isNull(messageList)) {
return;
}
if (StringUtil.equals(messageList.getOriginalOrganizationId(), messageList.getSecondedOrganizationId())) {
return;
}
AttendanceSecondedDto bean = new AttendanceSecondedDto();
bean.setBackTime(messageList.getSecondmentEndTime());
bean.setDepartureTime(messageList.getSecondmentStartTime());
bean.setStartTime(messageList.getSecondmentStartTime());
bean.setEndTime(messageList.getSecondmentEndTime());
bean.setUserIds(List.of(messageList.getUserId()));
TenantDataSourceUtil.switchTenant(messageList.getTenantId());
Map<String, String> org2grouopMap = attendanceGroupService.batchSaveGroupByOrgIds(messageList.getTenantId(), CollUtil.newArrayList(messageList.getOriginalOrganizationId(), messageList.getSecondedOrganizationId()));
if (!org2grouopMap.containsKey(messageList.getOriginalOrganizationId()) || !org2grouopMap.containsKey(messageList.getSecondedOrganizationId())) {
throw new ApproveException("组织不存在");
}
bean.setSelfGroupId(org2grouopMap.get(messageList.getOriginalOrganizationId()));
bean.setGroupId(org2grouopMap.get(messageList.getSecondedOrganizationId()));
try {
attendanceGroupService.secondedStart(bean);
} catch (HandleException e) {
throw new ApproveException(e.getMessage());
}
//修改用户排班
try {
attendanceDailyRuleService.secondmentDailyRuleHandle(bean.getUserIds(), bean.getSelfGroupId(), bean.getGroupId(), bean.getStartTime(), bean.getEndTime(), bean.getDepartureTime(), bean.getBackTime(), messageList.getTenantId());
} catch (HandleException e) {
throw new ApproveException(e.getMessage());
}
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
}
}
}

View File

@@ -0,0 +1,94 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.attendance.service.AttendanceDailyRuleService;
import jnpf.attendance.service.AttendanceGroupService;
import jnpf.attendance.service.AttendanceUserService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.entity.AttendanceGroup;
import jnpf.entity.AttendanceGroupUser;
import jnpf.model.attendance.vo.SecondmentDateVo;
import jnpf.model.personnels.dto.secondment.msg.SecondmentMsgUserInfo;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 借调撤销消息
*/
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.SECONDMENT_WITHDRAWAL_OUTPUT,
consumerGroup = MessageTopicConstants.SECONDMENT_WITHDRAWAL_GROUP,
replyTimeout = 600000,
nameServer = "${spring.cloud.stream.rocketmq.binder.name-server}"
)
@Slf4j
public class SecondmentWithdrawalConsumerMQListener implements RocketMQListener<String> {
@Resource
private AttendanceDailyRuleService attendanceDailyRuleService;
@Autowired
private AttendanceUserService attendanceUserService;
@Autowired
private AttendanceGroupService attendanceGroupService;
@Override
public void onMessage(String message) {
if (StringUtil.isEmpty(message)) {
return;
}
log.error("接受借调撤回信息{}", message);
try {
SecondmentMsgUserInfo messageList = JSONUtil.toBean(message, SecondmentMsgUserInfo.class);
if (Objects.isNull(messageList)) {
return;
}
TenantDataSourceUtil.switchTenant(messageList.getTenantId());
//存在排班的借调撤回不执行
Assert.isFalse(attendanceDailyRuleService.hasRuleByUserIdAndTime(messageList.getUserId(), messageList.getSecondmentStartTime(), messageList.getSecondmentEndTime()), "存在排班的借调撤回不执行");
List<AttendanceGroup> byOrgId = attendanceGroupService.getByOrgIds(List.of(messageList.getOriginalOrganizationId(), messageList.getSecondedOrganizationId()));
Map<String, String> group2orgMap = byOrgId.stream().collect(Collectors.toMap(AttendanceGroup::getId, AttendanceGroup::getOrgId));
List<AttendanceGroupUser> userList = attendanceUserService.list(new LambdaQueryWrapper<AttendanceGroupUser>()
.in(AttendanceGroupUser::getGroupId, byOrgId.stream().map(AttendanceGroup::getId).collect(Collectors.toList()))
.eq(AttendanceGroupUser::getUserId, messageList.getUserId())
);
//删除借调组数据
for (AttendanceGroupUser next : userList) {
String orgId = group2orgMap.get(next.getGroupId());
List<SecondmentDateVo> list = StringUtil.isEmpty(next.getTimeJson()) ? Lists.newArrayList() : JSONUtil.toList(next.getTimeJson(), SecondmentDateVo.class);
if (list.stream().noneMatch(item -> item.getStartTime().compareTo(messageList.getSecondmentStartTime()) == 0)) {
continue;
}
//如果是借调组或者被借调组则根据借调时间范围找到timeJSON中指定时段并清除
list.removeIf(item -> item.getStartTime().compareTo(messageList.getSecondmentStartTime()) == 0);
if (CollUtil.isNotEmpty(list) || Objects.equals(next.getType(),1)) {
next.setTimeJson(JSONUtil.toJsonStr(list));
attendanceUserService.updateById(next);
continue;
}
//当借调组的timeJson为空时则删除该用户信息
if (StringUtil.equals(orgId, messageList.getSecondedOrganizationId())) {
attendanceUserService.removeById(next);
}
}
} catch (Exception e) {
log.error("消息解析异常: message={}", message, e);
}
}
}

View File

@@ -0,0 +1,71 @@
package jnpf.attendance.event;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.constants.MessageTopicConstants;
import jnpf.model.attendance.event.StatisticsBatchClearDto;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
/**
* 批量清除日统计消息监听,监听批量清除日统计消息
* @author 石头
*/
@Slf4j
@Component
@RocketMQMessageListener(topic = MessageTopicConstants.ATTENDANCE_STATISTICS_BATCH_CLEAR_TOPIC,
consumerGroup = MessageTopicConstants.ATTENDANCE_STATISTICS_BATCH_CLEAR_CONSUMER_GROUP,
consumeMode = ConsumeMode.CONCURRENTLY,
maxReconsumeTimes = 10)
public class StatisticsBatchClearMQListener implements RocketMQListener<StatisticsBatchClearDto>, RocketMQPushConsumerLifecycleListener {
@Resource
private AttendanceDayStatisticsService attendanceDayStatisticsService;
@Override
public void onMessage(StatisticsBatchClearDto batchClearDto) {
if (Objects.isNull(batchClearDto)) {
log.warn("接收到空消息,忽略处理");
return;
}
log.info("接受到一条批量清除日统计消息,{}", JSONObject.toJSONString(batchClearDto));
if (CollUtil.isEmpty(batchClearDto.getUserIdList()) || StringUtil.isEmpty(batchClearDto.getTenantId()) ||
StringUtil.isEmpty(batchClearDto.getGroupId()) || (Objects.isNull(batchClearDto.getDay()) && Objects.isNull(batchClearDto.getStartDay()))){
log.error("消费批量清除日统计消息失败:消息格式无效,内容: {}", JSONObject.toJSONString(batchClearDto));
return;
}
try {
attendanceDayStatisticsService.batchStatisticDataClear(batchClearDto);
} catch (Exception ex) {
log.error("消费批量清除日统计消息消费失败,触发重试,消息 {}, 错误:{} ", JSONObject.toJSONString(batchClearDto), ExceptionUtil.stacktraceToString(ex));
throw new RuntimeException("消费批量清除日统计消息消费失败,触发重试", ex);
}
}
@Override
public void prepareStart(DefaultMQPushConsumer consumer) {
// 优化消费参数 每次拉取的消息数量
consumer.setPullBatchSize(32);
// 设置消费间隔,避免过于频繁拉取
consumer.setPullInterval(200);
// 设置消费超时时间(分钟)
consumer.setConsumeTimeout(10);
// 设置消费起始位置(从上次消费的位置继续)
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
// 调整消费线程池最小线程数
consumer.setConsumeThreadMin(10);
// 调整消费线程池最大线程数
consumer.setConsumeThreadMax(20);
}
}

View File

@@ -0,0 +1,104 @@
package jnpf.attendance.event;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.model.TenantVO;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.enums.attendance.StatisticsEnumUtil;
import jnpf.model.attendance.event.StatisticsSingleHistoryDto;
import jnpf.util.DateUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 考勤统计-单条消息监听,监听生成历史数据处理
* @author 石头
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_HISTORY_TOPIC,
consumerGroup = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_HISTORY_CONSUMER_GROUP,
consumeMode = ConsumeMode.CONCURRENTLY,
maxReconsumeTimes = 5)
public class StatisticsSingleHistoryMQListener implements RocketMQListener<StatisticsSingleHistoryDto>, RocketMQPushConsumerLifecycleListener {
@Resource
private RedissonClient redissonClient;
@Resource
private AttendanceDayStatisticsService attendanceDayStatisticsService;
@Override
public void onMessage(StatisticsSingleHistoryDto singleDto) {
if (Objects.isNull(singleDto)) {
log.warn("接收到空消息,忽略处理");
return;
}
log.info("接受到一条历史数据处理消息,{}", JSONObject.toJSONString(singleDto));
// 校验必要字段
if (ObjectUtil.isNull(singleDto) || StringUtil.isEmpty(singleDto.getUserId()) ||
StringUtil.isEmpty(singleDto.getTenantId()) || StringUtil.isEmpty(singleDto.getGroupId()) ||
ObjectUtil.isNull(singleDto.getDay())) {
log.error("生成历史数据处理消费失败:消息格式无效,内容: {}", JSONObject.toJSONString(singleDto));
return;
}
try {
String lockKey = String.format(StatisticsEnumUtil.AttendanceClockEnum.DAY_STATISTICS.getKey(), singleDto.getTenantId(), singleDto.getGroupId(),
singleDto.getUserId(), DateUtil.daFormat(singleDto.getDay()));
RLock lock = redissonClient.getLock(lockKey);
try {
// 立即尝试获取锁(不等待),失败直接抛异常
if (!lock.tryLock(5, 20, TimeUnit.SECONDS)) {
throw new RuntimeException("获取锁失败");
}
// 租户不为空就需要切库
TenantVO tenantVO = TenantDataSourceUtil.switchTenant(singleDto.getTenantId());
log.error("切换tenant对象{}", JSON.toJSONString(tenantVO));
attendanceDayStatisticsService.statisticDataChangeHistory(singleDto);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁时线程被中断: " + lockKey, e);
} finally {
// 安全释放:仅当前线程持有时释放,异常仅记录不抛出
if (lock.isHeldByCurrentThread() && lock.isHeldByThread(Thread.currentThread().getId())) {
lock.unlock();
}
}
} catch (Exception ex) {
log.info("处理历史数据处理失败,触发重试,消息 {}, 错误:{} ", JSONObject.toJSONString(singleDto), ex.getMessage());
throw new RuntimeException("处理历史数据处理失败,触发重试", ex);
}
}
@Override
public void prepareStart(DefaultMQPushConsumer consumer) {
// 优化消费参数 每次拉取的消息数量
consumer.setPullBatchSize(32);
// 设置消费间隔,避免过于频繁拉取
consumer.setPullInterval(200);
// 设置消费超时时间(分钟)
consumer.setConsumeTimeout(10);
// 设置消费起始位置(从上次消费的位置继续)
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
// 调整消费线程池最小线程数
consumer.setConsumeThreadMin(10);
// 调整消费线程池最大线程数
consumer.setConsumeThreadMax(20);
}
}

View File

@@ -0,0 +1,103 @@
package jnpf.attendance.event;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.service.AttendanceDayStatisticsService;
import jnpf.constants.MessageTopicConstants;
import jnpf.database.model.TenantVO;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.enums.attendance.StatisticsEnumUtil;
import jnpf.model.attendance.event.StatisticsSingleDto;
import jnpf.util.DateUtil;
import jnpf.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 考勤统计-单条消息监听,监听生成用户日统计消息
* @author 石头
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_TOPIC,
consumerGroup = MessageTopicConstants.ATTENDANCE_STATISTICS_SINGLE_CONSUMER_GROUP,
consumeMode = ConsumeMode.CONCURRENTLY,
maxReconsumeTimes = 5)
public class StatisticsSingleMQListener implements RocketMQListener<StatisticsSingleDto>, RocketMQPushConsumerLifecycleListener {
@Resource
private RedissonClient redissonClient;
@Resource
private AttendanceDayStatisticsService attendanceDayStatisticsService;
@Override
public void onMessage(StatisticsSingleDto singleDto) {
if (Objects.isNull(singleDto)) {
log.warn("接收到空消息,忽略处理");
return;
}
log.error("接受到一条生成用户日统计消息,{}", JSONObject.toJSONString(singleDto));
// 校验必要字段
if (ObjectUtil.isNull(singleDto) || StringUtil.isEmpty(singleDto.getUserId()) ||
StringUtil.isEmpty(singleDto.getTenantId()) || StringUtil.isEmpty(singleDto.getGroupId()) ||
ObjectUtil.isNull(singleDto.getDay())) {
log.error("生成用户日统计消息消费失败:消息格式无效,内容: {}", JSONObject.toJSONString(singleDto));
return;
}
try {
String lockKey = String.format(StatisticsEnumUtil.AttendanceClockEnum.DAY_STATISTICS.getKey(), singleDto.getTenantId(),
singleDto.getGroupId(), singleDto.getUserId(), DateUtil.daFormat(singleDto.getDay()));
RLock lock = redissonClient.getLock(lockKey);
try {
if (!lock.tryLock(5, 20, TimeUnit.SECONDS)) {
throw new RuntimeException("获取锁失败");
}
// 租户不为空就需要切库
TenantVO tenantVO = TenantDataSourceUtil.switchTenant(singleDto.getTenantId());
log.error("切换tenant对象{}", JSON.toJSONString(tenantVO));
attendanceDayStatisticsService.statisticDataChange(singleDto);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁时线程被中断: " + lockKey, e);
} finally {
// 安全释放:仅当前线程持有时释放,异常仅记录不抛出
if (lock.isHeldByCurrentThread() && lock.isHeldByThread(Thread.currentThread().getId())) {
lock.unlock();
}
}
} catch (Exception ex) {
log.error("处理用户日统计消息失败,触发重试,消息 {}, 错误:{} ", JSONObject.toJSONString(singleDto), ex.getMessage());
throw new RuntimeException("处理用户日统计消息失败,触发重试", ex);
}
}
@Override
public void prepareStart(DefaultMQPushConsumer consumer) {
// 优化消费参数 每次拉取的消息数量
consumer.setPullBatchSize(32);
// 设置消费间隔,避免过于频繁拉取
consumer.setPullInterval(200);
// 设置消费超时时间(分钟)
consumer.setConsumeTimeout(10);
// 设置消费起始位置(从上次消费的位置继续)
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
// 调整消费线程池最小线程数
consumer.setConsumeThreadMin(10);
// 调整消费线程池最大线程数
consumer.setConsumeThreadMax(20);
}
}

View File

@@ -0,0 +1,129 @@
package jnpf.attendance.excel;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
/**
* 考勤本导出单元格合并处理器
* 使用CellWriteHandler在数据写入过程中执行合并避免与表头合并冲突
*
* @author Generated
* @date 2026-04-29
*/
@Slf4j
public class AttendanceBookMergeHandler implements CellWriteHandler {
/**
* 需要合并的单元格范围列表(数据行索引,不包含表头)
*/
private final List<CellRangeAddress> mergeCells;
/**
* 表头行数(用于偏移数据行索引)
*/
private final int headerRowCount;
/**
* 预期数据总行数(用于判断是否是最后一行)
*/
private final int expectedDataRowCount;
/**
* 额外表头行数(标题行+说明行)
*/
private final int extraHeaderRows;
/**
* 总列数(用于合并标题和说明行)
*/
private final int totalColumns;
/**
* 是否已执行合并
*/
private boolean merged = false;
public AttendanceBookMergeHandler(List<CellRangeAddress> mergeCells, int headerRowCount, int expectedDataRowCount, int totalColumns) {
this.mergeCells = mergeCells;
this.headerRowCount = headerRowCount;
this.expectedDataRowCount = expectedDataRowCount;
this.extraHeaderRows = 0; // 使用3层表头不需要额外行
this.totalColumns = totalColumns;
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
// 只在未合并且是数据行(非表头)时才执行合并
if (!merged && mergeCells != null && !mergeCells.isEmpty()) {
Integer rowIndex = context.getRowIndex();
Boolean isHead = context.getHead();
// 跳过表头行
if (isHead != null && isHead) {
return;
}
// 在写入最后一行数据后执行合并
if (rowIndex != null && rowIndex >= headerRowCount + expectedDataRowCount - 1) {
Sheet sheet = context.getWriteSheetHolder().getSheet();
try {
int mergedCount = 0;
// 合并数据区域使用3层表头偏移量为headerRowCount
for (CellRangeAddress address : mergeCells) {
int offset = headerRowCount; // 3层表头
CellRangeAddress adjustedAddress = new CellRangeAddress(
address.getFirstRow() + offset,
address.getLastRow() + offset,
address.getFirstColumn(),
address.getLastColumn()
);
// 检查是否与现有合并区域冲突
if (!hasConflict(sheet, adjustedAddress)) {
sheet.addMergedRegion(adjustedAddress);
mergedCount++;
} else {
log.warn("跳过冲突的合并区域: {}", adjustedAddress);
}
}
merged = true;
log.info("考勤本导出单元格合并完成,成功合并 {} 个区域,表头行数: {}, 数据行数: {}",
mergedCount, headerRowCount, expectedDataRowCount);
} catch (Exception e) {
log.error("考勤本导出单元格合并失败: {}", e.getMessage(), e);
}
}
}
}
/**
* 检查合并区域是否与现有合并区域冲突
*/
private boolean hasConflict(Sheet sheet, CellRangeAddress newAddress) {
int numMergedRegions = sheet.getNumMergedRegions();
for (int i = 0; i < numMergedRegions; i++) {
CellRangeAddress existing = sheet.getMergedRegion(i);
if (isOverlap(newAddress, existing)) {
return true;
}
}
return false;
}
/**
* 检查两个合并区域是否重叠
*/
private boolean isOverlap(CellRangeAddress addr1, CellRangeAddress addr2) {
return !(addr1.getLastRow() < addr2.getFirstRow() ||
addr1.getFirstRow() > addr2.getLastRow() ||
addr1.getLastColumn() < addr2.getFirstColumn() ||
addr1.getFirstColumn() > addr2.getLastColumn());
}
}

View File

@@ -0,0 +1,52 @@
package jnpf.attendance.excel;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
/**
* 组合写入处理器
* 用于同时应用多个Handler样式Handler + 合并Handler
*
* @author Generated
* @date 2026-04-29
*/
@Slf4j
public class CompositeWriteHandler implements RowWriteHandler, CellWriteHandler {
private final RowWriteHandler rowHandler;
private final CellWriteHandler cellHandler;
public CompositeWriteHandler(RowWriteHandler rowHandler, CellWriteHandler cellHandler) {
this.rowHandler = rowHandler;
this.cellHandler = cellHandler;
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
// 执行样式Handler
if (rowHandler != null) {
try {
rowHandler.afterRowDispose(writeSheetHolder, writeTableHolder, row, relativeRowIndex, isHead);
} catch (Exception e) {
log.error("RowHandler执行失败: {}", e.getMessage(), e);
}
}
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
// 执行单元格Handler包含合并逻辑
if (cellHandler != null) {
try {
cellHandler.afterCellDispose(context);
} catch (Exception e) {
log.error("CellHandler执行失败: {}", e.getMessage(), e);
}
}
}
}

View File

@@ -0,0 +1,251 @@
package jnpf.attendance.excel;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
public class HeadStyleHandler implements RowWriteHandler, SheetWriteHandler {
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
Sheet sheet = writeSheetHolder.getSheet();
XSSFCellStyle contextCellStyle = getContextCellStyle(sheet);
int rowNum = row.getRowNum();
Workbook workbook = sheet.getWorkbook();
Font font = workbook.createFont();
font.setFontHeight((short) (20 * 18));
font.setColor(IndexedColors.WHITE.getIndex());
for (Cell cell : row) {
if (isHead) {
if (textSetRedFont(cell)) {
contextCellStyle.setAlignment(HorizontalAlignment.LEFT);
cell.setCellStyle(contextCellStyle);
continue;
}
// 3层表头结构
// rowNum == 0: 标题行(蓝色背景,白色字体)
// rowNum == 1: 说明行(黑色字体,左对齐,无背景)
// rowNum == 2: 表头行黑色字体大小11无背景
if (rowNum == 0) {
// 标题行:蓝色背景,白色字体
contextCellStyle.setFont(font);
contextCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
contextCellStyle.setFillForegroundColor(new XSSFColor(new java.awt.Color(48, 84, 150), null));
} else if (rowNum == 1) {
// 说明行:黑色字体,左对齐,无背景
Font noteFont = workbook.createFont();
noteFont.setFontHeight((short) (20 * 11));
noteFont.setColor(IndexedColors.BLACK.getIndex());
contextCellStyle.setFont(noteFont);
contextCellStyle.setAlignment(HorizontalAlignment.LEFT);
contextCellStyle.setFillPattern(FillPatternType.NO_FILL);
contextCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
} else if (rowNum == 2) {
// 表头行黑色字体大小11无背景
Font headerFont = workbook.createFont();
headerFont.setFontHeight((short) (20 * 11));
headerFont.setColor(IndexedColors.BLACK.getIndex());
contextCellStyle.setFont(headerFont);
contextCellStyle.setFillPattern(FillPatternType.NO_FILL);
contextCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
}
}
cell.setCellStyle(contextCellStyle);
}
// 实现自动行高调整,使用手动计算方式避免兼容性问题
calculateAndSetRowHeight(row);
}
/**
* 手动计算并设置行高
* @param row 行对象
*/
private void calculateAndSetRowHeight(Row row) {
float defaultRowHeight = 30.0f; // 默认行高
float maxCellHeight = defaultRowHeight;
// 遍历行中的每个单元格,根据内容计算所需高度
for (Cell cell : row) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null) {
// 计算基于内容的行高
float cellHeight = calculateCellHeight(cellValue);
if (cellHeight > maxCellHeight) {
maxCellHeight = cellHeight;
}
}
}
// 设置行高,确保不低于默认行高
row.setHeightInPoints(Math.max(maxCellHeight, defaultRowHeight));
}
/**
* 根据单元格内容和列宽计算所需行高
* @param content 单元格内容
* @return 计算的行高
*/
private float calculateCellHeight(String content) {
if (content == null || content.isEmpty()) {
return 19.0f;
}
// 计算需要的行数
String[] lines = content.split("\n");
int lineCount = lines.length;
// 每行大约15点高度
return lineCount * 19;
}
public Boolean textSetRedFont(Cell cell) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null && cellValue.contains("[RED]")) {
Workbook workbook = cell.getSheet().getWorkbook();
// 创建红色字体
Font redFont = workbook.createFont();
redFont.setColor(IndexedColors.RED.getIndex());
redFont.setFontHeight((short) (20 * 14));
redFont.setBold(true);
// 创建正常字体
Font normalFont = workbook.createFont();
normalFont.setColor(IndexedColors.BLACK.getIndex());
normalFont.setFontHeight((short) (20 * 12));
// 移除 [RED] 标记并保留原始内容
String cleanValue = cellValue.replace("[RED]", "");
XSSFRichTextString richText = new XSSFRichTextString(cleanValue);
// 分割文本为行
String[] lines = cleanValue.split("\n");
int currentIndex = 0;
for (String line : lines) {
// 检查当前行是否包含 [RED] 标记
if (cellValue.contains("[RED]"+ line)) {
richText.applyFont(currentIndex, currentIndex + line.length(), redFont);
} else {
richText.applyFont(currentIndex, currentIndex + line.length(), normalFont);
}
currentIndex += line.length() + 1; // +1 for \n
}
cell.setCellValue(richText);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
/**
* 安全地获取单元格的字符串值
*
* @param cell 单元格对象
* @return 单元格的字符串表示
*/
private String getCellValueAsString(Cell cell) {
if (cell == null) {
return null;
}
try {
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
// 数值转字符串
return String.valueOf(cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 尝试获取公式结果
try {
return cell.getStringCellValue();
} catch (Exception e) {
try {
return String.valueOf(cell.getNumericCellValue());
} catch (Exception ex) {
return cell.getCellFormula();
}
}
case BLANK:
default:
return "";
}
} catch (Exception e) {
// 如果出现异常,返回空字符串而不是抛出异常
return "";
}
}
private XSSFCellStyle getContextCellStyle(Sheet sheet) {
Workbook workbook = sheet.getWorkbook();
Font font = workbook.createFont();
font.setBold(false);
font.setFontName("宋体");
font.setFontHeight((short) (20 * 11));
XSSFCellStyle headCellStyle = (XSSFCellStyle) workbook.createCellStyle();
//居中
headCellStyle.setAlignment(HorizontalAlignment.CENTER);
headCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
//设置表头高度
//上下居中
headCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headCellStyle.setFont(font);
// 设置自动换行;
headCellStyle.setWrapText(true);
return headCellStyle;
}
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 不需要实现
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 在所有数据写入完成后,设置表头列宽自适应
Sheet sheet = writeSheetHolder.getSheet();
autoSizeHeaderColumns(sheet, 3); // 前3行是表头
}
/**
* 设置表头列宽自适应
*/
private void autoSizeHeaderColumns(Sheet sheet, int headerRowCount) {
if (sheet.getRow(0) == null) {
return;
}
// 遍历所有列
for (int col = 0; col < sheet.getRow(0).getLastCellNum(); col++) {
// 遍历表头行,找到最长的内容
int maxWidth = 0;
for (int row = 0; row < headerRowCount; row++) {
Row headerRow = sheet.getRow(row);
if (headerRow != null) {
Cell cell = headerRow.getCell(col);
if (cell != null) {
String content = cell.getStringCellValue();
if (content != null && !content.isEmpty()) {
// 中文字符算2个单位英文算1个单位
int width = content.replaceAll("[\u4e00-\u9fa5]", "**").length();
maxWidth = Math.max(maxWidth, width);
}
}
}
}
// 设置列宽额外增加2个字符的边距
if (maxWidth > 0) {
sheet.setColumnWidth(col, (maxWidth + 2) * 256);
}
}
}
}

View File

@@ -0,0 +1,41 @@
package jnpf.attendance.excel.listener;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 这里读的时候会调用
*
* @param context
* @return
*/
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return context.getReadCellData().getStringValue();
}
/**
* 这里是写的时候会调用 不用管
*
* @return
*/
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>(context.getValue());
}
}

View File

@@ -0,0 +1,161 @@
package jnpf.attendance.excel.listener;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import jnpf.attendance.service.impl.AttendanceDailyRuleServiceImpl;
import jnpf.entity.attendance.FtbAttendanceDailyRule;
import jnpf.entity.attendance.FtbAttendanceLineSchedulingConfig;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.ScheduleImportEnum;
import jnpf.model.attendance.model.ScheduleImportFailModel;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.util.DateDetail;
import jnpf.util.StringUtil;
import jnpf.util.attendance.DailyRuleUtil;
import jnpf.util.attendance.RuleExcelImportUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
// 有个很重要的点 DemoDataListener 不能被spring管理要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class LineSchedulesDataListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* 缓存的数据
*/
private final List<FtbAttendanceDailyRule> cachedDataList;
private final List<ScheduleImportFailModel> failList;
private final FtbAttendanceLineSchedulingConfig lineSchedulingConfig;
private final Date month;
private final String groupId;
private final List<String> heads;
private final Map<String, PartUserInfoVo> userMap;
private final Map<String, Integer> selfGroupMap;
private int headRow = 0;
public LineSchedulesDataListener(String groupId, List<FtbAttendanceDailyRule> cachedDataList, List<ScheduleImportFailModel> failList, List<String> heads, Date month, Map<String, PartUserInfoVo> userMap, FtbAttendanceLineSchedulingConfig lineSchedulingConfig, Map<String, Integer> selfGroupMap) {
this.cachedDataList = cachedDataList;
this.failList = failList;
this.heads = heads;
this.month = month;
this.groupId = groupId;
this.userMap = userMap;
this.lineSchedulingConfig = lineSchedulingConfig;
this.selfGroupMap = selfGroupMap;
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 处理表头数据
// headMap的key是列索引value是表头名称
headRow++;
if (context.readRowHolder().getRowIndex() < 2) {
return;
}
Assert.isFalse(heads.size() != headMap.size() ||
!CollectionUtils.isEqualCollection(heads, headMap.values()), ScheduleImportEnum.IMPORT_ERROR_FORMAT_WRONG.getMessage());
Assert.isFalse(Objects.isNull(lineSchedulingConfig), ScheduleImportEnum.IMPORT_ERROR_FORMAT_WRONG.getMessage());
}
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
Assert.isFalse(headRow != 3, ScheduleImportEnum.IMPORT_ERROR_FORMAT_WRONG.getMessage());
// 处理每一行数据
// data的key是列索引value是单元格值
//用户人员信息校验
String userName = data.get(0);
String phone = data.get(1);
//划线排班人员校验
if (RuleExcelImportUtil.addFail(StringUtil.isEmpty(userName) || StringUtil.isEmpty(phone), ScheduleImportEnum.IMPORT_ERROR_UNIDENTIFIED_MEMBER, userName, failList)) {
return;
}
PartUserInfoVo userEntity = userMap.get(phone);
if (RuleExcelImportUtil.addFail(Objects.isNull(userEntity), ScheduleImportEnum.IMPORT_ERROR_MEMBER_NOT_IN_GROUP, userName, failList)) {
return;
}
if (RuleExcelImportUtil.addFail(!userEntity.getRealName().equals(userName), ScheduleImportEnum.IMPORT_ERROR_UNIDENTIFIED_MEMBER, userName, failList)) {
return;
}
if (RuleExcelImportUtil.addFail(cachedDataList.stream().anyMatch(vo -> vo.getUserId().equals(userEntity.getUserId())), ScheduleImportEnum.IMPORT_ERROR_DUPLICATE_MEMBERS, userEntity.getRealName(), failList)) {
return;
}
Integer selfGroup = selfGroupMap.get(userEntity.getUserId());
if (RuleExcelImportUtil.addFail(Objects.isNull(selfGroup), ScheduleImportEnum.IMPORT_ERROR_MEMBER_NOT_IN_GROUP, userName, failList)) {
return;
}
DateTime today = DateUtil.beginOfDay(new Date());
//班次信息校验
List<FtbAttendanceDailyRule> cachedDayDataList = CollUtil.newArrayList();
data.forEach((index, periodSrt) -> {
if (index < 2 || heads.size() < index - 1) {
return;
}
String head = heads.get(index);
String dayIndex = head.split("\n")[1];
DateTime day = DateUtil.offsetDay(month, Integer.parseInt(dayIndex) - 1);
if (day.isBefore(today)) {
return;
}
Date startTime = DateDetail.getDateByPoint(day, lineSchedulingConfig.getStartTime(), lineSchedulingConfig.getStartType());
Date endTime = DateUtil.offsetHour(DateDetail.getDateByPoint(day, lineSchedulingConfig.getEndTime(), lineSchedulingConfig.getEndType()), 1);
if (StringUtil.isEmpty(periodSrt)) {
FtbAttendanceDailyRule ftbAttendanceDailyRule = AttendanceDailyRuleServiceImpl.buildLineSchedulesRule(groupId, selfGroup, day, day, day, userEntity.getUserId(), lineSchedulingConfig);
ftbAttendanceDailyRule.setAttendanceType(AttendanceTypeEnum.CLEAR.getCode());
cachedDataList.add(ftbAttendanceDailyRule);
return;
}
cachedDayDataList.clear();
String[] periodArr = periodSrt.split("\\|");
for (String period : periodArr) {
if (StringUtil.isEmpty(period)) {
continue;
}
String[] dateArr = period.trim().split("-");
try {
Date start = DateDetail.getDateByPoint(day, dateArr[0], 1);
Date end = DateDetail.getDateByPoint(day, dateArr[1], 1);
if (start.after(end)) {
end = DateUtil.offsetDay(end, 1);
}
Date ceilStart = new Date(start.getTime());
DailyRuleUtil.ceilToHalfHour(start);
Date ceilEnd = new Date(end.getTime());
DailyRuleUtil.ceilToHalfHour(end);
if (ceilStart.compareTo(start) != 0 || ceilEnd.compareTo(end) != 0) {
RuleExcelImportUtil.addFail(true, ScheduleImportEnum.IMPORT_ERROR_PERIOD_INVALID, userEntity.getRealName(), failList);
return;
}
if (start.before(startTime) || end.after(endTime)) {
RuleExcelImportUtil.addFail(true, ScheduleImportEnum.IMPORT_ERROR_OVERDUE_TIME_INVALID, userEntity.getRealName(), failList);
return;
}
cachedDayDataList.add(AttendanceDailyRuleServiceImpl.buildLineSchedulesRule(groupId, selfGroup, day, start, end, userEntity.getUserId(), lineSchedulingConfig));
} catch (Exception e) {
RuleExcelImportUtil.addFail(true, ScheduleImportEnum.IMPORT_ERROR_PERIOD_INVALID, userEntity.getRealName(), failList);
return;
}
}
cachedDataList.addAll(DailyRuleUtil.mergeRules(cachedDayDataList));
});
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成后的操作
log.error("所有数据解析完成");
}
}

View File

@@ -0,0 +1,177 @@
package jnpf.attendance.excel.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import jnpf.enums.attendance.AttendanceTypeEnum;
import jnpf.enums.attendance.ScheduleImportEnum;
import jnpf.model.attendance.model.ScheduleImportFailModel;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.ShiftNameVo;
import jnpf.permission.model.user.PartUserInfoVo;
import jnpf.util.StringUtil;
import jnpf.util.attendance.RuleExcelImportUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import java.util.*;
import java.util.stream.Collectors;
// 有个很重要的点 DemoDataListener 不能被spring管理要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class SchedulesDataListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* 缓存的数据
*/
private final List<SchedulesV2Vo> cachedDataList;
private final List<ScheduleImportFailModel> failList;
private final Date month;
private final List<String> heads;
private final Map<String, ShiftNameVo> shiftMap;
private final Map<String, PartUserInfoVo> userMap;
private static final String format = "yyyy-MM-dd";
private int headRow = 0;
public SchedulesDataListener(List<SchedulesV2Vo> cachedDataList, List<ScheduleImportFailModel> failList, List<String> heads, Date month, Map<String, ShiftNameVo> shiftMap, Map<String, PartUserInfoVo> userMap) {
this.cachedDataList = cachedDataList;
this.failList = failList;
this.heads = heads;
this.month = month;
this.shiftMap = shiftMap;
this.userMap = userMap;
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 处理表头数据
// headMap的key是列索引value是表头名称
headRow++;
if (context.readRowHolder().getRowIndex() < 2) {
return;
}
Assert.isFalse(heads.size() != headMap.size() ||
!CollectionUtils.isEqualCollection(heads, headMap.values()), ScheduleImportEnum.IMPORT_ERROR_FORMAT_WRONG.getMessage());
}
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
Assert.isFalse(headRow != 3, ScheduleImportEnum.IMPORT_ERROR_FORMAT_WRONG.getMessage());
// 处理每一行数据
// data的key是列索引value是单元格值
//用户人员信息校验
String userName = data.get(0);
String phone = data.get(1);
if (RuleExcelImportUtil.addFail(StringUtil.isEmpty(userName) || StringUtil.isEmpty(phone), ScheduleImportEnum.IMPORT_ERROR_UNIDENTIFIED_MEMBER, userName, failList)) {
return;
}
PartUserInfoVo userEntity = userMap.get(phone);
if (RuleExcelImportUtil.addFail(Objects.isNull(userEntity), ScheduleImportEnum.IMPORT_ERROR_MEMBER_NOT_IN_GROUP, userName, failList)) {
return;
}
if (RuleExcelImportUtil.addFail(!userEntity.getRealName().equals(userName), ScheduleImportEnum.IMPORT_ERROR_UNIDENTIFIED_MEMBER, userName, failList)) {
return;
}
if (RuleExcelImportUtil.addFail(cachedDataList.stream().anyMatch(vo -> vo.getUserId().equals(userEntity.getUserId())), ScheduleImportEnum.IMPORT_ERROR_DUPLICATE_MEMBERS, userEntity.getRealName(), failList)) {
return;
}
LinkedHashMap<String, SchedulesDayVo> schedulesDaySetDto = new LinkedHashMap<>();
cachedDataList.add(SchedulesV2Vo.builder()
.userId(userEntity.getUserId())
.mobilePhone(userEntity.getMobilePhone())
.realName(userEntity.getRealName())
.val(schedulesDaySetDto)
.build());
//班次信息校验
data.forEach((index, shiftNameSrt) -> {
if (index < 3 || heads.size() < index - 1) {
return;
}
String head = heads.get(index);
String dayIndex = head.split("\n")[1];
DateTime day = DateUtil.offsetDay(month, Integer.parseInt(dayIndex) - 1);
String dayStr = DateUtil.format(day, format);
if (StringUtil.isEmpty(shiftNameSrt)) {
//清空
schedulesDaySetDto.put(dayStr, SchedulesDayVo.builder().itemVos(List.of(SchedulesItemVo.builder().type(0).build())).build());
return;
}
String[] shiftNameArr = shiftNameSrt.split("\\|");
int length = shiftNameArr.length;
ArrayList<SchedulesItemVo> list = Lists.newArrayList();
schedulesDaySetDto.put(dayStr, SchedulesDayVo.builder().itemVos(list).build());
for (int i = 0; i < shiftNameArr.length; i++) {
//上午半天是否为休
boolean isAMOff = list.stream().anyMatch(vo -> Objects.equals(vo.getType(), 1) || Objects.equals(vo.getType(), 3) || Objects.equals(vo.getType(), 4));
String shiftName = shiftNameArr[i];
//判断班次类型
ShiftNameVo shiftPeriodVo = shiftMap.get(shiftName);
if (Objects.nonNull(shiftPeriodVo)) {
//是否为全天班
if (length > 1 && Arrays.stream(shiftNameArr).noneMatch(vo -> StringUtil.equals(vo, AttendanceTypeEnum.REST.getMsg()))) {
RuleExcelImportUtil.addFail(true, ScheduleImportEnum.IMPORT_ERROR_SHIFT_NAME_INVALID, userEntity.getRealName(), failList);
return;
}
//是否为全天班设置了两次
List<SchedulesItemVo> collect = list.stream().filter(vo -> Objects.equals(vo.getShiftId(), shiftPeriodVo.getId())).peek(vo -> vo.setSchedulesType(0)).collect(Collectors.toList());
if (CollUtil.isNotEmpty(collect)) {
continue;
}
list.add(SchedulesItemVo.builder()
.schedulesType(length < 2 ? 0 : i + 1)
.type(2)
.sort(length < 2 ? 0 : i + 1)
.shiftId(shiftPeriodVo.getId())
.name(shiftPeriodVo.getName())
.shortName(shiftPeriodVo.getShortName())
.colour(shiftPeriodVo.getColour())
.build());
continue;
}
//秀
if (!isAMOff && StringUtil.equals(shiftName, AttendanceTypeEnum.REST.getMsg())) {
list.add(SchedulesItemVo.builder()
.schedulesType(length < 2 ? 0 : i + 1)
.type(1)
.build());
continue;
}
if (!isAMOff && StringUtil.equals(shiftName, AttendanceTypeEnum.DEDUCTION_HOLIDAYS.getMsg())) {
list.add(SchedulesItemVo.builder()
.schedulesType(length < 2 ? 0 : i + 1)
.type(3)
.build());
continue;
}
if (!isAMOff && StringUtil.equals(shiftName, AttendanceTypeEnum.EXCHANGE_HOLIDAYS.getMsg())) {
list.add(SchedulesItemVo.builder()
.schedulesType(length < 2 ? 0 : i + 1)
.type(4)
.build());
continue;
}
RuleExcelImportUtil.addFail(true, ScheduleImportEnum.IMPORT_ERROR_SHIFT_NAME_INVALID, userEntity.getRealName(), failList);
return;
}
});
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成后的操作
log.error("所有数据解析完成");
}
}

View File

@@ -0,0 +1,86 @@
package jnpf.attendance.handler;
import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
public class JsonToListTypeHandler extends BaseTypeHandler<List<Object>> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<Object> parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, toJson(parameter));
}
@Override
public List<Object> getNullableResult(ResultSet rs, String columnName) throws SQLException {
List<Object> list = parseJson(rs.getString(columnName));
if(CollUtil.isEmpty(list)){
return null;
}
// 删除掉为空的数据
list.removeIf(Objects::isNull);
return list;
}
@Override
public List<Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
List<Object> list = parseJson(rs.getString(columnIndex));
if(CollUtil.isEmpty(list)){
return null;
}
// 删除掉为空的数据
list.removeIf(Objects::isNull);
return list;
}
@Override
public List<Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
List<Object> list = parseJson(cs.getString(columnIndex));
if(CollUtil.isEmpty(list)){
return null;
}
// 删除掉为空的数据
list.removeIf(Objects::isNull);
return list;
}
private String toJson(List<Object> list) {
try {
return OBJECT_MAPPER.writeValueAsString(list);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<Object> parseJson(String json) {
try {
if (json == null || json.isEmpty()) {
return null;
}
List<Object> result = OBJECT_MAPPER.readValue(json,
OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Object.class)
);
return result.stream()
.filter(Objects::nonNull)
.flatMap(item -> {
if (item instanceof List) {
List<?> itemList = (List<?>) item;
return itemList.stream().filter(Objects::nonNull);
} else {
return java.util.stream.Stream.of(item);
}
}).collect(java.util.stream.Collectors.toList());
} catch (Exception e) {
throw new RuntimeException("JSON解析失败: " + json, e);
}
}
}

View File

@@ -0,0 +1,13 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.ApiCallLog;
/**
* 接口调用记录Mapper
*
* @author yanwenfu
* @create 2026-01-28
*/
public interface ApiCallLogMapper extends SuperMapper<ApiCallLog> {
}

View File

@@ -0,0 +1,23 @@
package jnpf.attendance.mapper;
import jnpf.model.attendance.vo.attendance.ClockRecordVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 考勤ai Mapper
*
* @author yanwenfu
* @create 2026-05-07
*/
public interface AttendanceAIMapper {
/**
* 查询打卡记录列表
* @param userId 用户id
* @param queryDate 查询日期
* @return java.util.List<jnpf.model.attendance.vo.attendance.ClockRecordVo>
*/
List<ClockRecordVo> getClockRecordList(@Param("userId") String userId, @Param("queryDate") String queryDate);
}

View File

@@ -0,0 +1,16 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jnpf.entity.AttendanceApprovalSetting;
/**
* <p>
* 考勤审批设置表 Mapper 接口
* </p>
*
* @author Auto-generator
* @since 2023-11-21
*/
public interface AttendanceApprovalSettingMapper extends BaseMapper<AttendanceApprovalSetting> {
}

View File

@@ -0,0 +1,32 @@
package jnpf.attendance.mapper;
import jnpf.model.attendance.vo.OvertimeVouchersVo;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* describe
* 考勤审批相关
* @author HuangLinPan
* @date 2023/11/22
*/
public interface AttendanceApproveMapper {
/**
* 查前天的加班出勤规则找这些出勤规则对应的加班结果表数据
* @param date 日期
* @return java.util.List<jnpf.model.attendance.vo.OvertimeVouchersVo>
* @author hlp
*/
List<OvertimeVouchersVo> getOvertimeVouchersList(@Param("date") Date date);
/**
* 校验是否重复发放加班劵
* @param ruleId 排班Id
* @param code 类型 : 1.节日 2.假日 3.加班(调休)
*/
Integer getOvertimeByRuleId(@Param("ruleId") String ruleId, @Param("code") Integer code);
}

View File

@@ -0,0 +1,231 @@
package jnpf.attendance.mapper;
import jnpf.entity.attendance.AttendanceBalanceRecordEntity;
import jnpf.entity.attendance.AttendanceStorageRest;
import jnpf.model.attendance.model.*;
import jnpf.model.attendance.vo.AttendanceBalanceRecordVo;
import jnpf.model.attendance.vo.UserBalanceVo;
import jnpf.model.attendance.vo.VacationVo;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
public interface AttendanceBalanceRecordMapper {
/**
* 余额管理列表
*
* @param balanceQueryDto 查询参数
* @return java.util.List<jnpf.model.attendance.vo.UserBalanceVo>
* @author hlp
*/
// @Deprecated
// List<UserBalanceVo> getBalanceList(@Param("balanceQueryDto") BalanceQueryDto balanceQueryDto, @Param("balanceSort") String balanceSort, @Param("paidBalanceSort") String paidBalanceSort, @Param("unpaidBalanceSort") String unpaidBalanceSort, @Param("adventBalance") String adventBalance);
/**
* 新增假期节假日劵
*
* @param id 主键id
* @param userId 用户
* @param holidayName 节假日名称
* @param dayNum 总额
* @param expiresTime 过期时间
* @param unit 单位 : 1 :小时 2: 天
* @param grantWay 发放方式 0.自动发放 1.手动发放
* @param createUserId 创建人
* @param paid 是否带薪1带薪 2非带薪
* @param type 类型 : 1.节日 2.假日 3.加班
* @param objectId 关联id和Type联合使用 节日时是节日id 假日是是假日id 加班时是加班id
* @author hlp
*/
// @Deprecated
// void grantBalance(@Param("id") String id, @Param("userId") String userId, @Param("holidayName") String holidayName, @Param("dayNum") BigDecimal dayNum, @Param("expiresTime") Date expiresTime, @Param("unit") Integer unit, @Param("grantWay") Integer grantWay, @Param("createUserId") String createUserId, @Param("paid") Integer paid, @Param("type") Integer type, @Param("objectId") String objectId);
/**
* 余额记录列表
*
* @param balanceQueryDto 参数
* @return java.util.List<jnpf.model.attendance.vo.AttendanceBalanceRecordVo>
* @author hlp
*/
// @Deprecated
// List<AttendanceBalanceRecordVo> getUserBalance(@Param("balanceQueryDto") BalanceQueryDto balanceQueryDto);
/**
* 成员余额记录列表
*
* @param balanceQueryDto 参数
* @return java.util.List<jnpf.model.attendance.vo.AttendanceBalanceRecordVo>
* @author hlp
*/
// @Deprecated
// List<AttendanceBalanceRecordVo> getUserBalanceDetail(@Param("balanceQueryDto") BalanceQueryDto balanceQueryDto);
/**
* 修改指定劵的状态
*
* @param id 劵的id
* @param state 状态 0 正常 1 过期 2已停用
* @author hlp
*/
// @Deprecated
// void updateBalanceState(@Param("id") String id, @Param("state") Integer state, @Param("userId") String userId);
/**
* 获取用户余额
*
* @param userId 用户id
* @return jnpf.model.attendance.vo.UserBalanceVo
* @author hlp
*/
@Deprecated
UserBalanceVo getBalanceByUserId(@Param("userId") String userId);
/**
* 过期劵
*
* @author hlp
*/
void invalidationCoupons(@Param("ids") List<String> ids);
/**
* 获取节日信息
*
* @param balanceId 节日id
* @return jnpf.model.attendance.vo.VacationVo
* @author hlp
*/
VacationVo getFestivalSetting(@Param("balanceId") String balanceId);
/**
* 获取假日配置信息
*
* @param balanceId 假日id
* @return jnpf.model.attendance.vo.VacationVo
* @author hlp
*/
VacationVo getHolidaySetting(@Param("balanceId") String balanceId);
/**
* 获取用户的劵过期的排前面
*
* @param userId 用户id
* @param paid 是否带薪1带薪 2非带薪
* @return java.util.List<jnpf.model.attendance.vo.AttendanceBalanceRecordVo>
* @author hlp
*/
@Deprecated
List<AttendanceBalanceRecordVo> getBalanceRecordByUserId(@Param("userId") String userId, @Param("paid") List<Integer> paid);
/**
* 修改劵的使用情况
*
* @param id 主键id
* @param balance 余额
* @param over 是否使用完 0未使用完 1使用完
* @author hlp
*/
void updateBalance(@Param("id") String id, @Param("balance") BigDecimal balance, @Param("over") Integer over);
/**
* 获取用户指定类型假的余额总数
*
* @param userId 用户id
* @param paid 是否带薪1带薪 2非带薪
* @return java.math.BigDecimal
* @author hlp
*/
// BigDecimal getBalanceRecordCountByUserId(@Param("userId") String userId, @Param("paid") Integer paid);
/**
* 通过劵的id获取劵的详情
*
* @param balanceIds 劵的id
* @return java.util.List<jnpf.model.attendance.vo.AttendanceBalanceRecordVo>
* @author hlp
*/
List<AttendanceBalanceRecordVo> getBalanceDetailList(@Param("balanceIds") List<String> balanceIds);
/**
* 验证出勤规则id有无发加班劵
*
* @param ruleId 出勤规则id
* @return java.lang.Integer
* @author hlp
*/
Integer getBalanceByRuleId(@Param("ruleId") String ruleId);
/**
* 批量获取用户余额
*
* @param userIds 用户ids
*/
List<UserBalanceVo> getBalanceByUserIds(@Param("userIds") List<String> userIds);
/**
* 根据申请id集合批量获取用户余额
*
* @param applyIds 申请id集合
*/
List<BalanceRecord> getBalanceByApplyIds(@Param("applyIds") List<String> applyIds);
/**
* 获取用户余额
*
* @return
*/
List<AttendanceStorageRest> getStraightBalanceList();
/**
* 删除当月存休
*/
void deleteByYearMonth(@Param("lastMonthDate") String lastMonthDate);
/**
* 查询用户加班存休天数以及剩余天数
*
* @param userIds 用户id集合
*/
List<OvertimeBalanceModel> getOvertimeBalanceInfo(@Param("startDate") Date startDate, @Param("endDate") Date endDate,
@Param("userIds") List<String> userIds);
/**
* 查询用户存休剩余天数
*
* @param userIds 用户id集合
*/
List<SurplusDaysModel> getSurplusDaysInfo(@Param("userIds") List<String> userIds);
/**
* 查询用户节假日加班天数
*
* @param userIds 用户id集合
*/
List<OvertimeHolidaysInfoModel> getOvertimeHolidaysInfo(@Param("startDate") Date startDate, @Param("endDate") Date endDate,
@Param("userIds") List<String> userIds);
/**
* 查询查询用户各个请假类型余额天数
*
* @param userIds 用户id集合
*/
List<LeaveBalanceInfoModel> getLeaveBalanceInfo(@Param("startDate") Date startDate, @Param("endDate") Date endDate,
@Param("userIds") List<String> userIds);
/**
* 查询查询用户各个请假审批数据
*
* @param userIds 用户id集合
*/
List<LeaveApprovalModel> getLeaveApprovalInfo(@Param("leaveIds") List<String> leaveIds,
@Param("userIds") List<String> userIds);
List<AttendanceBalanceRecordEntity> selectInvalidationCoupons();
}

View File

@@ -0,0 +1,58 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jnpf.entity.attendance.AttendanceBalanceUseRecord;
import jnpf.model.doclibrary.vo.UseDetailVo;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
public interface AttendanceBalanceUseRecordMapper extends BaseMapper<AttendanceBalanceUseRecord> {
/**
* 获取用户劵的使用劵详情
* @param id 劵的Id
*/
List<UseDetailVo> getUserBalanceDetail(String id);
/**
* 新增劵使用记录
* @param id 主键id
* @param balanceId 余额劵id
* @param quota 使用额度
* @param unit 单位 : 1 :小时 2: 天
* @param userType 使用方式 0: 审批 ,1:排班
* @param objectId 关联id和useType联合使用
* @param startTime 开始时间
* @param endTime 结束时间
* @param lock 锁定: 1锁定 2已消费
* @author hlp
*/
void addOne(@Param("id") String id, @Param("balanceId") String balanceId, @Param("quota") BigDecimal quota, @Param("unit") Integer unit, @Param("userType") Integer userType, @Param("objectId") String objectId, @Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("lock") Integer lock);
/**
* 查出本次排班相关的所有消费记录
* @param id 关联id和useType联合使用
* @param userType 使用方式 0: 审批 ,1:排班
* @return java.util.List<jnpf.model.doclibrary.vo.UseDetailVo>
* @author hlp
*/
List<UseDetailVo> getUserBalanceListByObjectId(@Param("id") String id, @Param("userType") Integer userType);
/**
* 根据劵的使用记录id删除记录
* @param userBalanceList 劵的使用记录id
* @author hlp
*/
void deleteByIds(@Param("userBalanceList") List<UseDetailVo> userBalanceList);
/**
* 批量新增劵使用记录
* @param useRecordList 劵使用记录
* @author hlp
*/
void addBatch(@Param("useRecordList") List<AttendanceBalanceUseRecord> useRecordList);
}

View File

@@ -0,0 +1,22 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceBaseSetting;
/**
* <p>
* 考勤基础设置表 Mapper 接口
* </p>
*
* @author ahua
* @since 2023-11-29
*/
public interface AttendanceBaseSettingMapper extends SuperMapper<AttendanceBaseSetting> {
/**
* 获取出勤换算
* @param userId
* @return
*/
Integer getAttendanceRatio(String userId);
}

View File

@@ -0,0 +1,16 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceBookConfigEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 考勤本配置Mapper
*
* @author Generated
* @create 2026-04-15
*/
@Mapper
public interface AttendanceBookConfigMapper extends SuperMapper<AttendanceBookConfigEntity>{
}

View File

@@ -0,0 +1,15 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jnpf.entity.attendance.AttendanceBookOperationLogEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 考勤本操作日志Mapper
*
* @author Generated
* @create 2026-04-15
*/
@Mapper
public interface AttendanceBookOperationLogMapper extends BaseMapper<AttendanceBookOperationLogEntity> {
}

View File

@@ -0,0 +1,18 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import jnpf.entity.attendance.AttendanceBookRecordEntity;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
/**
* 考勤本记录表Mapper
*
* @author Generated
* @create 2026-04-15
*/
@Mapper
@Component
public interface AttendanceBookRecordMapper extends BaseMapper<AttendanceBookRecordEntity> {
}

View File

@@ -0,0 +1,6 @@
package jnpf.attendance.mapper;
public interface AttendanceCardReplacementApproveMapper {
}

View File

@@ -0,0 +1,114 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.FtbAttendanceClockIn;
import jnpf.model.attendance.model.ClockClassRecord;
import jnpf.model.attendance.vo.*;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* 打卡mapper
*
* @author yanwenfu
* @create 2023-11-21
*/
public interface AttendanceClockInMapper extends SuperMapper<FtbAttendanceClockIn> {
/**
* 查询用户出勤结果
* @param list 用户id
* @return java.util.List<jnpf.model.attendance.vo.ClockInVo>
*/
List<ClockInVo> getClockInResultByRule(@Param("list") List<RuleUserVo> list);
/**
* 查询用户出勤结果
* @param ruleIdList 出勤规则ids
* @return java.util.List<jnpf.model.attendance.vo.ClockInVo>
*/
List<ClockInVo> getClockInResultByRuleList(@Param("list") List<String> ruleIdList);
/**
* 查询时间段内的补卡次数
* @param startTime 开始时间
* @param endTime 结束时间
* @param userId 用户id
* @param groupId 考勤组id
* @return int
*/
int getRepairCount(@Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("userId") String userId, @Param("groupId") String groupId);
/**
* 查询考勤组本月异常打卡记录
* @param userId 用户id
* @param groupId 考勤组id
* @param monthBeginDate 月初
* @param monthEndDate 月末
* @param absenceType 能否补缺勤卡(1: 是, 0: 否)
* @param queryTypeList 补卡类型(1: 迟到, 2: 早退, 3: 缺卡)
* @return java.util.List<jnpf.model.attendance.vo.AbnormalClockInVo>
*/
List<AbnormalClockInVo> getAbnormalClockInList(@Param("userId") String userId, @Param("groupId") String groupId,
@Param("monthBeginDate") Date monthBeginDate, @Param("monthEndDate") Date monthEndDate,
@Param("absenceType") Integer absenceType, @Param("list") List<Integer> queryTypeList);
/**
* 更新为缺勤
* @param ruleId 出勤规则id
* @param userId 用户id
* @param handleUser 上次操作人
* @return int
*/
int updateToAbsence(@Param("ruleId") String ruleId, @Param("userId") String userId, @Param("handleUser") String handleUser);
/**
* 更新为不缺勤
* @param ruleId 出勤规则id
* @param userId 用户id
* @param handleUser 上次操作人
* @return int
*/
int updateToUnAbsence(@Param("ruleId") String ruleId, @Param("userId") String userId, @Param("handleUser") String handleUser);
/***
* 查询当日所有打卡记录
* @param userId 用户id
* @param queryDate 日期
* @return java.util.List<jnpf.model.attendance.vo.DailyClockInVo>
*/
List<DailyClockInVo> getListByRuleAndDate(@Param("userId") String userId, @Param("queryDate") String queryDate);
/***
* 查询当日所有打卡记录
* @param userId 用户id
* @param queryDate 日期
* @return java.util.List<jnpf.model.attendance.vo.DailyClockInVo>
*/
List<ClockClassRecord> getClockInByStatistics(@Param("userId") String userId, @Param("queryDate") Date queryDate);
/**
* 批量查询所有打卡记录
* @param userIds 用户id集合
* @param start 开始时间
* @param end 结束时间
*/
List<ClockClassRecord> getClockInList(@Param("userIds") List<String> userIds, @Param("start") String start, @Param("end") String end);
/**
* 查询打卡记录[批量]
* @param userDayList 用户日期列表
* @param approvalStatus 审批状态
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceClockIn>
*/
List<FtbAttendanceClockIn> selectListBatch(@Param("list") List<UserDayVo> userDayList, @Param("approvalStatus") Integer approvalStatus);
/**
* 删除审批中的打卡记录
* @param userDayList 用户日期列表
* @param passApproval 审批状态
*/
void deleteBatchByUserDay(@Param("list") List<UserDayVo> userDayList, @Param("passApproval") Integer passApproval);
}

View File

@@ -0,0 +1,13 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceClockInPic;
/**
* 外勤打卡图片mapper
*
* @author yanwenfu
* @create 2023-11-21
*/
public interface AttendanceClockInPicMapper extends SuperMapper<AttendanceClockInPic> {
}

View File

@@ -0,0 +1,189 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceClockInResult;
import jnpf.model.attendance.vo.ChangeInfoVo;
import jnpf.model.attendance.vo.ConditionVo;
import jnpf.model.attendance.vo.RuleDayVo;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* 打卡结果mapper
*
* @author yanwenfu
* @create 2023-11-27
*/
public interface AttendanceClockInResultMapper extends SuperMapper<AttendanceClockInResult> {
/**
* 根据出勤规则删除用户的打卡结果
* @param userId 用户id
* @param updateUserId 操作人
* @return int
*/
int deleteRecordByRule(@Param("userId") String userId, @Param("updateUserId") String updateUserId);
/**
* 根据出勤规则删除用户的打卡结果[批量]
* @param userIds 用户id列表
* @param updateUserId 操作人
* @return int
*/
int deleteRecordByRuleBatch(@Param("list") List<String> userIds, @Param("updateUserId") String updateUserId);
/**
* 根据条件查询用户打卡记录
* @param dateStr 日期
* @return java.util.List<jnpf.entity.attendance.AttendanceClockInResult>
*/
List<AttendanceClockInResult> selectUserClockInHistory(String dateStr);
/**
* 根据考勤组id,用户id及时间范围查询打卡结果记录
*/
List<ChangeInfoVo> selectUserClockInResult(@Param("groupId") String groupId, @Param("userId") String userId, @Param("start") Date start, @Param("end") Date end);
/**
* 批量更新记录为删除
* @param delList 删除列表
* @return int
*/
int updateBatchToDel(@Param("list") List<String> delList);
/**
* 更新申请信息为空
* @param clockInId 打卡id
* @param applyId 审批id
* @return int
*/
int updateReplyToNull(@Param("clockInId") String clockInId, @Param("applyId") String applyId);
/**
* 判断是否缺勤
* @param ruleId 出勤规则
* @param userId 用户id
* @return int
*/
int countAbsence(@Param("ruleId") String ruleId, @Param("userId") String userId);
/**
* 更新审批为空
* @param id 结果id
* @param dealUser 处理人
* @return int
*/
int updateResultReplyToNull(@Param("id") String id, @Param("dealUser") String dealUser);
/**
* 查询已存在的打卡结果
* @param list 打卡结果列表
* @param containsType 包含打卡类型(上/下班) 1: 是, 0: 否
* @return java.util.List<jnpf.entity.attendance.AttendanceClockInResult>
*/
List<AttendanceClockInResult> selectExistRecord(@Param("list") List<AttendanceClockInResult> list, @Param("containsType") Integer containsType);
/**
* 根据出勤规则删除打卡结果
* @param list 出勤规则列表
* @param updateUserId 更新用户id
* @return int
*/
int updateToDelByRule(@Param("list") List<String> list, @Param("updateUserId") String updateUserId);
/**
* 查询跨天的打卡id
* @param ruleIds 出勤规则ids
* @param day 日期
* @return java.util.List<java.lang.String>
*/
List<String> getCrossDayClockInIdList(@Param("list") List<String> ruleIds, @Param("day") Date day);
/**
* 查询跨天的打卡id[批量]
* @param ruleDayList 规则日期列表
* @return java.util.List<java.lang.String>
*/
List<String> getCrossDayClockInIdListBatch(@Param("list") List<RuleDayVo> ruleDayList);
/**
* 查询打卡结果是否在审批中
* @param clockInResultId 打卡结果id
* @return int
*/
int getReplyingCount(String clockInResultId);
/**
* 更新缺勤为正常
* @param id 结果id
* @return int
*/
int updateAbsenceToNormal(String id);
/**
* 更新为缺勤
* @param id 结果id
* @return int
*/
int updateToAbsence(String id);
/**
* 根据用户和日期查询打卡结果
* @param userId 用户id
* @param date 日期
* @return java.util.List<java.lang.String>
*/
List<String> getByUserAndDay(@Param("userId") String userId, @Param("date") Date date);
/**
* 根据出勤规则删除打卡结果
* @param ruleIdList 出勤规则列表
* @return int
*/
int removeClockInResultByRule(@Param("list") List<String> ruleIdList);
/**
* 获取用户打卡天数
*/
Integer getClockInDayNum(String userId);
/**
* 获取用户迟到次数
*/
Integer getLateClockInNum(@Param("dayRuleIds") List<String> dayRuleIds);
/**
* 查询补卡审批中次数
* @param userId 用户id
* @param groupId 考勤组id
* @return int
*/
int selectRepairApplyCount(@Param("userId") String userId, @Param("groupId") String groupId, @Param("beginDate") String beginDate, @Param("endDate") String endDate);
/**
* 根据打卡ids查询打卡结果
* @param clockInIds 打卡ids
* @return java.util.List<jnpf.entity.attendance.AttendanceClockInResult>
*/
List<AttendanceClockInResult> selectBatchByClockInIds(@Param("list") List<String> clockInIds);
/**
* 变更打卡结果绑定的rule
* @param resultId 打卡结果id
* @param ruleId 出勤规则id
*/
void updateResultRelation(@Param("resultId") String resultId, @Param("ruleId") String ruleId);
/**
* 查询需要更新为缺勤的打卡结果
* @param conditionList 缺勤条件
* @return java.util.List<jnpf.model.attendance.vo.ConditionVo>
*/
List<ConditionVo> selectRuleIdsForAbsence(@Param("list") List<ConditionVo> conditionList);
/**
* 更新为缺勤[批量]
* @param updateResultIds 打卡结果ids
*/
void updateToAbsenceBatch(@Param("list") List<String> updateResultIds);
}

View File

@@ -0,0 +1,12 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceCloudAlbum;
/**
* @Description: 考勤云相册表
* @Author: shiTou(他是小石头)
* @Date: 2024-10-30 10:26
*/
public interface AttendanceCloudAlbumMapper extends SuperMapper<AttendanceCloudAlbum> {
}

View File

@@ -0,0 +1,14 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceConfirmDetails;
/**
* 考勤确认详情
*
* @author shitou
* @email shitou@niujiekeji.com
* @date 2024-11-07 09:33:43
*/
public interface AttendanceConfirmDetailsMapper extends SuperMapper<AttendanceConfirmDetails> {
}

View File

@@ -0,0 +1,39 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceConfirm;
import jnpf.model.attendance.vo.attendance.ConfirmListQueryVo;
import jnpf.model.attendance.vo.attendance.ConfirmStatisticsVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 考勤确认
*
* @author shitou
* @email shitou@niujiekeji.com
* @date 2024-11-07 09:33:43
*/
public interface AttendanceConfirmMapper extends SuperMapper<AttendanceConfirm> {
/**
* 考勤确认聚合统计
*
* @param userIds 用户ID集合
* @param year 考勤年份
* @param month 月份
* @return 考勤确认聚合统计
*/
ConfirmStatisticsVo getStatistics(@Param("userIds") List<String> userIds, @Param("year") int year, @Param("month") int month,@Param("type") Integer type);
/**
* 考勤确认列表查询
*
* @param userIds 用户ID集合
* @param year 考勤年份
* @param month 月份
* @param type 数据类型
* @return 考勤确认列表
*/
List<ConfirmListQueryVo> getPageList(@Param("userIds") List<String> userIds, @Param("year") int year, @Param("month") int month, @Param("type") Integer type);
}

View File

@@ -0,0 +1,14 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceConfirmSetting;
/**
* 考勤确认设置
*
* @author shitou
* @email shitou@niujiekeji.com
* @date 2024-11-07 09:33:43
*/
public interface AttendanceConfirmSettingMapper extends SuperMapper<AttendanceConfirmSetting> {
}

View File

@@ -0,0 +1,16 @@
package jnpf.attendance.mapper;
import jnpf.entity.AttendanceCustomizeTable;
import jnpf.base.mapper.SuperMapper;
/**
* <p>
* 考勤-自定义报表设置 Mapper 接口
* </p>
*
* @author ahua
* @since 2024-09-03
*/
public interface AttendanceCustomizeTableMapper extends SuperMapper<AttendanceCustomizeTable> {
}

View File

@@ -0,0 +1,166 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.FtbAttendanceDailyRule;
import jnpf.model.attendance.vo.*;
import jnpf.model.attendance.vo.attendance.YesterdayRuleVo;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
/**
* <p>
* 考勤组-每日出勤规则 Mapper 接口
* </p>
*
* @author ahua
* @since 2023-11-22
*/
public interface AttendanceDailyRuleMapper extends SuperMapper<FtbAttendanceDailyRule> {
/**
* 查询昨天和今天的出勤规则
* @param currentDate 当前日期
* @param userId 用户id
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getRuleClockTimeContainsToday(@Param("currentDate") Date currentDate, @Param("userId") String userId);
/**
* 查询指定日期的最后一个出勤规则
* @param currentDate 当前日期
* @param userId 用户id
* @return jnpf.entity.attendance.FtbAttendanceDailyRule
*/
FtbAttendanceDailyRule getLastDailyRule(@Param("currentDate") Date currentDate, @Param("userId") String userId);
/**
* 查询ruleId的上一个或下一个(班/加班)出勤
* @param ruleId 出勤id
* @param startTime 开始时间
* @param endTime 结束时间
* @param userId 用户id
* @param queryType 查询类型(previous/next)
* @return jnpf.entity.attendance.FtbAttendanceDailyRule
*/
FtbAttendanceDailyRule getPreviousOrNextRule(@Param("ruleId") String ruleId, @Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("userId") String userId, @Param("queryType") String queryType);
/**
* 获取指定日期的排班
* @param startDay 指定日期 2023-11-11
* @return java.util.List<jnpf.model.attendance.vo.ClassesDetailVo>
* @author hlp
*/
List<ClassesDetailVo> getShiftByDay(@Param("startDay") String startDay, @Param("userId") String userId);
/**
* 查询所有下班缺卡时间是今天的出勤规则(普班)
* @param today 今天
* @param yesterday 昨天
* @param userId 用户id
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getRuleListByDate(@Param("today") Date today, @Param("yesterday") Date yesterday, @Param("userId") String userId);
/**
* 查询指定日期最后一个班次是加班的出勤记录
* @param date 日期
* @param userId 用户id
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getOverTimeRuleListByDate(@Param("date") Date date, @Param("userId") String userId);
/**
* 获取用户时间段内排班信息
* @param userId 用户id
* @param startTime 开始时间
* @param endTime 结束时间
* @return java.lang.Integer
* @author hlp
*/
Integer getUserShift(@Param("userId") String userId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
/**
* 查询所有工作时间是queryDate的出勤规则
* @param queryDate 日期
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getListWorkTimeInToday(@Param("queryDate") Date queryDate);
/**
* 查询上班/下班时间在时间范围内的出勤规则
* @param beginDate 开始时间
* @param endDate 结束时间
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getListByWorkTime(@Param("beginDate") Date beginDate, @Param("endDate") Date endDate);
/**
* 查询一个月的打卡记录(缺勤次数统计)
* @return java.util.List<jnpf.model.attendance.vo.MonthRecordVo>
*/
List<MonthRecordVo> selectMonthRecord();
/**
* 查询存在的出勤规则
* @param ruleIdList 出勤规则id列表
* @return java.util.List<java.lang.String>
*/
List<String> getExistRuleId(@Param("list") List<String> ruleIdList);
/**
* 获取排班用户数
* @param userIds 用户Ids
* @param groupIds 考勤组Ids
* @return int
*/
Integer getGroupUserNum(@Param("userIds") List<String> userIds, @Param("groupIds") List<String> groupIds);
/**
* 查询今日班次
* @param userId 用户Id
* @param startDate 开始时间
* @param endDate 结束时间
* @return java.util.List<YesterdayRuleVo>
*/
List<YesterdayRuleVo> getYesterdayRule(@Param("userId") String userId,@Param("startDate") Date startDate,@Param("endDate") Date endDate);
/**
* 获取排班用户数
* @param groupUserIds 用户Ids
* @param groupIds 考勤组Ids
* @return java.util.List<java.lang.String>
*/
List<String> getDayRule(@Param("groupUserIds") List<String> groupUserIds, @Param("groupIds") List<String> groupIds);
/**
* 批量查询[当日+前一日]出勤规则
* @param userDayList 用户日期列表
* @return java.util.List<jnpf.entity.attendance.FtbAttendanceDailyRule>
*/
List<FtbAttendanceDailyRule> getDailyRuleListBatch(@Param("list") List<UserDayVo> userDayList);
/**
* 获取用户已使用的公休数
* @param yearMonth 年月 格式yyyy-MM
* @param userIds 用户Ids
* @return java.util.Map<java.lang.String, java.math.BigDecimal>
*/
List<KeyValueVo> getUserPublicHoliday(@Param("yearMonth") String yearMonth, @Param("userIds") List<String> userIds);
/**
* 查询每个出勤规则的下一个出勤规则
* @param dayRuleIds 出勤规则ids
* @param dayStr 日期
* @return java.util.List<jnpf.model.attendance.vo.NextRuleVo>
*/
List<NextRuleVo> getNextRulePartInfo(@Param("list") List<String> dayRuleIds, @Param("dayStr") String dayStr);
/**
* 获取指定日期的出勤规则
* @param finalStartDate 开始日期
* @param finalEndDate 结束日期
* @return 排班列表
*/
List<CreateDayStatistics> getDayRuleByMonth(@Param("startDate") LocalDate finalStartDate, @Param("endDate") LocalDate finalEndDate);
}

View File

@@ -0,0 +1,381 @@
package jnpf.attendance.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jnpf.attendance.dto.AttendanceCountAvgHoursVo;
import jnpf.attendance.dto.DateDimensionsRangeDto;
import jnpf.entity.attendance.AttendanceDayStatistics;
import jnpf.model.attendance.dto.*;
import jnpf.model.attendance.vo.attendance.*;
import jnpf.model.common.DateRangeDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
/**
* 考勤日度统计表
*
* @author shitou
* @email shitou@niujiekeji.com
* @date 2024-06-24 09:33:43
*/
@Mapper
@Component
public interface AttendanceDayStatisticsMapper extends BaseMapper<AttendanceDayStatistics> {
/**
* 查询日统计数据
*
* @param queryDto 筛选条件
* @return 日统计数据
*/
StatisticsDataQueryVo getDayStatisticsDataQuery(@Param("req") StatisticsDataQueryDto queryDto);
/**
* 查询日统计分页数据
*
* @param req 筛选条件
* @return 分页数据
*/
List<DayStatisticsQueryVo> getDayPageList(@Param("req") DayStatisticsDataPageListQueryDto req);
/**
* 查询计薪日统计分页数据
*
* @param req 筛选条件
* @return 分页数据
*/
List<DayPayrollStatisticsQueryVo> getDayPayrollPageList(@Param("req") DayStatisticsDataPageListQueryDto req);
/**
* 查询月统计数据
*
* @param queryDto 日欺范围
* @param isGroupBy 是否按照考勤组分组
* @return 月统计数据
*/
StatisticsDataQueryVo getMonthStatisticsDataQuery(@Param("req") StatisticsDataQueryDto queryDto, @Param("isGroupBy") Boolean isGroupBy);
/**
* 查询月统计分页数据
*
* @param req 筛选条件
* @return 分页数据
*/
List<MonthStatisticsQueryVo> getMonthPageList(@Param("req") MonthStatisticsDataQueryDto req);
/**
* 查询计薪月统计分页数据
*
* @param req 筛选条件
* @return 分页数据
*/
List<MonthPayrollStatisticsQueryVo> getMonthPayrollPageList(@Param("req") MonthStatisticsDataQueryDto req);
/**
* 查询需要提醒的日统计数据
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 日统计数据
*/
List<DayStatisticsNoticeQueryVo> getDayStatisticsNotice(@Param("startDate") String startDate, @Param("endDate") String endDate);
/**
* 查询需要提醒的月统计数据
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 月统计数据
*/
List<MonthStatisticsNoticeQueryVo> getMonthStatisticsNotice(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 查询需要提醒的团队统计数据
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 团队统计数据
*/
List<TeamMonthStatisticsNoticeQueryVo> getStemMonthStatisticsNotice(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取考勤组每月的平均工时
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 每月的平均工时
*/
List<AttendanceCountAvgHoursVo> getAvgHours(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度统计数据
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 月度统计数据
*/
MonthStatsDetailsQueryVo getAttendanceAvgHoursDetails(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度人均工时折线图
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 人均工时折线图
*/
List<MonthStatsPerCapitaQueryVo> getAttendanceMonthPerCapita(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度日常情况
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 月度日常情况
*/
MonthStatsDailySituationQueryVo getAttendanceDailySituation(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度考勤工时排行
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤工时排行
*/
List<MonthStatsHoursRankingQueryVo> getAttendanceHoursRanking(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度全勤情况
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 全勤情况
*/
List<MonthStatsFullSituationQueryVo> getAttendanceFullSituation(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度异常情况
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 异常情况
*/
MonthStatsAbnormalConditionQueryVo getAttendanceAbnormalCondition(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取多考勤组月度加班情况
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 加班情况
*/
List<MonthStatsOvertimeSituationQueryVo> getAttendanceOvertimeSituation(@Param("groupIds") List<String> groupIds, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);
/**
* 获取当月考勤确认数据
*
* @param userIds 用户ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤组月度加班情况
*/
List<MonthStatisticsQueryVo> getConfirmDetailsInfoByMonth(@Param("userIds") List<String> userIds, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
/**
* 批量查询用户在指定月份是否封账
*
* @param userIdList 用户ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 是否封账
*/
List<SealUserInfoDataVo> selectUserIsSeal(@Param("userIdList") List<String> userIdList,
@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 按天查询用户在日期范围内的封存日期列表(仅返回已封存的日期)
* 用于跨月查询场景下按天判断每个用户每一天是否被封存
*
* @param userIdList 用户ID集合
* @param startDate 开始时间(含)
* @param endDate 结束时间(含)
* @return 用户按天封存记录
*/
List<UserDailySealVo> selectUserDailySeal(@Param("userIdList") List<String> userIdList,
@Param("startDate") String startDate,
@Param("endDate") String endDate);
/**
* 考勤封账-分页列表
*
* @param queryDto 考勤封账分页列表参数
* @param seal 封账状态
* @return 考勤封账分页列表结果
*/
List<MonthSealPageListVo> sealPageList(@Param("req") StatisticsDataQueryDto queryDto, @Param("seal") Integer seal);
/**
* 薪酬考勤数据支持
*
* @param dto 薪酬考勤数据支持参数
* @return 薪酬考勤数据支持结果
*/
List<SalaryAttendanceSupportQuery> salaryAttendanceSupport(@Param("req") SalaryAttendanceSupportDto dto);
/**
* 获取考勤组维度考勤数据
*
* @param groupIds 考勤组ID集合
* @param dimensions 维度范围
* @return 考勤组维度考勤数据
*/
List<AttendanceDimensionVo> getDimensionsAttendanceCountList(@Param("groupIds") List<String> groupIds,
@Param("dimensions") List<DateDimensionsRangeDto> dimensions);
/**
* 获取考勤组维度考勤数据-日维度
*
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤组维度考勤数据
*/
List<AttendanceDimensionDayVo> getDimensionsAttendanceDayCountList(@Param("groupIds") List<String> groupIds,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
/**
* 考勤平均工时趋势-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤平均工时趋势-分页列表结果
*/
Page<AverageTrendPageListVo> averageWorkHoursTrendPageList(@Param("page") Page<AverageTrendPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤人均工时趋势-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤人均工时趋势-分页列表结果
*/
Page<PersonnelTrendPageListVo> personWorkHoursTrendPageList(@Param("page") Page<PersonnelTrendPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤日常情况-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 日常情况-分页列表结果
*/
Page<DailySituationPageListVo> dailySituationPageList(@Param("page") Page<DailySituationPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤工时排名-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 考勤组工时排名-分页列表结果
*/
Page<WorkHoursRankingPageListVo> workHoursRankingPageList(@Param("page") Page<WorkHoursRankingPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤全勤情况-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 全勤情况-分页列表结果
*/
Page<FullAttendanceStatusPageListVo> fullAttendanceStatusPageList(@Param("page") Page<FullAttendanceStatusPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤异常情况-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 异常情况-分页列表结果
*/
Page<ExceptionSituationPageListVo> exceptionSituationPageList(@Param("page") Page<ExceptionSituationPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 考勤加班情况-分页列表
* @param page 分页参数
* @param groupIds 考勤组ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 加班情况-分页列表结果
*/
Page<OvertimeSituationPageListVo> overtimeSituationPageList(@Param("page") Page<OvertimeSituationPageListVo> page,
@Param("groupIds") List<String> groupIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 查询用户考勤统计信息
*
* @param userId 用户ID
* @param dateRangeDto 时间范围
* @return 用户考勤统计信息
*/
DayStatisticsQueryDbVo queryUserStatisticsInfo(@Param("userId") String userId, @Param("dateRangeDto") DateRangeDto dateRangeDto);
/**
* 查询用户加班详情
*
* @param userId 用户ID
* @param dateRangeDto 时间范围
* @return 用户加班详情
*/
List<QueryUserOvertimeDetailsDbVo> queryUserOvertime(@Param("userId") String userId, @Param("dateRangeDto") DateRangeDto dateRangeDto);
/**
* 查询用户工作状况统计信息
*
* @param userIds 用户ID集合
* @param startDate 开始时间
* @param endDate 结束时间
* @return 用户工作状况统计信息
*/
List<UserWorkSituationDbVo> queryUserWorkSituation(@Param("userIds") List<String> userIds,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
}

View File

@@ -0,0 +1,30 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceFestivalRules;
import jnpf.model.attendance.dto.FestivalRulesQueryDto;
import jnpf.model.attendance.vo.attendance.AttendanceFestivalRulesVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper
@Component
public interface AttendanceFestivalRulesMapper extends SuperMapper<AttendanceFestivalRules> {
/**
* 列表
* @param queryDto 查询参数
* @return 列表
*/
List<AttendanceFestivalRulesVo> getPageList(@Param("queryDto") FestivalRulesQueryDto queryDto);
/**
* 添加
* @param id 主键
* @param state 状态 1是 0否
* @param userId 用户ID
*/
void updateState(@Param("id") String id, @Param("state") Integer state, @Param("userId") String userId);
}

View File

@@ -0,0 +1,16 @@
package jnpf.attendance.mapper;
import jnpf.entity.attendance.AttendanceFestivalSettingEntity;
import jnpf.base.mapper.SuperMapper;
/**
* <p>
* 考勤配置-节日配置 Mapper 接口
* </p>
*
* @author ahua
* @since 2023-11-29
*/
public interface AttendanceFestivalSettingMapper extends SuperMapper<AttendanceFestivalSettingEntity> {
}

View File

@@ -0,0 +1,26 @@
package jnpf.attendance.mapper;
import jnpf.entity.attendance.AttendanceFieldPersonnel;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author huanglinpan
* @Date 2025/2/17 16:03
* @Version 1.0 (版本号)
*/
public interface AttendanceFieldPersonnelMapper {
/** 根据考勤组Id删除绑定的外勤人员 */
void deleteByGroupId(@Param("groupId") String groupId);
/** 批量新增考勤组可外勤人员 */
void insertList(@Param("fieldPersonnelList") List<AttendanceFieldPersonnel> fieldPersonnelList);
/**
* 根据考勤组Id获取外勤人员Id列表
* @param groupId 考勤组Id
*/
List<String> getUserIdsByGroupId(@Param("groupId") String groupId);
}

View File

@@ -0,0 +1,6 @@
package jnpf.attendance.mapper;
public interface AttendanceFieldpersonnelApproveMapper {
}

View File

@@ -0,0 +1,30 @@
package jnpf.attendance.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.entity.attendance.AttendanceFixedClassEntity;
import org.apache.ibatis.annotations.Param;
/**
* @Author huanglinpan
* @Date 2024/5/9 15:14
* @Version 1.0 (版本号)
*/
public interface AttendanceFixedClassMapper extends SuperMapper<AttendanceFixedClassEntity>{
/**
* 校验该班次有无在固定排班使用
* @param shiftNameId 班次Id
*/
Integer getUseBYShiftNameId(@Param("shiftNameId") String shiftNameId);
/**
* 校验该班次有无在排班使用
* @param shiftNameId 班次Id
*/
Integer getRuleUseBYShiftNameId(@Param("shiftNameId") String shiftNameId);
/**
* 校验该班次有无在快速排班使用
* @param shiftNameId 班次Id
*/
Integer getQuickUseBYShiftNameId(@Param("shiftNameId") String shiftNameId);
}

Some files were not shown because too many files have changed in this diff Show More