浏览代码

【feat】【v3】
1.完善打卡结算逻辑

ChenYL 11 月之前
父节点
当前提交
b52cbbd966

+ 0 - 3
doc/sql/update-v3.sql

@@ -236,9 +236,7 @@ CREATE TABLE `punch_in_multi_task` (
   `multi_task_status` varchar(10) NOT NULL COMMENT '任务状态(活跃-ACTIVE,归档-ARCHIVE)',
   `multi_task_version` int NOT NULL COMMENT '任务修改版本',
   `task_points_status` varchar(10) NOT NULL COMMENT '是否启用多任务积分计算(ENABLED-启用,DISABLED-关闭)',
-  `punch_in_method` varchar(10) NOT NULL COMMENT '打卡方式(次数-COUNT,比率-RATE)',
   `punch_in_done_count` int DEFAULT NULL COMMENT '打卡完成次数',
-  `punch_in_done_rate` decimal(5,2) DEFAULT NULL COMMENT '打卡完成率',
   `points` int NOT NULL COMMENT '奖励的积分(基本)',
   `grace_status` varchar(10) NOT NULL COMMENT '是否启用宽限期(ENABLED-启用,DISABLED-关闭)',
   `grace_day` int DEFAULT NULL COMMENT '宽限期(单位:天)',
@@ -327,7 +325,6 @@ CREATE TABLE `punch_in_multi_task_history` (
   `punch_in_status` varchar(10) NOT NULL COMMENT '打卡状态(DONE-完成,UNDONE-未完成)',
   `total_task_count` int NOT NULL COMMENT '总任务数',
   `punch_in_done_count` int NOT NULL COMMENT '打卡完成数',
-  `punch_in_done_rate` decimal(5,2) NOT NULL COMMENT '打卡完成率',
   `consecutive_status` varchar(20) NOT NULL COMMENT '连续打卡状态(正常打卡-NORMAL、中断--INTERRUPTED)',
   `consecutive_day` int NOT NULL COMMENT '连续天数,第一天开始就等于1',
   `settle_task_history_id` bigint DEFAULT NULL COMMENT '结算任务执行ID',

+ 45 - 46
doc/技术文档.md

@@ -70,6 +70,8 @@ ui设计工具:即时设计
 3. 单任务(当天、连续)的**前置判断是共用**
 4. 多任务(当天、连续)的**前置判断是共用**
 5. 法定节假日包含周末
+6. 单任务中-单次打卡类型 是没有额外奖励的,因为没有超出的数量进行计算
+7. 单任务-连续、多任务(当天、连续)额外奖励的计算方式目前只有区间计算
 
 
 
@@ -589,30 +591,28 @@ ui设计工具:即时设计
 
 表名:punch_in_multi_task
 
-| 字段                 | 类型         | 描述                                                | 索引     |
-| -------------------- | ------------ | --------------------------------------------------- | -------- |
-| id                   | bigint       | 主键                                                | 主键索引 |
-| user_id              | bigint       | 用户ID                                              | 普通索引 |
-| multi_task_unique_id | bigint       | 打卡多任务唯一ID                                    | 普通索引 |
-| multi_task_status    | varchar(10)  | 任务状态(活跃-ACTIVE,归档-ARCHIVE)                 |          |
-| multi_task_version   | int          | 任务修改版本                                        |          |
-| task_points_status   | varchar(10)  | 是否启用多任务积分计算(ENABLED-启用,DISABLED-关闭) |          |
-| punch_in_method      | varchar(10)  | 打卡方式(次数-COUNT,比率-RATE)                   |          |
-| punch_in_done_count  | int          | 打卡完成次数                                        |          |
-| punch_in_done_rate   | decimal(5,2) | 打卡完成率                                          |          |
-| points               | int          | 奖励的积分(基本)                                    |          |
-| grace_status         | varchar(10)  | 是否启用宽限期(ENABLED-启用,DISABLED-关闭)         |          |
-| grace_day            | int          | 宽限期(单位:天)                                  |          |
-| interrupted_day      | int          | 打卡中断天数(单位:天)                            |          |
-| penalty_day          | int          | 惩罚天数(单位:天)                                |          |
-| extra_method         | varchar(10)  | 额外奖励方式(无-NONE,固定-FIXED,区间-INTERVAL)  |          |
-| extra_points         | int          | 奖励的积分(额外)                                  |          |
-| created_by           | bigint       | 创建人                                              |          |
-| creation_time        | timestamp    | 创建时间                                            |          |
-| last_updated_by      | bigint       | 最后更新人                                          |          |
-| last_update_time     | timestamp    | 最后更新时间                                        |          |
-| version              | bigint       | 版本号                                              |          |
-| delete_flag          | tinyint      | 逻辑删除标志(0-未删除,1-已删除)                  |          |
+| 字段                 | 类型        | 描述                                                | 索引     |
+| -------------------- | ----------- | --------------------------------------------------- | -------- |
+| id                   | bigint      | 主键                                                | 主键索引 |
+| user_id              | bigint      | 用户ID                                              | 普通索引 |
+| multi_task_unique_id | bigint      | 打卡多任务唯一ID                                    | 普通索引 |
+| multi_task_status    | varchar(10) | 任务状态(活跃-ACTIVE,归档-ARCHIVE)                 |          |
+| multi_task_version   | int         | 任务修改版本                                        |          |
+| task_points_status   | varchar(10) | 是否启用多任务积分计算(ENABLED-启用,DISABLED-关闭) |          |
+| punch_in_done_count  | int         | 打卡完成次数                                        |          |
+| points               | int         | 奖励的积分(基本)                                    |          |
+| grace_status         | varchar(10) | 是否启用宽限期(ENABLED-启用,DISABLED-关闭)         |          |
+| grace_day            | int         | 宽限期(单位:天)                                  |          |
+| interrupted_day      | int         | 打卡中断天数(单位:天)                            |          |
+| penalty_day          | int         | 惩罚天数(单位:天)                                |          |
+| extra_method         | varchar(10) | 额外奖励方式(无-NONE,固定-FIXED,区间-INTERVAL)  |          |
+| extra_points         | int         | 奖励的积分(额外)                                  |          |
+| created_by           | bigint      | 创建人                                              |          |
+| creation_time        | timestamp   | 创建时间                                            |          |
+| last_updated_by      | bigint      | 最后更新人                                          |          |
+| last_update_time     | timestamp   | 最后更新时间                                        |          |
+| version              | bigint      | 版本号                                              |          |
+| delete_flag          | tinyint     | 逻辑删除标志(0-未删除,1-已删除)                  |          |
 
 
 
@@ -695,28 +695,27 @@ ui设计工具:即时设计
 
 表名:punch_in_multi_task_history
 
-| 字段                          | 类型         | 描述                                                         | 索引     |
-| ----------------------------- | ------------ | ------------------------------------------------------------ | -------- |
-| id                            | bigint       | 主键                                                         | 主键索引 |
-| user_id                       | bigint       | 用户ID                                                       | 普通索引 |
-| punch_in_multi_task_unique_id | bigint       | 打卡多任务唯一ID                                             | 普通索引 |
-| punch_in_date                 | varchar(10)  | 打卡日期                                                     | 普通索引 |
-| punch_in_status               | varchar(10)  | 打卡状态(DONE-完成,UNDONE-未完成)                           |          |
-| total_task_count              | int          | 总任务数                                                     |          |
-| punch_in_done_count           | int          | 打卡完成数                                                   |          |
-| punch_in_done_rate            | decimal(5,2) | 打卡完成率                                                   |          |
-| consecutive_status            | varchar(20)  | 连续打卡状态(正常打卡-NORMAL、中断--INTERRUPTED)             |          |
-| consecutive_day               | int          | 连续天数,第一天开始就等于1                                  |          |
-| settle_task_history_id        | bigint       | 结算任务执行记录ID                                           | 普通索引 |
-| settle_status                 | varchar(20)  | 结算状态(未结算-UNSETTLED,宽限期跳过J-GRACE_SKIP,惩罚跳过-PENALTY_SKIP,已结算-SETTLED) |          |
-| settle_punch_in_multi_task_id | bigint       | 结算时的打卡多任务ID                                         |          |
-| settle_points                 | int          | 结算奖励积分                                                 |          |
-| created_by                    | bigint       | 创建人                                                       |          |
-| creation_time                 | timestamp    | 创建时间                                                     |          |
-| last_updated_by               | bigint       | 最后更新人                                                   |          |
-| last_update_time              | timestamp    | 最后更新时间                                                 |          |
-| version                       | bigint       | 版本号                                                       |          |
-| delete_flag                   | tinyint      | 逻辑删除标志(0-未删除,1-已删除)                           |          |
+| 字段                          | 类型        | 描述                                                         | 索引     |
+| ----------------------------- | ----------- | ------------------------------------------------------------ | -------- |
+| id                            | bigint      | 主键                                                         | 主键索引 |
+| user_id                       | bigint      | 用户ID                                                       | 普通索引 |
+| punch_in_multi_task_unique_id | bigint      | 打卡多任务唯一ID                                             | 普通索引 |
+| punch_in_date                 | varchar(10) | 打卡日期                                                     | 普通索引 |
+| punch_in_status               | varchar(10) | 打卡状态(DONE-完成,UNDONE-未完成)                           |          |
+| total_task_count              | int         | 总任务数                                                     |          |
+| punch_in_done_count           | int         | 打卡完成数                                                   |          |
+| consecutive_status            | varchar(20) | 连续打卡状态(正常打卡-NORMAL、中断--INTERRUPTED)             |          |
+| consecutive_day               | int         | 连续天数,第一天开始就等于1                                  |          |
+| settle_task_history_id        | bigint      | 结算任务执行记录ID                                           | 普通索引 |
+| settle_status                 | varchar(20) | 结算状态(未结算-UNSETTLED,宽限期跳过J-GRACE_SKIP,惩罚跳过-PENALTY_SKIP,已结算-SETTLED) |          |
+| settle_punch_in_multi_task_id | bigint      | 结算时的打卡多任务ID                                         |          |
+| settle_points                 | int         | 结算奖励积分                                                 |          |
+| created_by                    | bigint      | 创建人                                                       |          |
+| creation_time                 | timestamp   | 创建时间                                                     |          |
+| last_updated_by               | bigint      | 最后更新人                                                   |          |
+| last_update_time              | timestamp   | 最后更新时间                                                 |          |
+| version                       | bigint      | 版本号                                                       |          |
+| delete_flag                   | tinyint     | 逻辑删除标志(0-未删除,1-已删除)                           |          |
 
 
 

+ 0 - 13
src/main/java/com/punchsettle/server/atomic/entity/PunchInMultiTask.java

@@ -62,25 +62,12 @@ public class PunchInMultiTask extends BaseEntity implements Serializable {
     @Column(name = "task_points_status")
     private CommonEnableStatusEnum taskPointsStatus;
 
-    /**
-     * 打卡方式(次数-COUNT,比率-RATE)
-     * @see PunchInMethodMultiEnum
-     */
-    @Column(name = "punch_in_method")
-    private PunchInMethodMultiEnum punchInMethod;
-
     /**
      * 打卡完成次数
      */
     @Column(name = "punch_in_done_count")
     private Integer punchInDoneCount;
 
-    /**
-     * 打卡完成率
-     */
-    @Column(name = "punch_in_done_rate")
-    private BigDecimal punchInDoneRate;
-
     /**
      * 奖励的积分(基本)
      */

+ 0 - 6
src/main/java/com/punchsettle/server/atomic/entity/PunchInMultiTaskHistory.java

@@ -55,12 +55,6 @@ public class PunchInMultiTaskHistory extends BaseEntity implements Serializable
     @Column(name = "punch_in_done_count")
     private Integer punchInDoneCount;
 
-    /**
-     * 打卡完成率
-     */
-    @Column(name = "punch_in_done_rate")
-    private BigDecimal punchInDoneRate;
-
     /**
      * 连续打卡状态(正常打卡-NORMAL、中断--INTERRUPTED)
      */

+ 2 - 2
src/main/java/com/punchsettle/server/service/controller/PunchInController.java

@@ -20,7 +20,7 @@ import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
 import com.punchsettle.server.pojo.punchin.PunchInRequest;
 import com.punchsettle.server.pojo.punchin.PunchInVO;
 import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
-import com.punchsettle.server.service.manager.IPunchInManager;
+import com.punchsettle.server.service.manager.IPunchInManagerV1;
 
 /**
  * @author tyuio
@@ -33,7 +33,7 @@ import com.punchsettle.server.service.manager.IPunchInManager;
 public class PunchInController {
 
     @Autowired
-    private IPunchInManager punchInManager;
+    private IPunchInManagerV1 punchInManager;
 
     /**
      * 查询打卡任务

+ 2 - 2
src/main/java/com/punchsettle/server/service/controller/SettleController.java

@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
 import com.punchsettle.server.pojo.settle.SettleQuery;
 import com.punchsettle.server.pojo.settle.SettleRequest;
 import com.punchsettle.server.pojo.settle.SettleVO;
-import com.punchsettle.server.service.manager.ISettleManager;
+import com.punchsettle.server.service.manager.ISettleManagerV1;
 
 /**
  * @author tyuio
@@ -25,7 +25,7 @@ import com.punchsettle.server.service.manager.ISettleManager;
 public class SettleController {
 
     @Autowired
-    private ISettleManager settleManager;
+    private ISettleManagerV1 settleManager;
 
     /**
      * 手动调起结算定时任务

+ 4 - 89
src/main/java/com/punchsettle/server/service/manager/IPunchInManager.java

@@ -1,86 +1,20 @@
 package com.punchsettle.server.service.manager;
 
-import java.util.List;
-
 import com.punchsettle.server.atomic.entity.PunchInMultiTask;
-import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
-import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
-import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
-import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
-import com.punchsettle.server.atomic.entity.PunchInStatus;
 import com.punchsettle.server.atomic.entity.PunchInTask;
-import com.punchsettle.server.atomic.entity.PunchInTaskExt;
 import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
 import com.punchsettle.server.constant.PunchInStatusEnum;
-import com.punchsettle.server.pojo.punchin.PunchInDataQuery;
-import com.punchsettle.server.pojo.punchin.PunchInDataVO;
-import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
-import com.punchsettle.server.pojo.punchin.PunchInRequest;
-import com.punchsettle.server.pojo.punchin.PunchInVO;
-import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
+
+import java.util.List;
 
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 打卡结算任务 服务类
- * @date 2024/11/25 15:00
+ * @date 2025/4/15 14:04
+ * @description 打卡服务类
  */
 public interface IPunchInManager {
 
-    /**
-     * 查询打卡任务及其打卡记录
-     * @return
-     */
-    List<PunchInWithRecordVO> queryPunchInAndRecord();
-
-    /**
-     * 根据ID获取打卡任务
-     */
-    PunchInVO queryPunchInById(Long punchInId);
-
-    /**
-     * 新增或更新打卡结算任务
-     * @param request 打卡结算
-     */
-    void saveOrUpdatePunchIn(PunchInRequest request);
-
-    /**
-     * 根据主键删除打卡结算任务
-     * @param punchInId 主键
-     */
-    void deletePunchIn(Long punchInId);
-
-    /**
-     * 完成打卡结算任务插入打卡记录
-     * @param request
-     */
-    void doPunchIn(PunchInRequest request);
-
-    /**
-     * 归档打卡任务
-     * @param punchInId
-     */
-    void archivePunchIn(Long punchInId);
-
-    /**
-     * 补打卡
-     * @param request
-     */
-    void remakePunchIn(PunchInRecordRequest request);
-
-    /**
-     * 撤销误打卡
-     * @param request
-     */
-    void revokePunchIn(PunchInRecordRequest request);
-
-    /**
-     * 查询打卡数据
-     * @param query
-     * @return
-     */
-    PunchInDataVO queryPunchInData(PunchInDataQuery query);
-
     /**
      * 判断指定日期是否在打卡任务的重复周期内
      * @param punchInTask 打卡任务
@@ -106,23 +40,4 @@ public interface IPunchInManager {
      * @return
      */
     PunchInStatusEnum judgePunchInStatusInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInTaskHistory> punchInTaskHistoryList, int multiTaskNum);
-
-    /**
-     * 计算任务积分
-     * @param punchInTask 打卡任务
-     * @param punchInTaskExtList 打卡任务拓展信息列表
-     * @param punchInTaskHistory 打卡记录
-     * @return
-     */
-    int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus);
-
-
-    /**
-     * 计算多任务积分
-     * @param punchInMultiTask
-     * @param punchInMultiTaskExts
-     * @param punchInMultiTaskHistory
-     * @return
-     */
-    int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus);
 }

+ 128 - 0
src/main/java/com/punchsettle/server/service/manager/IPunchInManagerV1.java

@@ -0,0 +1,128 @@
+package com.punchsettle.server.service.manager;
+
+import java.util.List;
+
+import com.punchsettle.server.atomic.entity.PunchInMultiTask;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
+import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
+import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
+import com.punchsettle.server.atomic.entity.PunchInStatus;
+import com.punchsettle.server.atomic.entity.PunchInTask;
+import com.punchsettle.server.atomic.entity.PunchInTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
+import com.punchsettle.server.constant.PunchInStatusEnum;
+import com.punchsettle.server.pojo.punchin.PunchInDataQuery;
+import com.punchsettle.server.pojo.punchin.PunchInDataVO;
+import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
+import com.punchsettle.server.pojo.punchin.PunchInRequest;
+import com.punchsettle.server.pojo.punchin.PunchInVO;
+import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @description 打卡结算任务 服务类
+ * @date 2024/11/25 15:00
+ */
+public interface IPunchInManagerV1 {
+
+    /**
+     * 查询打卡任务及其打卡记录
+     * @return
+     */
+    List<PunchInWithRecordVO> queryPunchInAndRecord();
+
+    /**
+     * 根据ID获取打卡任务
+     */
+    PunchInVO queryPunchInById(Long punchInId);
+
+    /**
+     * 新增或更新打卡结算任务
+     * @param request 打卡结算
+     */
+    void saveOrUpdatePunchIn(PunchInRequest request);
+
+    /**
+     * 根据主键删除打卡结算任务
+     * @param punchInId 主键
+     */
+    void deletePunchIn(Long punchInId);
+
+    /**
+     * 完成打卡结算任务插入打卡记录
+     * @param request
+     */
+    void doPunchIn(PunchInRequest request);
+
+    /**
+     * 归档打卡任务
+     * @param punchInId
+     */
+    void archivePunchIn(Long punchInId);
+
+    /**
+     * 补打卡
+     * @param request
+     */
+    void remakePunchIn(PunchInRecordRequest request);
+
+    /**
+     * 撤销误打卡
+     * @param request
+     */
+    void revokePunchIn(PunchInRecordRequest request);
+
+    /**
+     * 查询打卡数据
+     * @param query
+     * @return
+     */
+    PunchInDataVO queryPunchInData(PunchInDataQuery query);
+
+    /**
+     * 判断指定日期是否在打卡任务的重复周期内
+     * @param punchInTask 打卡任务
+     * @param repeatDateStr 指定日期
+     * @return true-在周期内,false-不在周期内
+     */
+    boolean judgeRepeat(PunchInTask punchInTask, String repeatDateStr);
+
+    /**
+     * 判断打卡状态
+     * @param punchInTask 当前打卡任务
+     * @param punchInTaskHistory 打卡记录
+     * @param holidayFlag 是否是节假日(是-true,否-false)
+     * @return
+     */
+    PunchInStatusEnum judgePunchInStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag);
+
+    /**
+     * 判断打卡状态(多任务)
+     * @param punchInMultiTask 打卡多任务任务
+     * @param punchInTaskHistoryList 打卡记录
+     * @param multiTaskNum 参与多任务计算数量
+     * @return
+     */
+    PunchInStatusEnum judgePunchInStatusInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInTaskHistory> punchInTaskHistoryList, int multiTaskNum);
+
+    /**
+     * 计算任务积分
+     * @param punchInTask 打卡任务
+     * @param punchInTaskExtList 打卡任务拓展信息列表
+     * @param punchInTaskHistory 打卡记录
+     * @return
+     */
+    int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus);
+
+
+    /**
+     * 计算多任务积分
+     * @param punchInMultiTask
+     * @param punchInMultiTaskExts
+     * @param punchInMultiTaskHistory
+     * @return
+     */
+    int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus);
+}

+ 22 - 32
src/main/java/com/punchsettle/server/service/manager/ISettleManager.java

@@ -1,51 +1,41 @@
 package com.punchsettle.server.service.manager;
 
-import java.time.LocalDate;
-import java.util.List;
+import com.punchsettle.server.atomic.entity.PunchInMultiTask;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
+import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
+import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
+import com.punchsettle.server.atomic.entity.PunchInStatus;
+import com.punchsettle.server.atomic.entity.PunchInTask;
+import com.punchsettle.server.atomic.entity.PunchInTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
 
-import com.punchsettle.server.atomic.entity.PunchIn;
-import com.punchsettle.server.atomic.entity.PunchInRecord;
-import com.punchsettle.server.constant.PunchInSettleTypeEnum;
-import com.punchsettle.server.constant.PunchInStatusV1Enum;
-import com.punchsettle.server.pojo.settle.SettleQuery;
-import com.punchsettle.server.pojo.settle.SettleRequest;
-import com.punchsettle.server.pojo.settle.SettleVO;
+import java.util.List;
 
 /**
  * @author tyuio
  * @version 1.0.0
+ * @date 2025/4/15 14:08
  * @description 结算服务类
- * @date 2024/12/12 22:23
  */
 public interface ISettleManager {
 
     /**
-     * 打卡结算
-     * @param settleType 结算类型
-     * @param settleDate 结算日期
-     * @param userIds 待结算的用户
-     * @param punchInIds 待结算用户的打卡任务ID
-     */
-    void settleHandler(PunchInSettleTypeEnum settleType, LocalDate settleDate, List<Long> userIds, List<Long> punchInIds);
-
-    /**
-     * 判断是否满足打卡规则完成打卡
-     * @param punchIn
-     * @param punchInRecord
-     * @return PunchInStatusEnum
+     * 计算任务积分
+     * @param punchInTask 打卡任务
+     * @param punchInTaskExtList 打卡任务拓展信息列表
+     * @param punchInTaskHistory 打卡记录
+     * @return
      */
-    PunchInStatusV1Enum judgePunchInStatus(PunchIn punchIn, PunchInRecord punchInRecord);
+    int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus);
 
-    /**
-     * 手动结算
-     * @param settleRequest
-     */
-    void manualSettle(SettleRequest settleRequest);
 
     /**
-     * 查询结算
-     * @param query
+     * 计算多任务积分
+     * @param punchInMultiTask 打卡多任务
+     * @param punchInMultiTaskExts 打卡多任务拓展信息列表
+     * @param punchInMultiTaskHistory 打卡记录
      * @return
      */
-    List<SettleVO> querySettle(SettleQuery query);
+    int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus);
 }

+ 51 - 0
src/main/java/com/punchsettle/server/service/manager/ISettleManagerV1.java

@@ -0,0 +1,51 @@
+package com.punchsettle.server.service.manager;
+
+import java.time.LocalDate;
+import java.util.List;
+
+import com.punchsettle.server.atomic.entity.PunchIn;
+import com.punchsettle.server.atomic.entity.PunchInRecord;
+import com.punchsettle.server.constant.PunchInSettleTypeEnum;
+import com.punchsettle.server.constant.PunchInStatusV1Enum;
+import com.punchsettle.server.pojo.settle.SettleQuery;
+import com.punchsettle.server.pojo.settle.SettleRequest;
+import com.punchsettle.server.pojo.settle.SettleVO;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @description 结算服务类
+ * @date 2024/12/12 22:23
+ */
+public interface ISettleManagerV1 {
+
+    /**
+     * 打卡结算
+     * @param settleType 结算类型
+     * @param settleDate 结算日期
+     * @param userIds 待结算的用户
+     * @param punchInIds 待结算用户的打卡任务ID
+     */
+    void settleHandler(PunchInSettleTypeEnum settleType, LocalDate settleDate, List<Long> userIds, List<Long> punchInIds);
+
+    /**
+     * 判断是否满足打卡规则完成打卡
+     * @param punchIn
+     * @param punchInRecord
+     * @return PunchInStatusEnum
+     */
+    PunchInStatusV1Enum judgePunchInStatus(PunchIn punchIn, PunchInRecord punchInRecord);
+
+    /**
+     * 手动结算
+     * @param settleRequest
+     */
+    void manualSettle(SettleRequest settleRequest);
+
+    /**
+     * 查询结算
+     * @param query
+     * @return
+     */
+    List<SettleVO> querySettle(SettleQuery query);
+}

+ 15 - 676
src/main/java/com/punchsettle/server/service/manager/impl/PunchInManagerImpl.java

@@ -1,461 +1,41 @@
 package com.punchsettle.server.service.manager.impl;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalTime;
-import java.time.YearMonth;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
-import com.punchsettle.server.atomic.entity.PunchInMultiTask;
-import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
-import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
-import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
-import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
-import com.punchsettle.server.atomic.entity.PunchInStatus;
-import com.punchsettle.server.atomic.entity.PunchInTaskExt;
-import com.punchsettle.server.constant.ConsecutiveStatusEnum;
-import com.punchsettle.server.constant.FullAttendancePeriodEnum;
-import com.punchsettle.server.constant.PunchInDimensionEnum;
-import com.punchsettle.server.constant.PunchInExtraMethodEnum;
-import com.punchsettle.server.constant.PunchInMethodMultiEnum;
-import com.punchsettle.server.constant.RepeatCategoryEnum;
-import com.punchsettle.server.service.manager.ICalendarManager;
-import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
+import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
-import com.punchsettle.server.atomic.entity.PunchIn;
-import com.punchsettle.server.atomic.entity.PunchInRecord;
+import com.punchsettle.server.atomic.entity.PunchInMultiTask;
 import com.punchsettle.server.atomic.entity.PunchInTask;
 import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
-import com.punchsettle.server.atomic.service.IPunchInRecordService;
-import com.punchsettle.server.atomic.service.IPunchInService;
+import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
 import com.punchsettle.server.common.exception.BusinessException;
 import com.punchsettle.server.common.utils.Assert;
-import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
 import com.punchsettle.server.constant.CompareRuleEnum;
-import com.punchsettle.server.constant.PunchInCategoryEnum;
 import com.punchsettle.server.constant.PunchInMethodEnum;
-import com.punchsettle.server.constant.PunchInSettleTypeEnum;
 import com.punchsettle.server.constant.PunchInStatusEnum;
-import com.punchsettle.server.constant.PunchInStatusV1Enum;
-import com.punchsettle.server.constant.PunchInStatusViewEnum;
-import com.punchsettle.server.pojo.punchin.PunchInCalendarDataVO;
-import com.punchsettle.server.pojo.punchin.PunchInDataQuery;
-import com.punchsettle.server.pojo.punchin.PunchInDataVO;
-import com.punchsettle.server.pojo.punchin.PunchInQuery;
-import com.punchsettle.server.pojo.punchin.PunchInRecordDataVO;
-import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
-import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
-import com.punchsettle.server.pojo.punchin.PunchInRecordVO;
-import com.punchsettle.server.pojo.punchin.PunchInRequest;
-import com.punchsettle.server.pojo.punchin.PunchInVO;
-import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
+import com.punchsettle.server.constant.RepeatCategoryEnum;
+import com.punchsettle.server.service.manager.ICalendarManager;
 import com.punchsettle.server.service.manager.IPunchInManager;
-import com.punchsettle.server.service.manager.ISettleManager;
-import com.punchsettle.server.utiis.DateUtils;
-import com.punchsettle.server.utiis.UserUtils;
 
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.StringUtils;
 
 /**
  * @author tyuio
  * @version 1.0.0
- * @description 打卡结算任务 服务类
- * @date 2024/11/25 15:12
+ * @date 2025/4/15 14:10
+ * @description 打卡服务类
  */
 @Slf4j
-@Service
+@Component
 public class PunchInManagerImpl implements IPunchInManager {
 
-    @Autowired
-    private IPunchInService punchInService;
-
-    @Autowired
-    private IPunchInRecordService punchInRecordService;
-
-    @Autowired
-    private ISettleManager settleManager;
-
-    /**
-     * 数值100
-     */
-    private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
-
-    @Override
-    public List<PunchInWithRecordVO> queryPunchInAndRecord() {
-        // 获取当前用户ID
-        Long currentUserId = Optional.ofNullable(UserUtils.getCurrentUserId())
-            .orElseThrow(() -> BusinessException.fail("无法获取当前用户信息,无法查询打卡任务"));
-
-        // 查询打卡任务
-        PunchInQuery punchInQuery = new PunchInQuery();
-        punchInQuery.setArchiveFlag(false);
-        punchInQuery.setUserIds(Arrays.asList(currentUserId));
-        List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
-        if (CollectionUtils.isEmpty(punchIns)) {
-            log.info("用户:{} 没有查询到生效的打卡任务");
-            return List.of();
-        }
-
-        // 获取一周的起始日期范围
-        List<LocalDate> weeklyDateRange = DateUtils.getWeeklyDateRange();
-        // 获取打卡任务ID
-        List<Long> punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
-        // 找出范围内的打卡记录
-        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
-        punchInRecordQuery.setPunchInIds(punchInIds);
-        punchInRecordQuery.setStartDate(weeklyDateRange.getFirst().toString());
-        punchInRecordQuery.setEndDate(weeklyDateRange.getLast().toString());
-        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
-
-        // 打卡任务-打卡记录 分组
-        Map<Long, List<PunchInRecord>> recordMap =
-            punchInRecords.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
-
-        // 日期格式化
-        SimpleDateFormat sdf = DateUtils.buildDateFormat();
-
-        // 当前日期
-        LocalDate today = LocalDate.now();
-
-        // 构建打卡记录
-        List<PunchInWithRecordVO> punchInWithRecordVOS = new ArrayList<>();
-        // 初始化的时间记录
-        LocalTime initRecordTimeTrack = LocalTime.parse("00:00:00.000");
-        for (PunchIn punchIn : punchIns) {
-            // 一周的打卡记录容器
-            List<PunchInRecordVO> weeklyRecords = new ArrayList<>();
-            // 打卡任务信息
-            PunchInWithRecordVO punchInWithRecordVO = new PunchInWithRecordVO();
-            BeanUtils.copyProperties(punchIn, punchInWithRecordVO);
-            punchInWithRecordVO.setPunchInId(punchIn.getId());
-            punchInWithRecordVO.setPunchInRecords(weeklyRecords);
-            punchInWithRecordVO.setRecordTimeTrack(initRecordTimeTrack);
-            punchInWithRecordVO.setRecordCountTrack(0);
-            punchInWithRecordVOS.add(punchInWithRecordVO);
-
-            // 打卡任务创建日期
-            LocalDate punchInCreationDate = LocalDate.parse(sdf.format(punchIn.getCreationTime()));
-
-            // 获取打卡任务对应的打卡记录,并转为打卡日期-打卡记录map
-            List<PunchInRecord> records =
-                Optional.ofNullable(recordMap.get(punchInWithRecordVO.getPunchInId())).orElse(List.of());
-            Map<String, PunchInRecord> weeklyRecordMap = records.stream()
-                .collect(Collectors.toMap(PunchInRecord::getPunchInDate, Function.identity(), (key1, key2) -> key1));
-
-            for (LocalDate weeklyDate : weeklyDateRange) {
-                String weeklyDateStr = weeklyDate.toString();
-
-                // 获取打卡记录
-                PunchInRecord punchInRecord = weeklyRecordMap.get(weeklyDateStr);
-
-                // 根据过往打卡记录设置打卡状态
-                PunchInStatusViewEnum punchInStatus =
-                    judgePunchInStatus(today, weeklyDate, punchInCreationDate, punchIn, punchInRecord);
-
-                // 设置打卡记录
-                PunchInRecordVO punchInRecordVO = new PunchInRecordVO();
-                punchInRecordVO.setPunchInDate(weeklyDateStr);
-                punchInRecordVO.setPunchInStatus(punchInStatus);
-                weeklyRecords.add(punchInRecordVO);
-
-                // 如果是今天的打卡记录,设置计数/计时属性
-                if (!Objects.isNull(punchInRecord) && today.isEqual(weeklyDate)) {
-                    punchInWithRecordVO.setRecordTimeTrack(punchInRecord.getTimeTrack());
-                    punchInWithRecordVO.setRecordCountTrack(punchInRecord.getCountTrack());
-                }
-
-                // 如果是今天设置状态控制页面显示
-                if (today.isEqual(weeklyDate)) {
-                    punchInWithRecordVO.setPunchInStatus(punchInStatus);
-                }
-            }
-        }
-
-        return punchInWithRecordVOS;
-    }
-
-    private PunchInStatusViewEnum judgePunchInStatus(LocalDate today, LocalDate weeklyDate,
-        LocalDate punchInCreationDate, PunchIn punchIn, PunchInRecord punchInRecord) {
-        // 一周某天还没到,无法打卡
-        if (weeklyDate.isAfter(today)) {
-            return PunchInStatusViewEnum.FUTURE_TIME;
-        }
-
-        // 一周某天早于任务创建时间,无法打卡
-        if (weeklyDate.isBefore(punchInCreationDate)) {
-            return PunchInStatusViewEnum.UNCREATED;
-        }
-
-        // 一周某天在今天之前存在打卡记录,则需要判断是否完成打卡
-        if (!Objects.isNull(punchInRecord) && weeklyDate.isBefore(today)) {
-            if (PunchInStatusV1Enum.FINISH.equals(punchInRecord.getPunchInStatus())
-                || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInRecord.getPunchInStatus())) {
-                return PunchInStatusViewEnum.PUNCH_IN;
-            }
-        }
-
-        // 一周的某天是今天,且存在打卡记录,则判断是否完成打卡
-        if (weeklyDate.isEqual(today) && !Objects.isNull(punchInRecord)) {
-            PunchInStatusV1Enum punchInStatusV1Enum = settleManager.judgePunchInStatus(punchIn, punchInRecord);
-            if (PunchInStatusV1Enum.FINISH.equals(punchInStatusV1Enum)
-                || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInStatusV1Enum)) {
-                return PunchInStatusViewEnum.PUNCH_IN;
-            }
-            // 当天存在打卡记录,但是还没满足打卡条件,则认为打卡状态未知
-            return PunchInStatusViewEnum.TODAY_UNKNOWN;
-        }
-
-        // 一周的某天是今天,且不存在打卡记录,则还没进行打卡
-        if (weeklyDate.isEqual(today) && Objects.isNull(punchInRecord)) {
-            return PunchInStatusViewEnum.TODAY_UNKNOWN;
-        }
-
-        // 不符合任何情况则是未打卡
-        return PunchInStatusViewEnum.UN_PUNCH_IN;
-    }
-
-    @Override
-    public PunchInVO queryPunchInById(Long punchInId) {
-        Assert.isNullInBusiness(punchInId, "请传入待查询的任务ID");
-        return Optional.ofNullable(punchInService.getById(punchInId)).map(punchIn -> {
-            PunchInVO punchInVO = new PunchInVO();
-            BeanUtils.copyProperties(punchIn, punchInVO);
-            return punchInVO;
-        }).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
-    }
-
-    @Override
-    public void saveOrUpdatePunchIn(PunchInRequest request) {
-        Assert.isNullInBusiness(request, "请传入任务信息");
-
-        if (PunchInCategoryEnum.COUNT.equals(request.getCategory())
-            && (Objects.isNull(request.getRule()) || Objects.isNull(request.getCountTrack()))) {
-            throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空");
-        }
-
-        if (PunchInCategoryEnum.TIME.equals(request.getCategory())
-            && (Objects.isNull(request.getRule()) || Objects.isNull(request.getTimeTrack()))) {
-            throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空");
-        }
-
-        PunchIn punchIn = new PunchIn();
-        BeanUtils.copyProperties(request, punchIn);
-        punchIn.setArchiveFlag(false);
-
-        if (Objects.isNull(punchIn.getId())) {
-            punchInService.insert(punchIn);
-        } else {
-            punchInService.update(punchIn);
-        }
-    }
-
-    @Override
-    public void deletePunchIn(Long punchInId) {
-        Assert.isNullInBusiness(punchInId, "请传入待删除的任务");
-        punchInService.delete(punchInId);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void doPunchIn(PunchInRequest request) {
-        if (Objects.isNull(request) || Objects.isNull(request.getId())) {
-            BusinessException.throwFail("请传入待打卡的任务");
-        }
-
-        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getId()))
-            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
-        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory()) && Objects.isNull(request.getTimeTrack())) {
-            BusinessException.throwFail("打卡类型:计时,请传入时间记录");
-        }
-
-        // 查询今天的打卡记录
-        LocalDate today = LocalDate.now();
-        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
-        punchInRecordQuery.setPunchInDate(today.toString());
-        punchInRecordQuery.setPunchInId(request.getId());
-        PunchInRecord oldPunchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
-
-        // 打卡类型:单次打卡,需要判断是否重复打卡
-        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory()) && !Objects.isNull(oldPunchInRecord)) {
-            throw BusinessException.fail("已打卡,无须重复打卡");
-        }
-
-        // 获取或创建打卡记录
-        PunchInRecord punchInRecord = Optional.ofNullable(oldPunchInRecord).orElseGet(() -> {
-            PunchInRecord record = new PunchInRecord();
-            record.setPunchInId(request.getId());
-            record.setPunchInDate(LocalDate.now().toString());
-            return record;
-        });
-
-        // 打卡类型:计数,需要累加打卡次数
-        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
-            punchInRecord.setCountTrack(Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) + 1);
-        }
-
-        // 打卡类型:计时,需要记录最新打卡时长
-        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
-            punchInRecord.setTimeTrack(request.getTimeTrack());
-        }
-
-        // 新增或更新
-        if (Objects.isNull(punchInRecord.getId())) {
-            punchInRecordService.insert(punchInRecord);
-        } else {
-            punchInRecordService.update(punchInRecord);
-        }
-    }
-
-    @Override
-    public void archivePunchIn(Long punchInId) {
-        Assert.isNullInBusiness(punchInId, "请选择待归档的任务");
-
-        PunchIn punchIn = new PunchIn();
-        punchIn.setId(punchInId);
-        punchIn.setArchiveFlag(true);
-        punchInService.update(punchIn);
-    }
-
-    @Override
-    public void remakePunchIn(PunchInRecordRequest request) {
-        // 待补卡的打卡日期
-        LocalDate punchInDate = LocalDate.parse(request.getPunchInDate());
-
-        // 如果补打卡日期是今天,则不允许补卡
-        if (punchInDate.isEqual(LocalDate.now())) {
-            BusinessException.throwFail("补打卡日期不能为今天");
-        }
-
-        // 获取打卡任务
-        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
-            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
-        // 补打卡的打卡日期不能早于打卡任务创建日期
-        if (punchInDate.isBefore(punchIn.getCreationTime().toLocalDateTime().toLocalDate())) {
-            BusinessException.throwFail("补打卡日期不能早于打卡任务创建日期");
-        }
-
-        settleManager.settleHandler(PunchInSettleTypeEnum.REMAKE, punchInDate,
-            Arrays.asList(UserUtils.getCurrentUserId()), Arrays.asList(request.getPunchInId()));
-    }
-
-    @Override
-    public void revokePunchIn(PunchInRecordRequest request) {
-        // 获取打卡任务
-        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
-            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
-
-        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
-        punchInRecordQuery.setPunchInId(request.getPunchInId());
-        punchInRecordQuery.setPunchInDate(LocalDate.now().toString());
-        PunchInRecord punchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
-        if (Objects.isNull(punchInRecord)) {
-            log.info("打卡任务:{},打卡日期:{} 没有找到当天打卡记录,进行撤销", punchInRecordQuery.getPunchInId(),
-                punchInRecordQuery.getPunchInDate());
-            return;
-        }
-
-        // 单次打卡/计时打卡 直接删除记录即可
-        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())
-            || PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
-            punchInRecordService.delete(punchInRecord.getId());
-        }
-
-        // 计数打卡,需要减去打卡次数
-        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())
-            && Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) > 0) {
-            PunchInRecord updateRecord = new PunchInRecord();
-            updateRecord.setId(punchInRecord.getId());
-            updateRecord.setCountTrack(punchInRecord.getCountTrack() - 1);
-            punchInRecordService.update(updateRecord);
-            if (updateRecord.getCountTrack() == 0) {
-                punchInRecordService.delete(punchInRecord.getId());
-            }
-        }
-    }
-
-    @Override
-    public PunchInDataVO queryPunchInData(PunchInDataQuery query) {
-        PunchIn punchIn = Optional.ofNullable(punchInService.getById(query.getId()))
-            .orElseThrow(() -> BusinessException.fail("找到指定的打卡任务"));
-
-        SimpleDateFormat sdf = DateUtils.buildDateFormat();
-        YearMonth yearMonth = YearMonth.of(query.getYear(), query.getMonth());
-        // 本月第一日
-        LocalDate firstOfMonth = yearMonth.atDay(1);
-        // 本月最后以日
-        LocalDate endOfMonth = yearMonth.atEndOfMonth();
-        // 任务创建日期
-        LocalDate punchInCreationDate = punchIn.getCreationTime().toLocalDateTime().toLocalDate();
-
-        // 获取打卡记录
-        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
-        punchInRecordQuery.setPunchInId(punchIn.getId());
-        punchInRecordQuery.setStartDate(String.format("%s 00:00:00.000", firstOfMonth));
-        punchInRecordQuery.setEndDate(String.format("%s 23:59:59.999", endOfMonth));
-        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
-
-        // 构造数据,日历部分的数据只需要已打卡的数据
-        List<PunchInCalendarDataVO> punchInCalendarDataVOS = new ArrayList<>();
-        List<PunchInRecordDataVO> punchInRecordDtoList = new ArrayList<>();
-        for (PunchInRecord punchInRecord : punchInRecords) {
-            PunchInRecordDataVO punchInRecordDataVO = new PunchInRecordDataVO();
-            BeanUtils.copyProperties(punchInRecord, punchInRecordDataVO);
-            punchInRecordDtoList.add(punchInRecordDataVO);
-
-            if (PunchInStatusV1Enum.UN_FINISH.equals(punchInRecord.getPunchInStatus())
-                || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) {
-                continue;
-            }
-            PunchInCalendarDataVO punchInCalendarDataVO = new PunchInCalendarDataVO();
-            punchInCalendarDataVO.setPunchInDate(punchInRecord.getPunchInDate());
-            punchInCalendarDataVO.setInfo("打卡");
-            punchInCalendarDataVO.setDate(punchInRecord.getPunchInDate());
-            punchInCalendarDataVOS.add(punchInCalendarDataVO);
-        }
-
-        // 计算全勤率
-        BigDecimal punchInRate = BigDecimal.ZERO;
-        // 要考虑任务刚创建的情况,任务较迟创建的话则使用任务创建时间
-        long dayLength = ChronoUnit.DAYS
-            .between(firstOfMonth.isBefore(punchInCreationDate) ? punchInCreationDate : firstOfMonth, LocalDate.now());
-        if (dayLength != 0) {
-            punchInRate = BigDecimal.valueOf(punchInCalendarDataVOS.size())
-                .divide(BigDecimal.valueOf(dayLength), 4, RoundingMode.HALF_DOWN).multiply(ONE_HUNDRED);
-        }
-
-        // 构造返回结果
-        PunchInDataVO punchInDataVO = new PunchInDataVO();
-        punchInDataVO.setStartDate(sdf.format(punchIn.getCreationTime()));
-        punchInDataVO.setEndDate(LocalDate.now().toString());
-        punchInDataVO.setPunchInNum(punchInCalendarDataVOS.size());
-        punchInDataVO.setCalendarSelected(punchInCalendarDataVOS);
-        punchInDataVO.setPunchInRecords(punchInRecordDtoList);
-        punchInDataVO.setPunchInRate(punchInRate);
-
-        return punchInDataVO;
-    }
-
-    /**
-     * 默认打卡完成率
-     */
-    private static final BigDecimal DEFAULT_PUNCH_IN_DONE_RATE = new BigDecimal("101.00");
-
     @Autowired
     private ICalendarManager calendarManager;
 
@@ -493,6 +73,7 @@ public class PunchInManagerImpl implements IPunchInManager {
     @Override
     public PunchInStatusEnum judgePunchInStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
         Assert.isNullInBusiness(punchInTask, "打卡任务不能为空 ");
+
         // 没有打卡记录,直接没完成
         if (Objects.isNull(punchInTaskHistory)) {
             return PunchInStatusEnum.UNDONE;
@@ -522,7 +103,7 @@ public class PunchInManagerImpl implements IPunchInManager {
         }
 
         // 计时打卡,要区分是否节假日使用不同的判断标准
-        if (PunchInCategoryEnum.TIME.equals(punchInTask.getPunchInMethod())) {
+        if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) {
             LocalTime timeTrack = enableHolidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack();
             LocalTime punchInTimeTrack = Optional.ofNullable(punchInTaskHistory.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
             if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) > -1) {
@@ -547,251 +128,9 @@ public class PunchInManagerImpl implements IPunchInManager {
         // 完成的打卡数量
         long punchInDoneCount = punchInTaskHistoryList.stream().filter(v -> PunchInStatusEnum.DONE.equals(v.getPunchInStatus())).count();
 
-        // 次数,当天打卡次数大于等于设置的次数,则完成打卡
-        if (PunchInMethodMultiEnum.COUNT.equals(punchInMultiTask.getPunchInMethod())) {
-            // 没有设置时默认设置一个较大的数,避免出现打卡次数为0的情况
-            Integer doneCount = Optional.ofNullable(punchInMultiTask.getPunchInDoneCount()).orElse(punchInTaskHistoryList.size() + 1);
-            return punchInDoneCount >= doneCount ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
-        }
-
-        // 比率,当天打卡完成比率大于等于设置的比率,则完成打卡
-
-        if (PunchInMethodMultiEnum.RATE.equals(punchInMultiTask.getPunchInMethod())) {
-            // 没有设置时默认设置一个较大的数,避免出现打卡完成率为0的情况
-            BigDecimal doneRate = Optional.ofNullable(punchInMultiTask.getPunchInDoneRate()).orElse(DEFAULT_PUNCH_IN_DONE_RATE);
-            BigDecimal punchInDoneRate = BigDecimal.valueOf(punchInDoneCount).divide(BigDecimal.valueOf(multiTaskNum), 2, RoundingMode.HALF_UP);
-            return punchInDoneRate.compareTo(doneRate) > -1 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
-        }
-
-        return PunchInStatusEnum.UNDONE;
-    }
-
-    @Override
-    public int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExts, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus) {
-        // 未完成打卡,积分为0
-        if (PunchInStatusEnum.UNDONE.equals(punchInTaskHistory.getPunchInStatus())) {
-            return 0;
-        }
-
-        // 单次打卡中使用的拓展信息
-        List<PunchInTaskExt> punchInExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
-        // 打卡任务使用的拓展信息
-        List<PunchInTaskExt> punchInTaskExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
-
-        // 单个任务积分=基本积分+(可选)额外积分+(可选)连续完成额外积分+法定节假日(含周末)双倍奖励+全勤双倍奖励
-        // 基本积分
-        int basicPoints = Optional.ofNullable(punchInTask.getPoints()).orElse(0);
-
-        // 额外积分
-        basicPoints += calculateExtraPointsInTask(punchInTask, punchInExtList, punchInTaskHistory, calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate()));
-
-        // 启用了全勤奖励
-        int fullAttendancePoints = 0;
-        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getFullAttendanceStatus())) {
-            LocalDate punchInDate = LocalDate.parse(punchInTaskHistory.getPunchInDate());
-            // 结算周期:周,并且结算日是周末,则双倍奖励;
-            if (FullAttendancePeriodEnum.WEEK.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.getDayOfWeek().getValue() == 7) {
-                int undoneCount = punchInStatsWeek.getPunchInTotalCount() - punchInStatsWeek.getPunchInDoneCount();
-                if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
-                    fullAttendancePoints = basicPoints;
-                }
-            }
-
-            // 结算周期:月,结算日是当月最后一天,则双倍奖励;
-            if (FullAttendancePeriodEnum.MONTH.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.lengthOfMonth() == punchInDate.getDayOfMonth()) {
-                int undoneCount = punchInStatsMonth.getPunchInTotalCount() - punchInStatsMonth.getPunchInDoneCount();
-                if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
-                    fullAttendancePoints = basicPoints;
-                }
-            }
-        }
-
-        // 启用了法定节假日(含周末)双倍奖励
-        int holidayPoints = 0;
-        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getHolidayStatus()) && calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())) {
-            holidayPoints = basicPoints;
-        }
-
-        // 连续完成额外积分
-        int taskPoints = 0;
-        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
-            // punchInTaskExtList 根据InitialValue倒序排列
-            punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed());
-            // 第二轮标志
-            boolean secondRound = false;
-            // 上一轮的值
-            int prevInitialValue = 0;
-            for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
-                // 比较结果
-                int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
-                // 如果连续日期小于initialValue,则跳过
-                if (compareValue == -1) {
-                    continue;
-                }
-                // 额外奖励数
-                int extraCount = 0;
-                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
-                if (secondRound) {
-                    extraCount = prevInitialValue - punchInTaskExt.getInitialValue();
-                } else if (compareValue >= 0) {
-                    extraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
-                    secondRound = true;
-                }
-                taskPoints +=  punchInTaskExt.getExtraPoints() * extraCount;
-                prevInitialValue = punchInTaskExt.getInitialValue();
-            }
-        }
-
-        return basicPoints + fullAttendancePoints + holidayPoints + taskPoints;
-    }
-
-    /**
-     * 计算额外积分
-     * @param punchInTask
-     * @param punchInTaskExtList
-     * @return
-     */
-    private int calculateExtraPointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
-        // 单次打卡或者没有启用额外积分计算则跳过
-        if (PunchInExtraMethodEnum.NONE.equals(punchInTask.getExtraMethod())
-            || PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
-            return 0;
-        }
-
-        // 计算超出部分,
-        int extraCountTrack = 0;
-        // 计数打卡
-        if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
-            extraCountTrack = punchInTaskHistory.getCountTrack() - (holidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack());
-        }
-        // 计时打卡
-        if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) {
-            // 超出的时间按照时间间隔转换成次数
-            long timeTrack = punchInTaskHistory.getTimeTrack().until((holidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack()), ChronoUnit.MINUTES);
-            extraCountTrack = BigDecimal.valueOf(timeTrack).divide(BigDecimal.valueOf(punchInTask.getExtraTimeStep()), 0, RoundingMode.FLOOR).intValue();
-        }
-
-        // 如果等于0则没有超出部分,不用计算
-        if (extraCountTrack == 0) {
-            return 0;
-        }
-
-        // 固定计算
-        if (PunchInExtraMethodEnum.FIXED.equals(punchInTask.getExtraMethod())) {
-            Integer extraPoints = Optional.ofNullable(punchInTask.getExtraPoints()).orElse(0);
-            return extraCountTrack * extraPoints;
-        }
-
-        // 区间计算
-        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInTaskExtList)) {
-            int basicPoints = 0;
-            int prevInitialValue = 0;
-            for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
-                Integer initialValue = Optional.ofNullable(punchInTaskExt.getInitialValue()).orElse(0);
-                // 第一轮:initialValue - extraCountTrack的值v1
-                // 如果v1≤0则超出initialValue范围,使用initialValue计算;
-                // 如果v1>0则在initialValue范围内 使用extraCountTrack
-                // 后续轮:本轮initialValue - extraCountTrack的值v1、
-                // 如果v1≤0则超出本轮initialValue范围,使用上一轮initialValue-本轮initialValue的值v2计算;
-                // 如果v1>0则在本轮initialValue范围内 使用extraCountTrack-上一轮initialValue的值v3计算
-                // 下面的代码不适合第一轮的计算
-                if (initialValue - extraCountTrack <= 0) {
-                    basicPoints = punchInTaskExt.getExtraPoints() * Math.abs(prevInitialValue - initialValue);
-                } else {
-                    basicPoints = punchInTaskExt.getExtraPoints() * (extraCountTrack - prevInitialValue);
-                }
-                prevInitialValue = initialValue;
-            }
-            return basicPoints;
-        }
-
-        return 0;
-    }
-
-    @Override
-    public int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus) {
-        // 未完成打卡或没有启用多任务积分计算,积分为0
-        if (PunchInStatusEnum.UNDONE.equals(punchInMultiTaskHistory.getPunchInStatus())
-            || CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus())) {
-            return 0;
-        }
-
-        // 单次打卡中使用的拓展信息
-        List<PunchInMultiTaskExt> punchInExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
-        // 打卡任务使用的拓展信息
-        List<PunchInMultiTaskExt> punchInTaskExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
-
-        // 多个任务积分=多任务基本积分+多任务额外积分+连续完成额外积分
-        // 基本积分
-        int basicPoints = punchInMultiTask.getPoints();
-
-        // 额外积分
-        int extraPoints = 0;
-        // 额外次数
-        int extractCount = punchInMultiTaskHistory.getPunchInDoneCount() - punchInMultiTask.getPunchInDoneCount();
-        // 固定计算
-        if (PunchInExtraMethodEnum.FIXED.equals(punchInMultiTask.getExtraMethod())) {
-            extraPoints = extractCount * punchInMultiTask.getExtraPoints();
-        }
-        // 区间计算
-        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInMultiTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInMultiTaskExts)) {
-            // punchInTaskExtList 根据InitialValue倒序排列
-            punchInExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
-            // 第二轮标志
-            boolean secondRound = false;
-            // 上一轮的值
-            int prevInitialValue = 0;
-            for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
-                // 比较结果
-                int compareValue = Integer.compare(extractCount, punchInTaskExt.getInitialValue());
-                // 如果连续日期小于initialValue,则跳过
-                if (compareValue == -1) {
-                    continue;
-                }
-                // 额外奖励数
-                int tempExtraCount = 0;
-                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
-                if (secondRound) {
-                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
-                } else if (compareValue >= 0) {
-                    tempExtraCount = extractCount - punchInTaskExt.getInitialValue() + 1;
-                    secondRound = true;
-                }
-                extraPoints +=  punchInTaskExt.getExtraPoints() * tempExtraCount;
-                prevInitialValue = punchInTaskExt.getInitialValue();
-            }
-        }
-
-        // 连续完成额外积分
-        int taskPoints = 0;
-        if (CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
-            // punchInTaskExtList 根据InitialValue倒序排列
-            punchInTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
-            // 第二轮标志
-            boolean secondRound = false;
-            // 上一轮的值
-            int prevInitialValue = 0;
-            for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
-                // 比较结果
-                int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
-                // 如果连续日期小于initialValue,则跳过
-                if (compareValue == -1) {
-                    continue;
-                }
-                // 额外奖励数
-                int tempExtraCount = 0;
-                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
-                if (secondRound) {
-                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
-                } else if (compareValue >= 0) {
-                    tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
-                    secondRound = true;
-                }
-                taskPoints +=  punchInTaskExt.getExtraPoints() * tempExtraCount;
-                prevInitialValue = punchInTaskExt.getInitialValue();
-            }
-        }
-
-        return basicPoints + extraPoints + taskPoints;
+        // 当天打卡次数大于等于设置的次数,则完成打卡
+        // 没有设置时默认设置一个较大的数,避免出现打卡次数为0的情况
+        Integer doneCount = Optional.ofNullable(punchInMultiTask.getPunchInDoneCount()).orElse(punchInTaskHistoryList.size() + 1);
+        return punchInDoneCount >= doneCount ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
     }
 }

+ 797 - 0
src/main/java/com/punchsettle/server/service/manager/impl/PunchInManagerV1Impl.java

@@ -0,0 +1,797 @@
+package com.punchsettle.server.service.manager.impl;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.YearMonth;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.punchsettle.server.atomic.entity.PunchInMultiTask;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
+import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
+import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
+import com.punchsettle.server.atomic.entity.PunchInStatus;
+import com.punchsettle.server.atomic.entity.PunchInTaskExt;
+import com.punchsettle.server.constant.ConsecutiveStatusEnum;
+import com.punchsettle.server.constant.FullAttendancePeriodEnum;
+import com.punchsettle.server.constant.PunchInDimensionEnum;
+import com.punchsettle.server.constant.PunchInExtraMethodEnum;
+import com.punchsettle.server.constant.PunchInMethodMultiEnum;
+import com.punchsettle.server.constant.RepeatCategoryEnum;
+import com.punchsettle.server.service.manager.ICalendarManager;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import com.punchsettle.server.atomic.entity.PunchIn;
+import com.punchsettle.server.atomic.entity.PunchInRecord;
+import com.punchsettle.server.atomic.entity.PunchInTask;
+import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
+import com.punchsettle.server.atomic.service.IPunchInRecordService;
+import com.punchsettle.server.atomic.service.IPunchInService;
+import com.punchsettle.server.common.exception.BusinessException;
+import com.punchsettle.server.common.utils.Assert;
+import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
+import com.punchsettle.server.constant.CompareRuleEnum;
+import com.punchsettle.server.constant.PunchInCategoryEnum;
+import com.punchsettle.server.constant.PunchInMethodEnum;
+import com.punchsettle.server.constant.PunchInSettleTypeEnum;
+import com.punchsettle.server.constant.PunchInStatusEnum;
+import com.punchsettle.server.constant.PunchInStatusV1Enum;
+import com.punchsettle.server.constant.PunchInStatusViewEnum;
+import com.punchsettle.server.pojo.punchin.PunchInCalendarDataVO;
+import com.punchsettle.server.pojo.punchin.PunchInDataQuery;
+import com.punchsettle.server.pojo.punchin.PunchInDataVO;
+import com.punchsettle.server.pojo.punchin.PunchInQuery;
+import com.punchsettle.server.pojo.punchin.PunchInRecordDataVO;
+import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
+import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
+import com.punchsettle.server.pojo.punchin.PunchInRecordVO;
+import com.punchsettle.server.pojo.punchin.PunchInRequest;
+import com.punchsettle.server.pojo.punchin.PunchInVO;
+import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
+import com.punchsettle.server.service.manager.IPunchInManagerV1;
+import com.punchsettle.server.service.manager.ISettleManagerV1;
+import com.punchsettle.server.utiis.DateUtils;
+import com.punchsettle.server.utiis.UserUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @description 打卡结算任务 服务类
+ * @date 2024/11/25 15:12
+ */
+@Slf4j
+@Service
+public class PunchInManagerV1Impl implements IPunchInManagerV1 {
+
+    @Autowired
+    private IPunchInService punchInService;
+
+    @Autowired
+    private IPunchInRecordService punchInRecordService;
+
+    @Autowired
+    private ISettleManagerV1 settleManager;
+
+    /**
+     * 数值100
+     */
+    private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
+
+    @Override
+    public List<PunchInWithRecordVO> queryPunchInAndRecord() {
+        // 获取当前用户ID
+        Long currentUserId = Optional.ofNullable(UserUtils.getCurrentUserId())
+            .orElseThrow(() -> BusinessException.fail("无法获取当前用户信息,无法查询打卡任务"));
+
+        // 查询打卡任务
+        PunchInQuery punchInQuery = new PunchInQuery();
+        punchInQuery.setArchiveFlag(false);
+        punchInQuery.setUserIds(Arrays.asList(currentUserId));
+        List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
+        if (CollectionUtils.isEmpty(punchIns)) {
+            log.info("用户:{} 没有查询到生效的打卡任务");
+            return List.of();
+        }
+
+        // 获取一周的起始日期范围
+        List<LocalDate> weeklyDateRange = DateUtils.getWeeklyDateRange();
+        // 获取打卡任务ID
+        List<Long> punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
+        // 找出范围内的打卡记录
+        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
+        punchInRecordQuery.setPunchInIds(punchInIds);
+        punchInRecordQuery.setStartDate(weeklyDateRange.getFirst().toString());
+        punchInRecordQuery.setEndDate(weeklyDateRange.getLast().toString());
+        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
+
+        // 打卡任务-打卡记录 分组
+        Map<Long, List<PunchInRecord>> recordMap =
+            punchInRecords.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
+
+        // 日期格式化
+        SimpleDateFormat sdf = DateUtils.buildDateFormat();
+
+        // 当前日期
+        LocalDate today = LocalDate.now();
+
+        // 构建打卡记录
+        List<PunchInWithRecordVO> punchInWithRecordVOS = new ArrayList<>();
+        // 初始化的时间记录
+        LocalTime initRecordTimeTrack = LocalTime.parse("00:00:00.000");
+        for (PunchIn punchIn : punchIns) {
+            // 一周的打卡记录容器
+            List<PunchInRecordVO> weeklyRecords = new ArrayList<>();
+            // 打卡任务信息
+            PunchInWithRecordVO punchInWithRecordVO = new PunchInWithRecordVO();
+            BeanUtils.copyProperties(punchIn, punchInWithRecordVO);
+            punchInWithRecordVO.setPunchInId(punchIn.getId());
+            punchInWithRecordVO.setPunchInRecords(weeklyRecords);
+            punchInWithRecordVO.setRecordTimeTrack(initRecordTimeTrack);
+            punchInWithRecordVO.setRecordCountTrack(0);
+            punchInWithRecordVOS.add(punchInWithRecordVO);
+
+            // 打卡任务创建日期
+            LocalDate punchInCreationDate = LocalDate.parse(sdf.format(punchIn.getCreationTime()));
+
+            // 获取打卡任务对应的打卡记录,并转为打卡日期-打卡记录map
+            List<PunchInRecord> records =
+                Optional.ofNullable(recordMap.get(punchInWithRecordVO.getPunchInId())).orElse(List.of());
+            Map<String, PunchInRecord> weeklyRecordMap = records.stream()
+                .collect(Collectors.toMap(PunchInRecord::getPunchInDate, Function.identity(), (key1, key2) -> key1));
+
+            for (LocalDate weeklyDate : weeklyDateRange) {
+                String weeklyDateStr = weeklyDate.toString();
+
+                // 获取打卡记录
+                PunchInRecord punchInRecord = weeklyRecordMap.get(weeklyDateStr);
+
+                // 根据过往打卡记录设置打卡状态
+                PunchInStatusViewEnum punchInStatus =
+                    judgePunchInStatus(today, weeklyDate, punchInCreationDate, punchIn, punchInRecord);
+
+                // 设置打卡记录
+                PunchInRecordVO punchInRecordVO = new PunchInRecordVO();
+                punchInRecordVO.setPunchInDate(weeklyDateStr);
+                punchInRecordVO.setPunchInStatus(punchInStatus);
+                weeklyRecords.add(punchInRecordVO);
+
+                // 如果是今天的打卡记录,设置计数/计时属性
+                if (!Objects.isNull(punchInRecord) && today.isEqual(weeklyDate)) {
+                    punchInWithRecordVO.setRecordTimeTrack(punchInRecord.getTimeTrack());
+                    punchInWithRecordVO.setRecordCountTrack(punchInRecord.getCountTrack());
+                }
+
+                // 如果是今天设置状态控制页面显示
+                if (today.isEqual(weeklyDate)) {
+                    punchInWithRecordVO.setPunchInStatus(punchInStatus);
+                }
+            }
+        }
+
+        return punchInWithRecordVOS;
+    }
+
+    private PunchInStatusViewEnum judgePunchInStatus(LocalDate today, LocalDate weeklyDate,
+        LocalDate punchInCreationDate, PunchIn punchIn, PunchInRecord punchInRecord) {
+        // 一周某天还没到,无法打卡
+        if (weeklyDate.isAfter(today)) {
+            return PunchInStatusViewEnum.FUTURE_TIME;
+        }
+
+        // 一周某天早于任务创建时间,无法打卡
+        if (weeklyDate.isBefore(punchInCreationDate)) {
+            return PunchInStatusViewEnum.UNCREATED;
+        }
+
+        // 一周某天在今天之前存在打卡记录,则需要判断是否完成打卡
+        if (!Objects.isNull(punchInRecord) && weeklyDate.isBefore(today)) {
+            if (PunchInStatusV1Enum.FINISH.equals(punchInRecord.getPunchInStatus())
+                || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInRecord.getPunchInStatus())) {
+                return PunchInStatusViewEnum.PUNCH_IN;
+            }
+        }
+
+        // 一周的某天是今天,且存在打卡记录,则判断是否完成打卡
+        if (weeklyDate.isEqual(today) && !Objects.isNull(punchInRecord)) {
+            PunchInStatusV1Enum punchInStatusV1Enum = settleManager.judgePunchInStatus(punchIn, punchInRecord);
+            if (PunchInStatusV1Enum.FINISH.equals(punchInStatusV1Enum)
+                || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInStatusV1Enum)) {
+                return PunchInStatusViewEnum.PUNCH_IN;
+            }
+            // 当天存在打卡记录,但是还没满足打卡条件,则认为打卡状态未知
+            return PunchInStatusViewEnum.TODAY_UNKNOWN;
+        }
+
+        // 一周的某天是今天,且不存在打卡记录,则还没进行打卡
+        if (weeklyDate.isEqual(today) && Objects.isNull(punchInRecord)) {
+            return PunchInStatusViewEnum.TODAY_UNKNOWN;
+        }
+
+        // 不符合任何情况则是未打卡
+        return PunchInStatusViewEnum.UN_PUNCH_IN;
+    }
+
+    @Override
+    public PunchInVO queryPunchInById(Long punchInId) {
+        Assert.isNullInBusiness(punchInId, "请传入待查询的任务ID");
+        return Optional.ofNullable(punchInService.getById(punchInId)).map(punchIn -> {
+            PunchInVO punchInVO = new PunchInVO();
+            BeanUtils.copyProperties(punchIn, punchInVO);
+            return punchInVO;
+        }).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
+    }
+
+    @Override
+    public void saveOrUpdatePunchIn(PunchInRequest request) {
+        Assert.isNullInBusiness(request, "请传入任务信息");
+
+        if (PunchInCategoryEnum.COUNT.equals(request.getCategory())
+            && (Objects.isNull(request.getRule()) || Objects.isNull(request.getCountTrack()))) {
+            throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空");
+        }
+
+        if (PunchInCategoryEnum.TIME.equals(request.getCategory())
+            && (Objects.isNull(request.getRule()) || Objects.isNull(request.getTimeTrack()))) {
+            throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空");
+        }
+
+        PunchIn punchIn = new PunchIn();
+        BeanUtils.copyProperties(request, punchIn);
+        punchIn.setArchiveFlag(false);
+
+        if (Objects.isNull(punchIn.getId())) {
+            punchInService.insert(punchIn);
+        } else {
+            punchInService.update(punchIn);
+        }
+    }
+
+    @Override
+    public void deletePunchIn(Long punchInId) {
+        Assert.isNullInBusiness(punchInId, "请传入待删除的任务");
+        punchInService.delete(punchInId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void doPunchIn(PunchInRequest request) {
+        if (Objects.isNull(request) || Objects.isNull(request.getId())) {
+            BusinessException.throwFail("请传入待打卡的任务");
+        }
+
+        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getId()))
+            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
+        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory()) && Objects.isNull(request.getTimeTrack())) {
+            BusinessException.throwFail("打卡类型:计时,请传入时间记录");
+        }
+
+        // 查询今天的打卡记录
+        LocalDate today = LocalDate.now();
+        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
+        punchInRecordQuery.setPunchInDate(today.toString());
+        punchInRecordQuery.setPunchInId(request.getId());
+        PunchInRecord oldPunchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
+
+        // 打卡类型:单次打卡,需要判断是否重复打卡
+        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory()) && !Objects.isNull(oldPunchInRecord)) {
+            throw BusinessException.fail("已打卡,无须重复打卡");
+        }
+
+        // 获取或创建打卡记录
+        PunchInRecord punchInRecord = Optional.ofNullable(oldPunchInRecord).orElseGet(() -> {
+            PunchInRecord record = new PunchInRecord();
+            record.setPunchInId(request.getId());
+            record.setPunchInDate(LocalDate.now().toString());
+            return record;
+        });
+
+        // 打卡类型:计数,需要累加打卡次数
+        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
+            punchInRecord.setCountTrack(Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) + 1);
+        }
+
+        // 打卡类型:计时,需要记录最新打卡时长
+        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
+            punchInRecord.setTimeTrack(request.getTimeTrack());
+        }
+
+        // 新增或更新
+        if (Objects.isNull(punchInRecord.getId())) {
+            punchInRecordService.insert(punchInRecord);
+        } else {
+            punchInRecordService.update(punchInRecord);
+        }
+    }
+
+    @Override
+    public void archivePunchIn(Long punchInId) {
+        Assert.isNullInBusiness(punchInId, "请选择待归档的任务");
+
+        PunchIn punchIn = new PunchIn();
+        punchIn.setId(punchInId);
+        punchIn.setArchiveFlag(true);
+        punchInService.update(punchIn);
+    }
+
+    @Override
+    public void remakePunchIn(PunchInRecordRequest request) {
+        // 待补卡的打卡日期
+        LocalDate punchInDate = LocalDate.parse(request.getPunchInDate());
+
+        // 如果补打卡日期是今天,则不允许补卡
+        if (punchInDate.isEqual(LocalDate.now())) {
+            BusinessException.throwFail("补打卡日期不能为今天");
+        }
+
+        // 获取打卡任务
+        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
+            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
+        // 补打卡的打卡日期不能早于打卡任务创建日期
+        if (punchInDate.isBefore(punchIn.getCreationTime().toLocalDateTime().toLocalDate())) {
+            BusinessException.throwFail("补打卡日期不能早于打卡任务创建日期");
+        }
+
+        settleManager.settleHandler(PunchInSettleTypeEnum.REMAKE, punchInDate,
+            Arrays.asList(UserUtils.getCurrentUserId()), Arrays.asList(request.getPunchInId()));
+    }
+
+    @Override
+    public void revokePunchIn(PunchInRecordRequest request) {
+        // 获取打卡任务
+        PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
+            .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
+
+        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
+        punchInRecordQuery.setPunchInId(request.getPunchInId());
+        punchInRecordQuery.setPunchInDate(LocalDate.now().toString());
+        PunchInRecord punchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
+        if (Objects.isNull(punchInRecord)) {
+            log.info("打卡任务:{},打卡日期:{} 没有找到当天打卡记录,进行撤销", punchInRecordQuery.getPunchInId(),
+                punchInRecordQuery.getPunchInDate());
+            return;
+        }
+
+        // 单次打卡/计时打卡 直接删除记录即可
+        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())
+            || PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
+            punchInRecordService.delete(punchInRecord.getId());
+        }
+
+        // 计数打卡,需要减去打卡次数
+        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())
+            && Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) > 0) {
+            PunchInRecord updateRecord = new PunchInRecord();
+            updateRecord.setId(punchInRecord.getId());
+            updateRecord.setCountTrack(punchInRecord.getCountTrack() - 1);
+            punchInRecordService.update(updateRecord);
+            if (updateRecord.getCountTrack() == 0) {
+                punchInRecordService.delete(punchInRecord.getId());
+            }
+        }
+    }
+
+    @Override
+    public PunchInDataVO queryPunchInData(PunchInDataQuery query) {
+        PunchIn punchIn = Optional.ofNullable(punchInService.getById(query.getId()))
+            .orElseThrow(() -> BusinessException.fail("找到指定的打卡任务"));
+
+        SimpleDateFormat sdf = DateUtils.buildDateFormat();
+        YearMonth yearMonth = YearMonth.of(query.getYear(), query.getMonth());
+        // 本月第一日
+        LocalDate firstOfMonth = yearMonth.atDay(1);
+        // 本月最后以日
+        LocalDate endOfMonth = yearMonth.atEndOfMonth();
+        // 任务创建日期
+        LocalDate punchInCreationDate = punchIn.getCreationTime().toLocalDateTime().toLocalDate();
+
+        // 获取打卡记录
+        PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
+        punchInRecordQuery.setPunchInId(punchIn.getId());
+        punchInRecordQuery.setStartDate(String.format("%s 00:00:00.000", firstOfMonth));
+        punchInRecordQuery.setEndDate(String.format("%s 23:59:59.999", endOfMonth));
+        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
+
+        // 构造数据,日历部分的数据只需要已打卡的数据
+        List<PunchInCalendarDataVO> punchInCalendarDataVOS = new ArrayList<>();
+        List<PunchInRecordDataVO> punchInRecordDtoList = new ArrayList<>();
+        for (PunchInRecord punchInRecord : punchInRecords) {
+            PunchInRecordDataVO punchInRecordDataVO = new PunchInRecordDataVO();
+            BeanUtils.copyProperties(punchInRecord, punchInRecordDataVO);
+            punchInRecordDtoList.add(punchInRecordDataVO);
+
+            if (PunchInStatusV1Enum.UN_FINISH.equals(punchInRecord.getPunchInStatus())
+                || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) {
+                continue;
+            }
+            PunchInCalendarDataVO punchInCalendarDataVO = new PunchInCalendarDataVO();
+            punchInCalendarDataVO.setPunchInDate(punchInRecord.getPunchInDate());
+            punchInCalendarDataVO.setInfo("打卡");
+            punchInCalendarDataVO.setDate(punchInRecord.getPunchInDate());
+            punchInCalendarDataVOS.add(punchInCalendarDataVO);
+        }
+
+        // 计算全勤率
+        BigDecimal punchInRate = BigDecimal.ZERO;
+        // 要考虑任务刚创建的情况,任务较迟创建的话则使用任务创建时间
+        long dayLength = ChronoUnit.DAYS
+            .between(firstOfMonth.isBefore(punchInCreationDate) ? punchInCreationDate : firstOfMonth, LocalDate.now());
+        if (dayLength != 0) {
+            punchInRate = BigDecimal.valueOf(punchInCalendarDataVOS.size())
+                .divide(BigDecimal.valueOf(dayLength), 4, RoundingMode.HALF_DOWN).multiply(ONE_HUNDRED);
+        }
+
+        // 构造返回结果
+        PunchInDataVO punchInDataVO = new PunchInDataVO();
+        punchInDataVO.setStartDate(sdf.format(punchIn.getCreationTime()));
+        punchInDataVO.setEndDate(LocalDate.now().toString());
+        punchInDataVO.setPunchInNum(punchInCalendarDataVOS.size());
+        punchInDataVO.setCalendarSelected(punchInCalendarDataVOS);
+        punchInDataVO.setPunchInRecords(punchInRecordDtoList);
+        punchInDataVO.setPunchInRate(punchInRate);
+
+        return punchInDataVO;
+    }
+
+    /**
+     * 默认打卡完成率
+     */
+    private static final BigDecimal DEFAULT_PUNCH_IN_DONE_RATE = new BigDecimal("101.00");
+
+    @Autowired
+    private ICalendarManager calendarManager;
+
+    @Override
+    public boolean judgeRepeat(PunchInTask punchInTask, String repeatDateStr) {
+        // 每天
+        if (RepeatCategoryEnum.EVERYDAY.equals(punchInTask.getRepeatCategory())) {
+            return true;
+        }
+
+        // 法定工作日
+        if (RepeatCategoryEnum.WORKDAY.equals(punchInTask.getRepeatCategory())) {
+            return !calendarManager.judgeHoliday(repeatDateStr);
+        }
+
+        // 法定节假日(含周末)
+        if (RepeatCategoryEnum.HOLIDAY.equals(punchInTask.getRepeatCategory())) {
+            return !calendarManager.judgeHoliday(repeatDateStr);
+        }
+
+        // 自定义
+        if (RepeatCategoryEnum.CUSTOM.equals(punchInTask.getRepeatCategory())) {
+            if (!StringUtils.hasText(punchInTask.getRepeatCustomDay())) {
+                BusinessException.throwFail(String.format("打卡任务ID:%s, 重复周期类型:自定义重复,没有设定自定义重复日期"));
+            }
+
+            LocalDate repeatDate = LocalDate.parse(repeatDateStr);
+            int dayOfWeekValue = repeatDate.getDayOfWeek().getValue();
+            return punchInTask.getRepeatCustomDay().contains(String.valueOf(dayOfWeekValue));
+        }
+
+        return false;
+    }
+
+    @Override
+    public PunchInStatusEnum judgePunchInStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
+        Assert.isNullInBusiness(punchInTask, "打卡任务不能为空 ");
+        // 没有打卡记录,直接没完成
+        if (Objects.isNull(punchInTaskHistory)) {
+            return PunchInStatusEnum.UNDONE;
+        }
+
+        // 单次打卡,不区分是否节假日
+        if (PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
+            int countTrack = Optional.ofNullable(punchInTask.getCountTrack()).orElse(0);
+            return countTrack > 0 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
+        }
+
+        // 打卡任务的节假日启用配置
+        CommonEnableStatusEnum holidayStatus = Optional.ofNullable(punchInTask.getHolidayStatus()).orElse(CommonEnableStatusEnum.DISABLED);
+        boolean enableHolidayFlag = CommonEnableStatusEnum.ENABLED.equals(holidayStatus) && holidayFlag;
+
+        // 计数打卡,要区分是否节假日使用不同的判断标准
+        if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
+            int countTrack = enableHolidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack();
+            int punchInCountTrack = Optional.ofNullable(punchInTaskHistory.getCountTrack()).orElse(0);
+            if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInCountTrack >= countTrack) {
+                return PunchInStatusEnum.DONE;
+            }
+            if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInCountTrack <= countTrack) {
+                return PunchInStatusEnum.DONE;
+            }
+            return PunchInStatusEnum.UNDONE;
+        }
+
+        // 计时打卡,要区分是否节假日使用不同的判断标准
+        if (PunchInCategoryEnum.TIME.equals(punchInTask.getPunchInMethod())) {
+            LocalTime timeTrack = enableHolidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack();
+            LocalTime punchInTimeTrack = Optional.ofNullable(punchInTaskHistory.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
+            if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) > -1) {
+                return PunchInStatusEnum.DONE;
+            }
+            if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) < 1) {
+                return PunchInStatusEnum.DONE;
+            }
+
+            return PunchInStatusEnum.UNDONE;
+        }
+
+        return PunchInStatusEnum.UNDONE;
+    }
+
+    @Override
+    public PunchInStatusEnum judgePunchInStatusInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInTaskHistory> punchInTaskHistoryList, int multiTaskNum) {
+        if (CollectionUtils.isEmpty(punchInTaskHistoryList) || multiTaskNum == 0) {
+            return PunchInStatusEnum.UNDONE;
+        }
+
+        // 完成的打卡数量
+        long punchInDoneCount = punchInTaskHistoryList.stream().filter(v -> PunchInStatusEnum.DONE.equals(v.getPunchInStatus())).count();
+
+//        // 次数,当天打卡次数大于等于设置的次数,则完成打卡
+//        if (PunchInMethodMultiEnum.COUNT.equals(punchInMultiTask.getPunchInMethod())) {
+//            // 没有设置时默认设置一个较大的数,避免出现打卡次数为0的情况
+//            Integer doneCount = Optional.ofNullable(punchInMultiTask.getPunchInDoneCount()).orElse(punchInTaskHistoryList.size() + 1);
+//            return punchInDoneCount >= doneCount ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
+//        }
+//
+//        // 比率,当天打卡完成比率大于等于设置的比率,则完成打卡
+//
+//        if (PunchInMethodMultiEnum.RATE.equals(punchInMultiTask.getPunchInMethod())) {
+//            // 没有设置时默认设置一个较大的数,避免出现打卡完成率为0的情况
+//            BigDecimal doneRate = Optional.ofNullable(punchInMultiTask.getPunchInDoneRate()).orElse(DEFAULT_PUNCH_IN_DONE_RATE);
+//            BigDecimal punchInDoneRate = BigDecimal.valueOf(punchInDoneCount).divide(BigDecimal.valueOf(multiTaskNum), 2, RoundingMode.HALF_UP);
+//            return punchInDoneRate.compareTo(doneRate) > -1 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
+//        }
+
+        return PunchInStatusEnum.UNDONE;
+    }
+
+    @Override
+    public int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExts, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus) {
+        // 未完成打卡,积分为0
+        if (PunchInStatusEnum.UNDONE.equals(punchInTaskHistory.getPunchInStatus())) {
+            return 0;
+        }
+
+        // 单次打卡中使用的拓展信息
+        List<PunchInTaskExt> punchInExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        // 打卡任务使用的拓展信息
+        List<PunchInTaskExt> punchInTaskExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
+
+        // 单个任务积分=基本积分+(可选)额外积分+(可选)连续完成额外积分+法定节假日(含周末)双倍奖励+全勤双倍奖励
+        // 基本积分
+        int basicPoints = Optional.ofNullable(punchInTask.getPoints()).orElse(0);
+
+        // 额外积分
+        basicPoints += calculateExtraPointsInTask(punchInTask, punchInExtList, punchInTaskHistory, calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate()));
+
+        // 启用了全勤奖励
+        int fullAttendancePoints = 0;
+        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getFullAttendanceStatus())) {
+            LocalDate punchInDate = LocalDate.parse(punchInTaskHistory.getPunchInDate());
+            // 结算周期:周,并且结算日是周末,则双倍奖励;
+            if (FullAttendancePeriodEnum.WEEK.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.getDayOfWeek().getValue() == 7) {
+                int undoneCount = punchInStatsWeek.getPunchInTotalCount() - punchInStatsWeek.getPunchInDoneCount();
+                if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
+                    fullAttendancePoints = basicPoints;
+                }
+            }
+
+            // 结算周期:月,结算日是当月最后一天,则双倍奖励;
+            if (FullAttendancePeriodEnum.MONTH.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.lengthOfMonth() == punchInDate.getDayOfMonth()) {
+                int undoneCount = punchInStatsMonth.getPunchInTotalCount() - punchInStatsMonth.getPunchInDoneCount();
+                if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
+                    fullAttendancePoints = basicPoints;
+                }
+            }
+        }
+
+        // 启用了法定节假日(含周末)双倍奖励
+        int holidayPoints = 0;
+        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getHolidayStatus()) && calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())) {
+            holidayPoints = basicPoints;
+        }
+
+        // 连续完成额外积分
+        int taskPoints = 0;
+        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
+            // punchInTaskExtList 根据InitialValue倒序排列
+            punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed());
+            // 第二轮标志
+            boolean secondRound = false;
+            // 上一轮的值
+            int prevInitialValue = 0;
+            for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
+                // 比较结果
+                int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
+                // 如果连续日期小于initialValue,则跳过
+                if (compareValue == -1) {
+                    continue;
+                }
+                // 额外奖励数
+                int extraCount = 0;
+                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+                if (secondRound) {
+                    extraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+                } else if (compareValue >= 0) {
+                    extraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
+                    secondRound = true;
+                }
+                taskPoints +=  punchInTaskExt.getExtraPoints() * extraCount;
+                prevInitialValue = punchInTaskExt.getInitialValue();
+            }
+        }
+
+        return basicPoints + fullAttendancePoints + holidayPoints + taskPoints;
+    }
+
+    /**
+     * 计算额外积分
+     * @param punchInTask
+     * @param punchInTaskExtList
+     * @return
+     */
+    private int calculateExtraPointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
+        // 单次打卡或者没有启用额外积分计算则跳过
+        if (PunchInExtraMethodEnum.NONE.equals(punchInTask.getExtraMethod())
+            || PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
+            return 0;
+        }
+
+        // 计算超出部分,
+        int extraCountTrack = 0;
+        // 计数打卡
+        if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
+            extraCountTrack = punchInTaskHistory.getCountTrack() - (holidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack());
+        }
+        // 计时打卡
+        if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) {
+            // 超出的时间按照时间间隔转换成次数
+            long timeTrack = punchInTaskHistory.getTimeTrack().until((holidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack()), ChronoUnit.MINUTES);
+            extraCountTrack = BigDecimal.valueOf(timeTrack).divide(BigDecimal.valueOf(punchInTask.getExtraTimeStep()), 0, RoundingMode.FLOOR).intValue();
+        }
+
+        // 如果等于0则没有超出部分,不用计算
+        if (extraCountTrack == 0) {
+            return 0;
+        }
+
+        // 固定计算
+        if (PunchInExtraMethodEnum.FIXED.equals(punchInTask.getExtraMethod())) {
+            Integer extraPoints = Optional.ofNullable(punchInTask.getExtraPoints()).orElse(0);
+            return extraCountTrack * extraPoints;
+        }
+
+        // 区间计算
+        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInTaskExtList)) {
+            int basicPoints = 0;
+            int prevInitialValue = 0;
+            for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
+                Integer initialValue = Optional.ofNullable(punchInTaskExt.getInitialValue()).orElse(0);
+                // 第一轮:initialValue - extraCountTrack的值v1
+                // 如果v1≤0则超出initialValue范围,使用initialValue计算;
+                // 如果v1>0则在initialValue范围内 使用extraCountTrack
+                // 后续轮:本轮initialValue - extraCountTrack的值v1、
+                // 如果v1≤0则超出本轮initialValue范围,使用上一轮initialValue-本轮initialValue的值v2计算;
+                // 如果v1>0则在本轮initialValue范围内 使用extraCountTrack-上一轮initialValue的值v3计算
+                // 下面的代码不适合第一轮的计算
+                if (initialValue - extraCountTrack <= 0) {
+                    basicPoints = punchInTaskExt.getExtraPoints() * Math.abs(prevInitialValue - initialValue);
+                } else {
+                    basicPoints = punchInTaskExt.getExtraPoints() * (extraCountTrack - prevInitialValue);
+                }
+                prevInitialValue = initialValue;
+            }
+            return basicPoints;
+        }
+
+        return 0;
+    }
+
+    @Override
+    public int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus) {
+        // 未完成打卡或没有启用多任务积分计算,积分为0
+        if (PunchInStatusEnum.UNDONE.equals(punchInMultiTaskHistory.getPunchInStatus())
+            || CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus())) {
+            return 0;
+        }
+
+        // 单次打卡中使用的拓展信息
+        List<PunchInMultiTaskExt> punchInExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        // 打卡任务使用的拓展信息
+        List<PunchInMultiTaskExt> punchInTaskExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
+
+        // 多个任务积分=多任务基本积分+多任务额外积分+连续完成额外积分
+        // 基本积分
+        int basicPoints = punchInMultiTask.getPoints();
+
+        // 额外积分
+        int extraPoints = 0;
+        // 额外次数
+        int extractCount = punchInMultiTaskHistory.getPunchInDoneCount() - punchInMultiTask.getPunchInDoneCount();
+        // 固定计算
+        if (PunchInExtraMethodEnum.FIXED.equals(punchInMultiTask.getExtraMethod())) {
+            extraPoints = extractCount * punchInMultiTask.getExtraPoints();
+        }
+        // 区间计算
+        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInMultiTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInMultiTaskExts)) {
+            // punchInTaskExtList 根据InitialValue倒序排列
+            punchInExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
+            // 第二轮标志
+            boolean secondRound = false;
+            // 上一轮的值
+            int prevInitialValue = 0;
+            for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
+                // 比较结果
+                int compareValue = Integer.compare(extractCount, punchInTaskExt.getInitialValue());
+                // 如果连续日期小于initialValue,则跳过
+                if (compareValue == -1) {
+                    continue;
+                }
+                // 额外奖励数
+                int tempExtraCount = 0;
+                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+                if (secondRound) {
+                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+                } else if (compareValue >= 0) {
+                    tempExtraCount = extractCount - punchInTaskExt.getInitialValue() + 1;
+                    secondRound = true;
+                }
+                extraPoints +=  punchInTaskExt.getExtraPoints() * tempExtraCount;
+                prevInitialValue = punchInTaskExt.getInitialValue();
+            }
+        }
+
+        // 连续完成额外积分
+        int taskPoints = 0;
+        if (CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
+            // punchInTaskExtList 根据InitialValue倒序排列
+            punchInTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
+            // 第二轮标志
+            boolean secondRound = false;
+            // 上一轮的值
+            int prevInitialValue = 0;
+            for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
+                // 比较结果
+                int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
+                // 如果连续日期小于initialValue,则跳过
+                if (compareValue == -1) {
+                    continue;
+                }
+                // 额外奖励数
+                int tempExtraCount = 0;
+                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+                if (secondRound) {
+                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+                } else if (compareValue >= 0) {
+                    tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
+                    secondRound = true;
+                }
+                taskPoints +=  punchInTaskExt.getExtraPoints() * tempExtraCount;
+                prevInitialValue = punchInTaskExt.getInitialValue();
+            }
+        }
+
+        return basicPoints + extraPoints + taskPoints;
+    }
+}

+ 1 - 1
src/main/java/com/punchsettle/server/service/manager/impl/RewardManagerImpl.java → src/main/java/com/punchsettle/server/service/manager/impl/RewardManagerV1Impl.java

@@ -29,7 +29,7 @@ import com.punchsettle.server.utiis.UserUtils;
  * @date 2024/11/25 20:59
  */
 @Service
-public class RewardManagerImpl implements IRewardManager {
+public class RewardManagerV1Impl implements IRewardManager {
 
     @Autowired
     private IUserClaimRewardRecordService userClaimRewardRecordService;

+ 318 - 397
src/main/java/com/punchsettle/server/service/manager/impl/SettleManagerImpl.java

@@ -1,484 +1,405 @@
 package com.punchsettle.server.service.manager.impl;
 
-import java.sql.Timestamp;
-import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import org.springframework.beans.BeanUtils;
+import com.punchsettle.server.atomic.entity.PunchInMultiTask;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
+import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
+import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
+import com.punchsettle.server.atomic.entity.PunchInStatus;
+import com.punchsettle.server.atomic.entity.PunchInTask;
+import com.punchsettle.server.atomic.entity.PunchInTaskExt;
+import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
+import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
+import com.punchsettle.server.constant.ConsecutiveStatusEnum;
+import com.punchsettle.server.constant.FullAttendancePeriodEnum;
+import com.punchsettle.server.constant.PunchInDimensionEnum;
+import com.punchsettle.server.constant.PunchInExtraMethodEnum;
+import com.punchsettle.server.constant.PunchInMethodEnum;
+import com.punchsettle.server.constant.PunchInStatusEnum;
+import com.punchsettle.server.service.manager.ICalendarManager;
+import com.punchsettle.server.service.manager.ISettleManager;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
-import com.punchsettle.server.atomic.entity.PunchIn;
-import com.punchsettle.server.atomic.entity.PunchInRecord;
-import com.punchsettle.server.atomic.entity.PunchInRecordSettlementRela;
-import com.punchsettle.server.atomic.entity.PunchInSettlement;
-import com.punchsettle.server.atomic.entity.SettlementTask;
-import com.punchsettle.server.atomic.entity.User;
-import com.punchsettle.server.atomic.service.IPunchInRecordService;
-import com.punchsettle.server.atomic.service.IPunchInRecordSettlementRelaService;
-import com.punchsettle.server.atomic.service.IPunchInService;
-import com.punchsettle.server.atomic.service.IPunchInSettlementService;
-import com.punchsettle.server.atomic.service.ISettlementTaskService;
-import com.punchsettle.server.atomic.service.IUserService;
-import com.punchsettle.server.common.exception.BusinessException;
-import com.punchsettle.server.common.utils.Assert;
-import com.punchsettle.server.constant.PunchInCategoryEnum;
-import com.punchsettle.server.constant.PunchInRuleEnum;
-import com.punchsettle.server.constant.PunchInSettleTypeEnum;
-import com.punchsettle.server.constant.PunchInStatusV1Enum;
-import com.punchsettle.server.pojo.punchin.PunchInQuery;
-import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
-import com.punchsettle.server.pojo.settle.SettleInfoDto;
-import com.punchsettle.server.pojo.settle.SettleQuery;
-import com.punchsettle.server.pojo.settle.SettleRequest;
-import com.punchsettle.server.pojo.settle.SettleResultDto;
-import com.punchsettle.server.pojo.settle.SettleVO;
-import com.punchsettle.server.service.manager.ISettleManager;
-import com.punchsettle.server.utiis.DateUtils;
-import com.punchsettle.server.utiis.SpringUtils;
-
-import lombok.extern.slf4j.Slf4j;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * @author tyuio
  * @version 1.0.0
+ * @date 2025/4/15 14:25
  * @description 结算服务类
- * @date 2024/12/12 22:29
  */
 @Slf4j
 @Service
 public class SettleManagerImpl implements ISettleManager {
 
     @Autowired
-    private IPunchInService punchInService;
-
-    @Autowired
-    private IPunchInRecordService punchInRecordService;
-
-    @Autowired
-    private IUserService userService;
-
-    @Autowired
-    private IPunchInSettlementService punchInSettlementService;
-
-    @Autowired
-    private ISettlementTaskService settlementTaskService;
-
-    @Autowired
-    private IPunchInRecordSettlementRelaService punchInRecordSettlementRelaService;
+    private ICalendarManager calendarManager;
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void settleHandler(PunchInSettleTypeEnum settleType, LocalDate settleDate, List<Long> userIds, List<Long> punchInIds) {
-        if (Objects.isNull(settleType)) {
-            log.info("结算任务异常,原因:没有指定结算类型");
-            return;
+    public int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList,
+        PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth,
+        PunchInStatus punchInStatus) {
+        // 未完成打卡,积分为0
+        if (PunchInStatusEnum.UNDONE.equals(punchInTaskHistory.getPunchInStatus())) {
+            return 0;
         }
 
-        if (Objects.isNull(settleDate)) {
-            log.info("结算任务异常,原因:没有指定结算日期");
-            return;
-        }
+        // 节假日标识
+        var holidayFlag = calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate());
 
-        if (settleDate.isAfter(LocalDate.now())) {
-            BusinessException.throwFail(String.format("结算任务异常,原因:结算日期{} 不能大于等于今天", settleDate));
-        }
+        // 单个任务积分=基本积分+额外积分+法定节假日(含周末)双倍奖励+全勤双倍奖励+连续完成额外积分
+        // 基本积分
+        int basicPoints = Optional.ofNullable(punchInTask.getPoints()).orElse(0);
 
-        log.info("结算任务开始,结算类型:{}, 结算日期:{}", settleType.getName(), settleDate);
-        Timestamp settleStartTime = new Timestamp(System.currentTimeMillis());
-
-        SettleInfoDto settleInfo = new SettleInfoDto(settleType, settleDate);
-
-        // 读取用户数据,如果userIds为空则结算所有的用户
-        userIds = Optional.ofNullable(userIds).orElseGet(() -> {
-            PunchInRecordQuery recordQuery = new PunchInRecordQuery();
-            recordQuery.setStartDate(settleInfo.getSettleDateStr());
-            recordQuery.setEndDate(settleInfo.getSettleDateStr());
-            List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
-            return punchInRecords.stream().map(PunchInRecord::getCreatedBy).collect(Collectors.toList());
-        });
-        List<User> users = userService.listByIds(userIds);
-        if (CollectionUtils.isEmpty(users)) {
-            log.info("结算任务结束,原因:没有找到待结算的用户信息");
-            return;
-        }
+        // 额外积分计算
+        List<PunchInTaskExt> punchInOneExtList = punchInTaskExtList.stream()
+            .filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        basicPoints += calculateExtraPointsInTask(punchInTask, punchInOneExtList, punchInTaskHistory, holidayFlag);
 
-        // 读取用户的打卡任务,如果punchIds为空则结算用户所有的打卡任务
-        PunchInQuery punchInQuery = new PunchInQuery();
-        punchInQuery.setUserIds(userIds);
-        if (!CollectionUtils.isEmpty(punchInIds)) {
-            punchInQuery.setPunchInIds(punchInIds);
-        }
-        List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
-        if (CollectionUtils.isEmpty(punchIns)) {
-            log.info("结算任务结束,原因:没有找到打卡任务");
-            return;
-        }
-        // 获取最新的打卡任务ID
-        punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
-
-        // 获取结算日的打卡记录
-        PunchInRecordQuery recordQuery = new PunchInRecordQuery();
-        recordQuery.setStartDate(settleInfo.getSettleDateStr());
-        recordQuery.setEndDate(settleInfo.getSettleDateStr());
-        recordQuery.setPunchInIds(punchInIds);
-        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
-        if (CollectionUtils.isEmpty(punchInRecords) && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
-            log.info("结算任务结束,原因:没有打卡记录");
-            return;
+        // 全勤积分,全勤则双倍奖励
+        int fullAttendancePoints = 0;
+        if (judgeFullAttendanceStatusInTask(punchInTask, punchInTaskHistory, punchInStatsWeek, punchInStatsMonth)) {
+            fullAttendancePoints = basicPoints;
         }
 
-        // 获取一周的打卡记录
-        Map<Long, List<PunchInRecord>> weeklyPunchInRecords = Map.of();
-        if (settleInfo.getSundayFlag() || PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
-            PunchInRecordQuery weeklyRecordQuery = new PunchInRecordQuery();
-            weeklyRecordQuery.setPunchInIds(punchInIds);
-            weeklyRecordQuery.setStartDate(DateUtils.getLastWeekMonday(settleInfo.getSettleDate()).toString());
-            weeklyRecordQuery.setEndDate(DateUtils.getLastWeekSunday(settleInfo.getSettleDate()).toString());
-            List<PunchInRecord> punchInRecordForWeeks = punchInRecordService.listByCondition(weeklyRecordQuery);
-            if (!CollectionUtils.isEmpty(punchInRecordForWeeks)) {
-                weeklyPunchInRecords = punchInRecordForWeeks.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
-            }
+        // 法定节假日积分, 法定节假日(含周末)双倍奖励
+        int holidayPoints = 0;
+        if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getHolidayStatus())
+            && calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())) {
+            holidayPoints = basicPoints;
         }
 
-        // 用户-打卡任务分组
-        Map<Long, List<PunchIn>> userPunchInMap = punchIns.stream().collect(Collectors.groupingBy(PunchIn::getCreatedBy));
-        // 打卡任务-打卡记录 map
-        Map<Long, PunchInRecord> punchInRecordMap = punchInRecords.stream().collect(Collectors.toMap(PunchInRecord::getPunchInId, Function.identity(), (key1, key2) -> key1));
-
-        // 先创建结算任务执行记录
-        SettlementTask settlementTask = new SettlementTask();
-        settlementTask.setSettleDate(settleDate.toString());
-        settlementTask.setStartTime(settleStartTime);
-        settlementTask.setProcessedNum(users.size());
-        settlementTaskService.insert(settlementTask);
-
-        // 结算
-        List<SettleResultDto> settleResultDtoList = new ArrayList<>();
-        for (User user : users) {
-            SettleResultDto settleResult = settle(settleInfo, user, userPunchInMap.get(user.getId()), punchInRecordMap, weeklyPunchInRecords);
-            settleResultDtoList.add(settleResult);
-        }
+        // 打卡任务使用的拓展信息
+        List<PunchInTaskExt> punchInMultiExtList = punchInTaskExtList.stream()
+            .filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        // 连续完成额外积分
+        int taskPoints = calculateConsecutivePointsInTask(punchInTask, punchInMultiExtList, punchInStatus);
 
-        // 更新数据
-        // 待更新的用户
-        List<User> updateUsers = new ArrayList<>();
-        // 待更新的打卡记录
-        List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
-        // 待新增的打卡记录
-        List<PunchInRecord> addPunchInRecords = new ArrayList<>();
-        // 待新增的打卡结算信息
-        List<PunchInSettlement> addPunchInSettlements = new ArrayList<>();
-
-        for (SettleResultDto settleResult : settleResultDtoList) {
-            updateUsers.add(settleResult.getUpdateUser());
-            addPunchInRecords.addAll(settleResult.getAddPunchInRecords());
-            updatePunchInRecords.addAll(settleResult.getUpdatePunchInRecords());
-            addPunchInSettlements.add(settleResult.getAddPunchInSettlement());
-        }
+        return basicPoints + fullAttendancePoints + holidayPoints + taskPoints;
+    }
 
-        // 更新用户奖励信息
-        if (!CollectionUtils.isEmpty(updateUsers)) {
-            userService.batchUpdateUser(updateUsers);
+    /**
+     * 计算额外积分
+     * 
+     * @param punchInTask
+     * @param punchInTaskExtList
+     * @return
+     */
+    private int calculateExtraPointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList,
+        PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
+        // 单次打卡或者没有启用额外积分计算则跳过
+        if (PunchInExtraMethodEnum.NONE.equals(punchInTask.getExtraMethod())
+            || PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
+            return 0;
         }
 
-        // 新增打卡记录
-        if (!CollectionUtils.isEmpty(addPunchInRecords)) {
-            punchInRecordService.batchInsert(addPunchInRecords);
-        }
+        // 计算超出部分,获取额外次数
+        int extraCount = getExtraCountInTask(punchInTask, punchInTaskHistory, holidayFlag);
 
-        // 更新已有的打卡记录
-        if (!CollectionUtils.isEmpty(updatePunchInRecords)) {
-            punchInRecordService.batchUpdate(updatePunchInRecords);
+        // 如果等于0则没有超出部分,不用计算
+        if (extraCount == 0) {
+            return 0;
         }
 
-        // 新增结算信息
-        if (!CollectionUtils.isEmpty(addPunchInSettlements)) {
-            // 补充本次的计算任务ID
-            addPunchInSettlements.stream().forEach(v -> v.setSettlementTaskId(settlementTask.getId()));
-            punchInSettlementService.batchInsert(addPunchInSettlements);
+        // 固定计算
+        if (PunchInExtraMethodEnum.FIXED.equals(punchInTask.getExtraMethod())) {
+            Integer extraPoints = Optional.ofNullable(punchInTask.getExtraPoints()).orElse(0);
+            return extraCount * extraPoints;
         }
 
-        // 新增关联信息
-        if (!CollectionUtils.isEmpty(settleResultDtoList)) {
-            List<PunchInRecordSettlementRela> relaList = buildPunchInRecordSettlementRelaList(settleResultDtoList);
-            if (!CollectionUtils.isEmpty(relaList)) {
-                punchInRecordSettlementRelaService.batchInsert(relaList);
+        // 区间计算
+        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInTask.getExtraMethod())
+            && !CollectionUtils.isEmpty(punchInTaskExtList)) {
+            // 任务积分
+            int extraPoints = 0;
+            // 第二轮标志
+            boolean secondRound = false;
+            // 上一轮的值
+            int prevInitialValue = 0;
+            // 拓展信息 根据InitialValue倒序排列
+            punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed());
+            // 使用区间计算的方式
+            for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
+                // 比较结果
+                int compareValue = Integer.compare(extraCount, punchInTaskExt.getInitialValue());
+                // 如果连续日期小于initialValue,则跳过
+                if (compareValue == -1) {
+                    continue;
+                }
+                // 额外奖励数
+                int tempExtraCount = 0;
+                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+                if (secondRound) {
+                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+                } else if (compareValue >= 0) {
+                    tempExtraCount = extraCount - punchInTaskExt.getInitialValue() + 1;
+                    secondRound = true;
+                }
+                // 计算 任务积分
+                extraPoints += punchInTaskExt.getExtraPoints() * tempExtraCount;
+                // 记录本轮的initialValue
+                prevInitialValue = punchInTaskExt.getInitialValue();
             }
-        }
 
-        // 构造并新增结算任务信息
-        settlementTask.setProcessedSettleNum(addPunchInSettlements.size());
-        settlementTask.setProcessedUnsettleNum(settlementTask.getProcessedNum() - settlementTask.getProcessedSettleNum());
-        settlementTask.setEndTime(new Timestamp(System.currentTimeMillis()));
-        settlementTaskService.update(settlementTask);
+            return extraPoints;
+        }
 
-        log.info("结算任务结束");
+        return 0;
     }
 
     /**
-     * 结算
-     * @param settleInfo 结算基本信息
-     * @param user 待结算的用户
-     * @param punchIns 打卡任务
-     * @param punchInRecordMap 打卡任务-打卡记录 map
-     * @param weeklyPunchInRecordMap 打卡任务-一周打卡记录 map
+     * 获取额外次数
+     * 
+     * @param punchInTask 打卡任务
+     * @param punchInTaskHistory 打卡记录
+     * @param holidayFlag 是否是法定节假日 true-是,false-不是
      * @return
      */
-    private SettleResultDto settle(SettleInfoDto settleInfo, User user, List<PunchIn> punchIns, Map<Long, PunchInRecord> punchInRecordMap, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
-        // 结算奖励数
-        int settleRewardNum = 0;
-        // 待更新的打卡记录
-        List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
-        // 待新增的打卡记录
-        List<PunchInRecord> addPunchInRecords = new ArrayList<>();
-        // 结算
-        for (PunchIn punchIn : punchIns) {
-            // 获取打卡记录
-            PunchInRecord punchInRecord = punchInRecordMap.get(punchIn.getId());
-            // 不是补打卡且不存在打卡记录直接跳过,无须结算和更新记录状态
-            if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
-                continue;
-            }
-            // 判断是否满足打卡规则
-            PunchInStatusV1Enum punchInStatus = judgePunchInStatus(punchIn, punchInRecord);
-            // 不是补卡或打卡任务未完成,则跳过
-            if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusV1Enum.UN_FINISH.equals(punchInStatus)) {
-                PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
-                updatePunchInRecord.setId(punchInRecord.getId());
-                updatePunchInRecord.setPunchInStatus(punchInStatus);
-                updatePunchInRecords.add(updatePunchInRecord);
-                continue;
-            }
-
-            // 补打卡,完全没有打卡记录则需要补充打卡记录
-            if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
-                punchInRecord = buildPunchInRecordForSettle(punchIn);
-                punchInRecord.setPunchInId(punchIn.getId());
-                punchInRecord.setPunchInDate(settleInfo.getSettleDateStr());
-                punchInRecord.setPunchInStatus(PunchInStatusV1Enum.REMAKE_FINISH);
-                addPunchInRecords.add(punchInRecord);
-            }
-
-            // 补打卡,已有打卡记录但是不满足打卡规则,则需要对打卡记录做准备
-            if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusV1Enum.UN_FINISH.equals(punchInStatus)) {
-                fillTrack(punchIn, punchInRecord);
-            }
-
-            // 不是补卡,且打卡状态为完成,需要更新打卡状态为完成
-            if (Objects.isNull(punchInRecord.getPunchInStatus()) || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) {
-                PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
-                updatePunchInRecord.setId(punchInRecord.getId());
-                updatePunchInRecord.setPunchInStatus(punchInStatus);
-                updatePunchInRecords.add(updatePunchInRecord);
-            }
-
-            // 周末双倍奖励,否则计算普通奖励
-            settleRewardNum += settleInfo.getWeekendFlag() && punchIn.getWeekendDoubleFlag() ? punchIn.getRewardNum() * 2 : punchIn.getRewardNum();
-            // 计算全勤双倍奖励
-            if (judgeFullAttendance(settleInfo, punchIn, weeklyPunchInRecordMap)) {
-                settleRewardNum += punchIn.getRewardNum() * 2;
-            }
+    private int getExtraCountInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory,
+        boolean holidayFlag) {
+        int extraCount = 0;
+        // 计数打卡
+        if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
+            extraCount = punchInTaskHistory.getCountTrack()
+                - (holidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack());
         }
-
-        // 计算结算前后,用户奖励数的变化
-//        int beforeSettleRewardNum = user.getUnclaimedRewardNum();
-        int beforeSettleRewardNum = 0;
-        int afterSettleRewardNum = beforeSettleRewardNum + settleRewardNum;
-//        int totalRewardNum = user.getTotalRewardNum() + settleRewardNum;
-        int totalRewardNum = 0;
-
-        // 构造结算信息
-        PunchInSettlement addPunchInSettlement = new PunchInSettlement();
-        addPunchInSettlement.setUserId(user.getId());
-        addPunchInSettlement.setSettleRewardNum(settleRewardNum);
-        addPunchInSettlement.setSettlementTime(new Timestamp(System.currentTimeMillis()));
-        addPunchInSettlement.setBeforeSettleRewardNum(beforeSettleRewardNum);
-        addPunchInSettlement.setAfterSettleRewardNum(afterSettleRewardNum);
-
-        // 构造用户奖励信息
-        User updateUser = new User();
-        updateUser.setId(user.getId());
-//        updateUser.setTotalRewardNum(totalRewardNum);
-//        updateUser.setUnclaimedRewardNum(afterSettleRewardNum);
-
-        SettleResultDto settleResultDto = new SettleResultDto();
-        settleResultDto.setUpdateUser(updateUser);
-        settleResultDto.setAddPunchInRecords(addPunchInRecords);
-        settleResultDto.setUpdatePunchInRecords(updatePunchInRecords);
-        settleResultDto.setAddPunchInSettlement(addPunchInSettlement);
-        return settleResultDto;
+        // 计时打卡
+        if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) {
+            // 超出的时间按照时间间隔转换成次数
+            long timeTrack = punchInTaskHistory.getTimeTrack().until(
+                (holidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack()), ChronoUnit.MINUTES);
+            extraCount = BigDecimal.valueOf(timeTrack)
+                .divide(BigDecimal.valueOf(punchInTask.getExtraTimeStep()), 0, RoundingMode.FLOOR).intValue();
+        }
+        return extraCount;
     }
 
-    @Override
-    public PunchInStatusV1Enum judgePunchInStatus(PunchIn punchIn, PunchInRecord punchInRecord) {
-        // 没有打卡记录,直接没完成,包含单次打卡的情况无需额外判断
-        if (Objects.isNull(punchInRecord)) {
-            return PunchInStatusV1Enum.UN_FINISH;
+    /**
+     * 判断全勤状态
+     * 
+     * @return true-是全勤,false-不是全勤
+     */
+    private boolean judgeFullAttendanceStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory,
+        PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth) {
+        // 没有启用全勤 则直接返回
+        if (CommonEnableStatusEnum.DISABLED.equals(punchInTask.getFullAttendanceStatus())) {
+            return false;
         }
 
-        // 单次打卡
-        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())) {
-            return PunchInStatusV1Enum.FINISH;
-        }
+        // 打卡日期/结算日期
+        LocalDate punchInDate = LocalDate.parse(punchInTaskHistory.getPunchInDate());
 
-        // 计数打卡
-        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
-            Integer recordCountTrack = Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0);
-            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == -1) {
-                return PunchInStatusV1Enum.UN_FINISH;
-            }
-            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == 1) {
-                return PunchInStatusV1Enum.UN_FINISH;
-            }
+        // 未完成数,默认设置一个较大值,以防判断有误
+        int undoneCount = punchInTask.getFullAttendanceFaultToleranceCnt() + 1;
 
-            return PunchInStatusV1Enum.FINISH;
+        // 结算周期:周,并且结算日是周末;结算周期:月,结算日是当月最后一天;
+        if (FullAttendancePeriodEnum.WEEK.equals(punchInTask.getFullAttendancePeriod())
+            && punchInDate.getDayOfWeek().getValue() == 7) {
+            undoneCount = punchInStatsWeek.getPunchInTotalCount() - punchInStatsWeek.getPunchInDoneCount();
+        } else if (FullAttendancePeriodEnum.MONTH.equals(punchInTask.getFullAttendancePeriod())
+            && punchInDate.lengthOfMonth() == punchInDate.getDayOfMonth()) {
+            undoneCount = punchInStatsMonth.getPunchInTotalCount() - punchInStatsMonth.getPunchInDoneCount();
         }
 
-        // 计时打卡
-        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
-            LocalTime recordTimeTrack = Optional.ofNullable(punchInRecord.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
-            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == -1) {
-                return PunchInStatusV1Enum.UN_FINISH;
-            }
-            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == 1) {
-                return PunchInStatusV1Enum.UN_FINISH;
-            }
-
-            return PunchInStatusV1Enum.FINISH;
-        }
-
-        return PunchInStatusV1Enum.UN_FINISH;
+        // 未完成数在容错范围内,则是全勤
+        return undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt();
     }
 
     /**
-     * 填充打卡记录的记录信息(不用考虑单次打卡的情况)
-     * @param punchIn
-     * @param punchInRecord
+     * 计算连续积分
+     * 
+     * @param punchInTask 打卡任务
+     * @param punchInTaskExtList 打卡任务拓展信息
+     * @param punchInStatus 打卡状态
+     * @return
      */
-    private void fillTrack(PunchIn punchIn, PunchInRecord punchInRecord) {
-        // 计数打卡
-        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
-            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
-                punchInRecord.setCountTrack(punchIn.getCountTrack() + 1);
-            }
-            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
-                punchInRecord.setCountTrack(punchIn.getCountTrack() - 1);
-            }
+    private int calculateConsecutivePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList,
+        PunchInStatus punchInStatus) {
+        // 没有启用任务积分计算、或是连续打卡状态为中断、或是没有拓展信息 则不进行计算
+        if (CommonEnableStatusEnum.DISABLED.equals(punchInTask.getTaskPointsStatus())
+            || ConsecutiveStatusEnum.INTERRUPTED.equals(punchInStatus.getConsecutiveStatus())
+            || CollectionUtils.isEmpty(punchInTaskExtList)) {
+            return 0;
         }
 
-        // 计时打卡
-        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
-            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
-                punchInRecord.setTimeTrack(punchIn.getTimeTrack().plusSeconds(1));
+        // 任务积分
+        int taskPoints = 0;
+        // 第二轮标志
+        boolean secondRound = false;
+        // 上一轮的值
+        int prevInitialValue = 0;
+        // 拓展信息 根据InitialValue倒序排列
+        punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed());
+        // 使用区间计算的方式
+        for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
+            // 比较结果
+            int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
+            // 如果连续日期小于initialValue,则跳过
+            if (compareValue == -1) {
+                continue;
             }
-            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
-                punchInRecord.setTimeTrack(punchIn.getTimeTrack().minusSeconds(1));
+            // 额外奖励数
+            int tempExtraCount = 0;
+            // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+            if (secondRound) {
+                tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+            } else if (compareValue >= 0) {
+                tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
+                secondRound = true;
             }
+            // 计算 任务积分
+            taskPoints += punchInTaskExt.getExtraPoints() * tempExtraCount;
+            // 记录本轮的initialValue
+            prevInitialValue = punchInTaskExt.getInitialValue();
         }
+
+        return taskPoints;
     }
 
-    /**
-     * 判断是否进行全勤结算
-     * @param settleInfo 结算信息
-     * @param punchIn 打卡任务
-     * @param weeklyPunchInRecordMap 一周打卡记录
-     * @return true-
-     */
-    private boolean judgeFullAttendance(SettleInfoDto settleInfo, PunchIn punchIn, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
-        // 没有启用全勤奖励则跳过
-        if (!punchIn.getFullAttendanceFlag()) {
-            return false;
+    @Override
+    public int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask,
+        List<PunchInMultiTaskExt> punchInMultiTaskExtList, PunchInMultiTaskHistory punchInMultiTaskHistory,
+        PunchInStatus punchInStatus) {
+        // 未完成打卡或没有启用多任务积分计算,积分为0
+        if (PunchInStatusEnum.UNDONE.equals(punchInMultiTaskHistory.getPunchInStatus())
+            || CommonEnableStatusEnum.DISABLED.equals(punchInMultiTask.getTaskPointsStatus())) {
+            return 0;
         }
 
-        // 不是周日结算或者补打卡则跳过,
-        if (!settleInfo.getSundayFlag() && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
-            return false;
-        }
+        // 多个任务积分=多任务基本积分+多任务额外积分+连续完成额外积分
+        // 基本积分
+        int basicPoints = punchInMultiTask.getPoints();
+
+        // 额外积分
+        List<PunchInMultiTaskExt> punchInTaskOneExtList = punchInMultiTaskExtList.stream()
+                .filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        int extraPoints = calculateExtraPointsInMultiTask(punchInMultiTask, punchInTaskOneExtList, punchInMultiTaskHistory);
 
-        // 获取一周的完成打卡的打卡记录,并且要排除结算日这天的记录
-        List<PunchInRecord> weeklyFinishRecord = Optional.ofNullable(weeklyPunchInRecordMap.get(punchIn.getId())).orElse(new ArrayList<>())
-                .stream().filter(record -> !settleInfo.getSettleDateStr().equals(record.getPunchInDate()) && (PunchInStatusV1Enum.FINISH.equals(record.getPunchInStatus()) || PunchInStatusV1Enum.REMAKE_FINISH.equals(record.getPunchInStatus())))
-                .collect(Collectors.toList());
+        // 连续完成额外积分
+        List<PunchInMultiTaskExt> punchInTaskMultiExtList = punchInMultiTaskExtList.stream()
+            .filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
+        int taskPoints =
+            calculateConsecutivePointsInMultiTask(punchInMultiTask, punchInTaskMultiExtList, punchInStatus);
 
-        // 1个是容错允许不打卡或未完成打卡,1个是当天的结算打卡来到这段逻辑就认为已经完成打卡,因此只要有5个完成打卡,则认为全勤
-        return weeklyFinishRecord.size() >= 5;
+        return basicPoints + extraPoints + taskPoints;
     }
 
     /**
-     * 构建待更新结算数据的打卡记录
+     * 计算额外积分(多任务)
+     * 
+     * @param punchInMultiTask 多任务打卡信息
+     * @param punchInMultiTaskExtList 多任务打卡拓展信息
+     * @param punchInMultiTaskHistory 多任务打卡历史信息
      * @return
      */
-    private PunchInRecord buildPunchInRecordForSettle(PunchIn punchIn) {
-        PunchInRecord updatePunchInRecord = new PunchInRecord();
-        updatePunchInRecord.setSettleRewardNum(punchIn.getRewardNum());
-        updatePunchInRecord.setSettleCategory(punchIn.getCategory());
-        updatePunchInRecord.setSettleRule(punchIn.getRule());
-        updatePunchInRecord.setSettleCountTrack(punchIn.getCountTrack());
-        updatePunchInRecord.setSettleTimeTrack(punchIn.getTimeTrack());
-        return updatePunchInRecord;
+
+    private int calculateExtraPointsInMultiTask(PunchInMultiTask punchInMultiTask,
+        List<PunchInMultiTaskExt> punchInMultiTaskExtList, PunchInMultiTaskHistory punchInMultiTaskHistory) {
+        // 额外次数
+        int extractCount = punchInMultiTaskHistory.getPunchInDoneCount() - punchInMultiTask.getPunchInDoneCount();
+
+        // 固定计算
+        if (PunchInExtraMethodEnum.FIXED.equals(punchInMultiTask.getExtraMethod())) {
+            return extractCount * punchInMultiTask.getExtraPoints();
+        }
+
+        // 区间计算
+        if (PunchInExtraMethodEnum.INTERVAL.equals(punchInMultiTask.getExtraMethod())
+            && !CollectionUtils.isEmpty(punchInMultiTaskExtList)) {
+            // 额外积分
+            int extraPoints = 0;
+            // 第二轮标志
+            boolean secondRound = false;
+            // 上一轮的值
+            int prevInitialValue = 0;
+            // punchInMultiTaskExtList 根据InitialValue倒序排列
+            punchInMultiTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
+
+            for (PunchInMultiTaskExt punchInTaskExt : punchInMultiTaskExtList) {
+                // 比较结果
+                int compareValue = Integer.compare(extractCount, punchInTaskExt.getInitialValue());
+                // 如果连续日期小于initialValue,则跳过
+                if (compareValue == -1) {
+                    continue;
+                }
+                // 额外奖励数
+                int tempExtraCount = 0;
+                // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+                if (secondRound) {
+                    tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+                } else if (compareValue >= 0) {
+                    tempExtraCount = extractCount - punchInTaskExt.getInitialValue() + 1;
+                    secondRound = true;
+                }
+                // 计算机分
+                extraPoints += punchInTaskExt.getExtraPoints() * tempExtraCount;
+                // 记录本轮的initialValue
+                prevInitialValue = punchInTaskExt.getInitialValue();
+            }
+        }
+
+        return 0;
     }
 
     /**
-     * 构造打卡记录与结算记录的关联关系
-     * @param settleResultDtos
+     * 计算多任务连续打卡积分
+     * 
+     * @param punchInMultiTask 多任务打卡信息
+     * @param punchInMultiTaskExtList 多任务打卡拓展信息
+     * @param punchInStatus 状态信息
      * @return
      */
-    private static List<PunchInRecordSettlementRela> buildPunchInRecordSettlementRelaList(List<SettleResultDto> settleResultDtos) {
-        if (CollectionUtils.isEmpty(settleResultDtos)) {
-            return List.of();
+
+    private int calculateConsecutivePointsInMultiTask(PunchInMultiTask punchInMultiTask,
+        List<PunchInMultiTaskExt> punchInMultiTaskExtList, PunchInStatus punchInStatus) {
+        // 没有启用多任务积分计算、或是连续打卡状态为中断、或是没有拓展信息 则不进行计算
+        if (CommonEnableStatusEnum.DISABLED.equals(punchInMultiTask.getTaskPointsStatus())
+            || ConsecutiveStatusEnum.INTERRUPTED.equals(punchInStatus.getConsecutiveStatus())
+            || CollectionUtils.isEmpty(punchInMultiTaskExtList)) {
+            return 0;
         }
 
-        List<PunchInRecordSettlementRela> relaList = new ArrayList();
-        // 补充关联关系ID(结算记录ID、打卡记录ID)
-        for (SettleResultDto settleResultDto : settleResultDtos) {
-            for (PunchInRecord punchInRecord : settleResultDto.getAddPunchInRecords()) {
-                PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
-                rela.setRecordId(punchInRecord.getId());
-                rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
-                relaList.add(rela);
+        // 积分
+        int points = 0;
+        // 第二轮标志
+        boolean secondRound = false;
+        // 上一轮的值
+        int prevInitialValue = 0;
+        // punchInMultiTaskExtList 根据InitialValue倒序排列
+        punchInMultiTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
+
+        for (PunchInMultiTaskExt punchInTaskExt : punchInMultiTaskExtList) {
+            // 比较结果
+            int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
+            // 如果连续日期小于initialValue,则跳过
+            if (compareValue == -1) {
+                continue;
             }
-
-            for (PunchInRecord punchInRecord : settleResultDto.getUpdatePunchInRecords()) {
-                PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
-                rela.setRecordId(punchInRecord.getId());
-                rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
-                relaList.add(rela);
+            // 额外奖励数
+            int tempExtraCount = 0;
+            // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
+            if (secondRound) {
+                tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
+            } else if (compareValue >= 0) {
+                tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
+                secondRound = true;
             }
+            // 计算积分
+            points += punchInTaskExt.getExtraPoints() * tempExtraCount;
+            // 记录本轮的initialValue
+            prevInitialValue = punchInTaskExt.getInitialValue();
         }
-        return relaList;
-    }
-
-    @Override
-    public void manualSettle(SettleRequest settleRequest) {
-        // TODO 这里考虑加判断条件防止重复手动结算
-        Assert.isNullInBusiness(settleRequest, "结算请求不能为空");
-        if (!PunchInSettleTypeEnum.OPS.equals(settleRequest.getSettleType())) {
-            BusinessException.throwFail("非运维结算,禁止执行");
-        }
-        SpringUtils.getBean(ISettleManager.class).settleHandler(settleRequest.getSettleType(), LocalDate.parse(settleRequest.getSettleDate()), settleRequest.getUserIds(), settleRequest.getPunchInIds());
-    }
 
-    @Override
-    public List<SettleVO> querySettle(SettleQuery query) {
-        SimpleDateFormat sdf = DateUtils.buildDateTimeFormat();
-
-        List<PunchInSettlement> punchInSettlements = punchInSettlementService.listByCondition(query);
-        return punchInSettlements.stream().map(settlement -> {
-            SettleVO settleVO = new SettleVO();
-            BeanUtils.copyProperties(settlement, settleVO);
-            settleVO.setSettlementTime(sdf.format(settlement.getSettlementTime()));
-            return settleVO;
-        }).toList();
+        return points;
     }
 }

+ 484 - 0
src/main/java/com/punchsettle/server/service/manager/impl/SettleManagerV1Impl.java

@@ -0,0 +1,484 @@
+package com.punchsettle.server.service.manager.impl;
+
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import com.punchsettle.server.atomic.entity.PunchIn;
+import com.punchsettle.server.atomic.entity.PunchInRecord;
+import com.punchsettle.server.atomic.entity.PunchInRecordSettlementRela;
+import com.punchsettle.server.atomic.entity.PunchInSettlement;
+import com.punchsettle.server.atomic.entity.SettlementTask;
+import com.punchsettle.server.atomic.entity.User;
+import com.punchsettle.server.atomic.service.IPunchInRecordService;
+import com.punchsettle.server.atomic.service.IPunchInRecordSettlementRelaService;
+import com.punchsettle.server.atomic.service.IPunchInService;
+import com.punchsettle.server.atomic.service.IPunchInSettlementService;
+import com.punchsettle.server.atomic.service.ISettlementTaskService;
+import com.punchsettle.server.atomic.service.IUserService;
+import com.punchsettle.server.common.exception.BusinessException;
+import com.punchsettle.server.common.utils.Assert;
+import com.punchsettle.server.constant.PunchInCategoryEnum;
+import com.punchsettle.server.constant.PunchInRuleEnum;
+import com.punchsettle.server.constant.PunchInSettleTypeEnum;
+import com.punchsettle.server.constant.PunchInStatusV1Enum;
+import com.punchsettle.server.pojo.punchin.PunchInQuery;
+import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
+import com.punchsettle.server.pojo.settle.SettleInfoDto;
+import com.punchsettle.server.pojo.settle.SettleQuery;
+import com.punchsettle.server.pojo.settle.SettleRequest;
+import com.punchsettle.server.pojo.settle.SettleResultDto;
+import com.punchsettle.server.pojo.settle.SettleVO;
+import com.punchsettle.server.service.manager.ISettleManagerV1;
+import com.punchsettle.server.utiis.DateUtils;
+import com.punchsettle.server.utiis.SpringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author tyuio
+ * @version 1.0.0
+ * @description 结算服务类
+ * @date 2024/12/12 22:29
+ */
+@Slf4j
+@Service
+public class SettleManagerV1Impl implements ISettleManagerV1 {
+
+    @Autowired
+    private IPunchInService punchInService;
+
+    @Autowired
+    private IPunchInRecordService punchInRecordService;
+
+    @Autowired
+    private IUserService userService;
+
+    @Autowired
+    private IPunchInSettlementService punchInSettlementService;
+
+    @Autowired
+    private ISettlementTaskService settlementTaskService;
+
+    @Autowired
+    private IPunchInRecordSettlementRelaService punchInRecordSettlementRelaService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void settleHandler(PunchInSettleTypeEnum settleType, LocalDate settleDate, List<Long> userIds, List<Long> punchInIds) {
+        if (Objects.isNull(settleType)) {
+            log.info("结算任务异常,原因:没有指定结算类型");
+            return;
+        }
+
+        if (Objects.isNull(settleDate)) {
+            log.info("结算任务异常,原因:没有指定结算日期");
+            return;
+        }
+
+        if (settleDate.isAfter(LocalDate.now())) {
+            BusinessException.throwFail(String.format("结算任务异常,原因:结算日期{} 不能大于等于今天", settleDate));
+        }
+
+        log.info("结算任务开始,结算类型:{}, 结算日期:{}", settleType.getName(), settleDate);
+        Timestamp settleStartTime = new Timestamp(System.currentTimeMillis());
+
+        SettleInfoDto settleInfo = new SettleInfoDto(settleType, settleDate);
+
+        // 读取用户数据,如果userIds为空则结算所有的用户
+        userIds = Optional.ofNullable(userIds).orElseGet(() -> {
+            PunchInRecordQuery recordQuery = new PunchInRecordQuery();
+            recordQuery.setStartDate(settleInfo.getSettleDateStr());
+            recordQuery.setEndDate(settleInfo.getSettleDateStr());
+            List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
+            return punchInRecords.stream().map(PunchInRecord::getCreatedBy).collect(Collectors.toList());
+        });
+        List<User> users = userService.listByIds(userIds);
+        if (CollectionUtils.isEmpty(users)) {
+            log.info("结算任务结束,原因:没有找到待结算的用户信息");
+            return;
+        }
+
+        // 读取用户的打卡任务,如果punchIds为空则结算用户所有的打卡任务
+        PunchInQuery punchInQuery = new PunchInQuery();
+        punchInQuery.setUserIds(userIds);
+        if (!CollectionUtils.isEmpty(punchInIds)) {
+            punchInQuery.setPunchInIds(punchInIds);
+        }
+        List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
+        if (CollectionUtils.isEmpty(punchIns)) {
+            log.info("结算任务结束,原因:没有找到打卡任务");
+            return;
+        }
+        // 获取最新的打卡任务ID
+        punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
+
+        // 获取结算日的打卡记录
+        PunchInRecordQuery recordQuery = new PunchInRecordQuery();
+        recordQuery.setStartDate(settleInfo.getSettleDateStr());
+        recordQuery.setEndDate(settleInfo.getSettleDateStr());
+        recordQuery.setPunchInIds(punchInIds);
+        List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
+        if (CollectionUtils.isEmpty(punchInRecords) && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
+            log.info("结算任务结束,原因:没有打卡记录");
+            return;
+        }
+
+        // 获取一周的打卡记录
+        Map<Long, List<PunchInRecord>> weeklyPunchInRecords = Map.of();
+        if (settleInfo.getSundayFlag() || PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
+            PunchInRecordQuery weeklyRecordQuery = new PunchInRecordQuery();
+            weeklyRecordQuery.setPunchInIds(punchInIds);
+            weeklyRecordQuery.setStartDate(DateUtils.getLastWeekMonday(settleInfo.getSettleDate()).toString());
+            weeklyRecordQuery.setEndDate(DateUtils.getLastWeekSunday(settleInfo.getSettleDate()).toString());
+            List<PunchInRecord> punchInRecordForWeeks = punchInRecordService.listByCondition(weeklyRecordQuery);
+            if (!CollectionUtils.isEmpty(punchInRecordForWeeks)) {
+                weeklyPunchInRecords = punchInRecordForWeeks.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
+            }
+        }
+
+        // 用户-打卡任务分组
+        Map<Long, List<PunchIn>> userPunchInMap = punchIns.stream().collect(Collectors.groupingBy(PunchIn::getCreatedBy));
+        // 打卡任务-打卡记录 map
+        Map<Long, PunchInRecord> punchInRecordMap = punchInRecords.stream().collect(Collectors.toMap(PunchInRecord::getPunchInId, Function.identity(), (key1, key2) -> key1));
+
+        // 先创建结算任务执行记录
+        SettlementTask settlementTask = new SettlementTask();
+        settlementTask.setSettleDate(settleDate.toString());
+        settlementTask.setStartTime(settleStartTime);
+        settlementTask.setProcessedNum(users.size());
+        settlementTaskService.insert(settlementTask);
+
+        // 结算
+        List<SettleResultDto> settleResultDtoList = new ArrayList<>();
+        for (User user : users) {
+            SettleResultDto settleResult = settle(settleInfo, user, userPunchInMap.get(user.getId()), punchInRecordMap, weeklyPunchInRecords);
+            settleResultDtoList.add(settleResult);
+        }
+
+        // 更新数据
+        // 待更新的用户
+        List<User> updateUsers = new ArrayList<>();
+        // 待更新的打卡记录
+        List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
+        // 待新增的打卡记录
+        List<PunchInRecord> addPunchInRecords = new ArrayList<>();
+        // 待新增的打卡结算信息
+        List<PunchInSettlement> addPunchInSettlements = new ArrayList<>();
+
+        for (SettleResultDto settleResult : settleResultDtoList) {
+            updateUsers.add(settleResult.getUpdateUser());
+            addPunchInRecords.addAll(settleResult.getAddPunchInRecords());
+            updatePunchInRecords.addAll(settleResult.getUpdatePunchInRecords());
+            addPunchInSettlements.add(settleResult.getAddPunchInSettlement());
+        }
+
+        // 更新用户奖励信息
+        if (!CollectionUtils.isEmpty(updateUsers)) {
+            userService.batchUpdateUser(updateUsers);
+        }
+
+        // 新增打卡记录
+        if (!CollectionUtils.isEmpty(addPunchInRecords)) {
+            punchInRecordService.batchInsert(addPunchInRecords);
+        }
+
+        // 更新已有的打卡记录
+        if (!CollectionUtils.isEmpty(updatePunchInRecords)) {
+            punchInRecordService.batchUpdate(updatePunchInRecords);
+        }
+
+        // 新增结算信息
+        if (!CollectionUtils.isEmpty(addPunchInSettlements)) {
+            // 补充本次的计算任务ID
+            addPunchInSettlements.stream().forEach(v -> v.setSettlementTaskId(settlementTask.getId()));
+            punchInSettlementService.batchInsert(addPunchInSettlements);
+        }
+
+        // 新增关联信息
+        if (!CollectionUtils.isEmpty(settleResultDtoList)) {
+            List<PunchInRecordSettlementRela> relaList = buildPunchInRecordSettlementRelaList(settleResultDtoList);
+            if (!CollectionUtils.isEmpty(relaList)) {
+                punchInRecordSettlementRelaService.batchInsert(relaList);
+            }
+        }
+
+        // 构造并新增结算任务信息
+        settlementTask.setProcessedSettleNum(addPunchInSettlements.size());
+        settlementTask.setProcessedUnsettleNum(settlementTask.getProcessedNum() - settlementTask.getProcessedSettleNum());
+        settlementTask.setEndTime(new Timestamp(System.currentTimeMillis()));
+        settlementTaskService.update(settlementTask);
+
+        log.info("结算任务结束");
+    }
+
+    /**
+     * 结算
+     * @param settleInfo 结算基本信息
+     * @param user 待结算的用户
+     * @param punchIns 打卡任务
+     * @param punchInRecordMap 打卡任务-打卡记录 map
+     * @param weeklyPunchInRecordMap 打卡任务-一周打卡记录 map
+     * @return
+     */
+    private SettleResultDto settle(SettleInfoDto settleInfo, User user, List<PunchIn> punchIns, Map<Long, PunchInRecord> punchInRecordMap, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
+        // 结算奖励数
+        int settleRewardNum = 0;
+        // 待更新的打卡记录
+        List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
+        // 待新增的打卡记录
+        List<PunchInRecord> addPunchInRecords = new ArrayList<>();
+        // 结算
+        for (PunchIn punchIn : punchIns) {
+            // 获取打卡记录
+            PunchInRecord punchInRecord = punchInRecordMap.get(punchIn.getId());
+            // 不是补打卡且不存在打卡记录直接跳过,无须结算和更新记录状态
+            if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
+                continue;
+            }
+            // 判断是否满足打卡规则
+            PunchInStatusV1Enum punchInStatus = judgePunchInStatus(punchIn, punchInRecord);
+            // 不是补卡或打卡任务未完成,则跳过
+            if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusV1Enum.UN_FINISH.equals(punchInStatus)) {
+                PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
+                updatePunchInRecord.setId(punchInRecord.getId());
+                updatePunchInRecord.setPunchInStatus(punchInStatus);
+                updatePunchInRecords.add(updatePunchInRecord);
+                continue;
+            }
+
+            // 补打卡,完全没有打卡记录则需要补充打卡记录
+            if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
+                punchInRecord = buildPunchInRecordForSettle(punchIn);
+                punchInRecord.setPunchInId(punchIn.getId());
+                punchInRecord.setPunchInDate(settleInfo.getSettleDateStr());
+                punchInRecord.setPunchInStatus(PunchInStatusV1Enum.REMAKE_FINISH);
+                addPunchInRecords.add(punchInRecord);
+            }
+
+            // 补打卡,已有打卡记录但是不满足打卡规则,则需要对打卡记录做准备
+            if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusV1Enum.UN_FINISH.equals(punchInStatus)) {
+                fillTrack(punchIn, punchInRecord);
+            }
+
+            // 不是补卡,且打卡状态为完成,需要更新打卡状态为完成
+            if (Objects.isNull(punchInRecord.getPunchInStatus()) || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) {
+                PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
+                updatePunchInRecord.setId(punchInRecord.getId());
+                updatePunchInRecord.setPunchInStatus(punchInStatus);
+                updatePunchInRecords.add(updatePunchInRecord);
+            }
+
+            // 周末双倍奖励,否则计算普通奖励
+            settleRewardNum += settleInfo.getWeekendFlag() && punchIn.getWeekendDoubleFlag() ? punchIn.getRewardNum() * 2 : punchIn.getRewardNum();
+            // 计算全勤双倍奖励
+            if (judgeFullAttendance(settleInfo, punchIn, weeklyPunchInRecordMap)) {
+                settleRewardNum += punchIn.getRewardNum() * 2;
+            }
+        }
+
+        // 计算结算前后,用户奖励数的变化
+//        int beforeSettleRewardNum = user.getUnclaimedRewardNum();
+        int beforeSettleRewardNum = 0;
+        int afterSettleRewardNum = beforeSettleRewardNum + settleRewardNum;
+//        int totalRewardNum = user.getTotalRewardNum() + settleRewardNum;
+        int totalRewardNum = 0;
+
+        // 构造结算信息
+        PunchInSettlement addPunchInSettlement = new PunchInSettlement();
+        addPunchInSettlement.setUserId(user.getId());
+        addPunchInSettlement.setSettleRewardNum(settleRewardNum);
+        addPunchInSettlement.setSettlementTime(new Timestamp(System.currentTimeMillis()));
+        addPunchInSettlement.setBeforeSettleRewardNum(beforeSettleRewardNum);
+        addPunchInSettlement.setAfterSettleRewardNum(afterSettleRewardNum);
+
+        // 构造用户奖励信息
+        User updateUser = new User();
+        updateUser.setId(user.getId());
+//        updateUser.setTotalRewardNum(totalRewardNum);
+//        updateUser.setUnclaimedRewardNum(afterSettleRewardNum);
+
+        SettleResultDto settleResultDto = new SettleResultDto();
+        settleResultDto.setUpdateUser(updateUser);
+        settleResultDto.setAddPunchInRecords(addPunchInRecords);
+        settleResultDto.setUpdatePunchInRecords(updatePunchInRecords);
+        settleResultDto.setAddPunchInSettlement(addPunchInSettlement);
+        return settleResultDto;
+    }
+
+    @Override
+    public PunchInStatusV1Enum judgePunchInStatus(PunchIn punchIn, PunchInRecord punchInRecord) {
+        // 没有打卡记录,直接没完成,包含单次打卡的情况无需额外判断
+        if (Objects.isNull(punchInRecord)) {
+            return PunchInStatusV1Enum.UN_FINISH;
+        }
+
+        // 单次打卡
+        if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())) {
+            return PunchInStatusV1Enum.FINISH;
+        }
+
+        // 计数打卡
+        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
+            Integer recordCountTrack = Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0);
+            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == -1) {
+                return PunchInStatusV1Enum.UN_FINISH;
+            }
+            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == 1) {
+                return PunchInStatusV1Enum.UN_FINISH;
+            }
+
+            return PunchInStatusV1Enum.FINISH;
+        }
+
+        // 计时打卡
+        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
+            LocalTime recordTimeTrack = Optional.ofNullable(punchInRecord.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
+            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == -1) {
+                return PunchInStatusV1Enum.UN_FINISH;
+            }
+            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == 1) {
+                return PunchInStatusV1Enum.UN_FINISH;
+            }
+
+            return PunchInStatusV1Enum.FINISH;
+        }
+
+        return PunchInStatusV1Enum.UN_FINISH;
+    }
+
+    /**
+     * 填充打卡记录的记录信息(不用考虑单次打卡的情况)
+     * @param punchIn
+     * @param punchInRecord
+     */
+    private void fillTrack(PunchIn punchIn, PunchInRecord punchInRecord) {
+        // 计数打卡
+        if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
+            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
+                punchInRecord.setCountTrack(punchIn.getCountTrack() + 1);
+            }
+            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
+                punchInRecord.setCountTrack(punchIn.getCountTrack() - 1);
+            }
+        }
+
+        // 计时打卡
+        if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
+            if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
+                punchInRecord.setTimeTrack(punchIn.getTimeTrack().plusSeconds(1));
+            }
+            if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
+                punchInRecord.setTimeTrack(punchIn.getTimeTrack().minusSeconds(1));
+            }
+        }
+    }
+
+    /**
+     * 判断是否进行全勤结算
+     * @param settleInfo 结算信息
+     * @param punchIn 打卡任务
+     * @param weeklyPunchInRecordMap 一周打卡记录
+     * @return true-
+     */
+    private boolean judgeFullAttendance(SettleInfoDto settleInfo, PunchIn punchIn, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
+        // 没有启用全勤奖励则跳过
+        if (!punchIn.getFullAttendanceFlag()) {
+            return false;
+        }
+
+        // 不是周日结算或者补打卡则跳过,
+        if (!settleInfo.getSundayFlag() && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
+            return false;
+        }
+
+        // 获取一周的完成打卡的打卡记录,并且要排除结算日这天的记录
+        List<PunchInRecord> weeklyFinishRecord = Optional.ofNullable(weeklyPunchInRecordMap.get(punchIn.getId())).orElse(new ArrayList<>())
+                .stream().filter(record -> !settleInfo.getSettleDateStr().equals(record.getPunchInDate()) && (PunchInStatusV1Enum.FINISH.equals(record.getPunchInStatus()) || PunchInStatusV1Enum.REMAKE_FINISH.equals(record.getPunchInStatus())))
+                .collect(Collectors.toList());
+
+        // 1个是容错允许不打卡或未完成打卡,1个是当天的结算打卡来到这段逻辑就认为已经完成打卡,因此只要有5个完成打卡,则认为全勤
+        return weeklyFinishRecord.size() >= 5;
+    }
+
+    /**
+     * 构建待更新结算数据的打卡记录
+     * @return
+     */
+    private PunchInRecord buildPunchInRecordForSettle(PunchIn punchIn) {
+        PunchInRecord updatePunchInRecord = new PunchInRecord();
+        updatePunchInRecord.setSettleRewardNum(punchIn.getRewardNum());
+        updatePunchInRecord.setSettleCategory(punchIn.getCategory());
+        updatePunchInRecord.setSettleRule(punchIn.getRule());
+        updatePunchInRecord.setSettleCountTrack(punchIn.getCountTrack());
+        updatePunchInRecord.setSettleTimeTrack(punchIn.getTimeTrack());
+        return updatePunchInRecord;
+    }
+
+    /**
+     * 构造打卡记录与结算记录的关联关系
+     * @param settleResultDtos
+     * @return
+     */
+    private static List<PunchInRecordSettlementRela> buildPunchInRecordSettlementRelaList(List<SettleResultDto> settleResultDtos) {
+        if (CollectionUtils.isEmpty(settleResultDtos)) {
+            return List.of();
+        }
+
+        List<PunchInRecordSettlementRela> relaList = new ArrayList();
+        // 补充关联关系ID(结算记录ID、打卡记录ID)
+        for (SettleResultDto settleResultDto : settleResultDtos) {
+            for (PunchInRecord punchInRecord : settleResultDto.getAddPunchInRecords()) {
+                PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
+                rela.setRecordId(punchInRecord.getId());
+                rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
+                relaList.add(rela);
+            }
+
+            for (PunchInRecord punchInRecord : settleResultDto.getUpdatePunchInRecords()) {
+                PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
+                rela.setRecordId(punchInRecord.getId());
+                rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
+                relaList.add(rela);
+            }
+        }
+        return relaList;
+    }
+
+    @Override
+    public void manualSettle(SettleRequest settleRequest) {
+        // TODO 这里考虑加判断条件防止重复手动结算
+        Assert.isNullInBusiness(settleRequest, "结算请求不能为空");
+        if (!PunchInSettleTypeEnum.OPS.equals(settleRequest.getSettleType())) {
+            BusinessException.throwFail("非运维结算,禁止执行");
+        }
+        SpringUtils.getBean(ISettleManagerV1.class).settleHandler(settleRequest.getSettleType(), LocalDate.parse(settleRequest.getSettleDate()), settleRequest.getUserIds(), settleRequest.getPunchInIds());
+    }
+
+    @Override
+    public List<SettleVO> querySettle(SettleQuery query) {
+        SimpleDateFormat sdf = DateUtils.buildDateTimeFormat();
+
+        List<PunchInSettlement> punchInSettlements = punchInSettlementService.listByCondition(query);
+        return punchInSettlements.stream().map(settlement -> {
+            SettleVO settleVO = new SettleVO();
+            BeanUtils.copyProperties(settlement, settleVO);
+            settleVO.setSettlementTime(sdf.format(settlement.getSettlementTime()));
+            return settleVO;
+        }).toList();
+    }
+}

+ 2 - 2
src/main/java/com/punchsettle/server/service/manager/impl/TaskManagerImpl.java

@@ -5,7 +5,7 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
 import com.punchsettle.server.constant.PunchInSettleTypeEnum;
-import com.punchsettle.server.service.manager.ISettleManager;
+import com.punchsettle.server.service.manager.ISettleManagerV1;
 import com.punchsettle.server.service.manager.ITaskManager;
 import com.punchsettle.server.utiis.DateUtils;
 
@@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j;
 public class TaskManagerImpl implements ITaskManager {
 
     @Autowired
-    private ISettleManager settleManager;
+    private ISettleManagerV1 settleManager;
 
     @Override
     @Scheduled(cron = "0 5 0 * * ?")