From 571ccc18671ef45c6403496e8d99efec82168083 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 26 六月 2026 10:21:02 +0800
Subject: [PATCH] 1.人员排班同步请假信息 2.人员排班同步调休信息

---
 docs/personal_shift_holiday_linkage.md                                   |  142 +++++++++++++++++++++++
 src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java            |    5 
 src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java            |    7 +
 src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java |  143 +++++++++++++++++++++--
 src/main/resources/mapper/staff/PersonalShiftMapper.xml                  |   18 +++
 5 files changed, 298 insertions(+), 17 deletions(-)

diff --git a/docs/personal_shift_holiday_linkage.md b/docs/personal_shift_holiday_linkage.md
new file mode 100644
index 0000000..911bcf4
--- /dev/null
+++ b/docs/personal_shift_holiday_linkage.md
@@ -0,0 +1,142 @@
+# 浜哄憳鎺掔彮璇峰亣浼戞伅鑱斿姩
+
+## 娑夊強椤甸潰
+
+- 浜哄憳鎺掔彮绠$悊椤甸潰锛堟湀搴︽帓鐝煡鐪嬶級
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /personalShift/page | 浜哄憳鎺掔彮鏈堝害鍒嗛〉鏌ヨ锛堝凡鏂板璇峰亣浼戞伅鑱斿姩锛� |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| size | Integer | 鏄� | 鍒嗛〉澶у皬 |
+| current | Integer | 鏄� | 褰撳墠椤电爜 |
+| time | String | 鏄� | 鏃堕棿锛堟牸寮忥細yyyy-MM-dd HH:mm:ss锛� |
+| userName | String | 鍚� | 鍛樺伐濮撳悕锛堟ā绯婃煡璇級 |
+| sysDeptId | Integer | 鍚� | 閮ㄩ棬ID |
+
+**鍝嶅簲缁撴瀯鍙樺寲锛�**
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "page": {
+      "records": [
+        {
+          "name": "鍛樺伐濮撳悕",
+          "userId": "鍛樺伐ID",
+          "department": "閮ㄩ棬",
+          "shiftTime": null,
+          "monthlyAttendance": {
+            "鏃�": 5,
+            "涓�": 3,
+            "浼戞伅": 2,
+            "totalAttendance": 8
+          },
+          "list": [
+            {
+              "id": "鎺掔彮璁板綍ID",
+              "shift": "鏃�",
+              "time": "2026-06-01 00:00:00",
+              "isHoliday": false
+            },
+            {
+              "id": "鎺掔彮璁板綍ID",
+              "shift": "浼戞伅",
+              "time": "2026-06-05 00:00:00",
+              "isHoliday": true
+            }
+          ],
+          "holidayDates": ["2026-06-05", "2026-06-06"]
+        }
+      ]
+    },
+    "headerList": [...]
+  }
+}
+```
+
+### 鏂板瀛楁璇存槑
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| list[].isHoliday | Boolean | 璇ユ棩鏈熸槸鍚︿负瀹℃牳閫氳繃鐨勮鍋囨棩鏈� |
+| list[].shift | String | 濡傛湁璇峰亣鍒欐樉绀�"浼戞伅"锛屽惁鍒欐樉绀哄師鐝 |
+| holidayDates | List<String> | 璇ュ憳宸ュ綋鏈堟墍鏈夊鏍搁�氳繃鐨勮鍋囨棩鏈熷垪琛紙鏍煎紡锛歽yyy-MM-dd锛� |
+| monthlyAttendance.浼戞伅 | Integer | 褰撴湀璇峰亣浼戞伅澶╂暟缁熻 |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 琛ㄦ牸娓叉煋閫昏緫淇敼
+
+鍦ㄦ覆鏌撴帓鐝〃鏍兼椂锛岄渶瑕佹牴鎹� `isHoliday` 瀛楁鍒ゆ柇鏄惁涓鸿鍋囦紤鎭棩鏈燂紝骞舵樉绀虹壒娈婃牱寮忋��
+
+```html
+<el-table-column v-for="day in headerList" :key="day.headerTime" :label="day.headerTime">
+  <template #default="{ row }">
+    <!-- 鎵惧埌瀵瑰簲鏃ユ湡鐨勬帓鐝暟鎹� -->
+    <div
+      v-for="item in row.list.filter(i => i.time.startsWith(day.headerTime.split(' ')[0]))"
+      :key="item.id"
+      :class="{ 'holiday-cell': item.isHoliday }"
+    >
+      {{ item.shift }}
+    </div>
+  </template>
+</el-table-column>
+```
+
+### 2. 鏍峰紡淇敼
+
+璇峰亣浼戞伅鏃ユ湡闇�瑕佺壒娈婃牱寮忔爣璁般��
+
+```css
+.holiday-cell {
+  background-color: #fff3cd;
+  color: #856404;
+  border: 1px solid #ffc107;
+  padding: 4px 8px;
+  border-radius: 4px;
+}
+```
+
+### 3. 鏁版嵁澶勭悊
+
+```js
+// 鍦ㄨ幏鍙栨暟鎹悗锛屽鐞嗚鍋囨棩鏈熸樉绀�
+handleShiftData(data) {
+  data.page.records.forEach(row => {
+    // row.holidayDates 鍖呭惈鎵�鏈夎鍋囨棩鏈�
+    // row.list 涓� isHoliday=true 鐨勯」琛ㄧず璇峰亣浼戞伅
+    // row.monthlyAttendance.浼戞伅 琛ㄧず褰撴湀璇峰亣浼戞伅澶╂暟
+  });
+}
+```
+
+### 4. 鍙�夛細璇峰亣璇︽儏寮圭獥
+
+濡傞渶鏌ョ湅璇峰亣璇︽儏锛屽彲閫氳繃 `holidayDates` 鍒楄〃灞曠ず銆�
+
+```html
+<el-tooltip v-if="row.holidayDates.length > 0" placement="top">
+  <template #content>
+    <div>璇峰亣鏃ユ湡锛�</div>
+    <div v-for="date in row.holidayDates" :key="date">{{ date }}</div>
+  </template>
+  <el-tag type="warning" size="small">{{ row.holidayDates.length }}澶╄鍋�</el-tag>
+</el-tooltip>
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 璇峰亣鏁版嵁鏉ユ簮浜� `HolidayApplication` 琛紝鍙湁鐘舵�佷负 `APPROVED`锛堝鏍搁�氳繃锛夌殑璇峰亣璁板綍鎵嶄細琚悓姝�
+- 璇峰亣鏃ユ湡鑼冨洿鍐咃紝鍘熺彮娆′細琚浛鎹负"浼戞伅"鏄剧ず
+- 鏈堝害缁熻涓殑"浼戞伅"澶╂暟浼氬崟鐙粺璁¤鍋囦紤鎭ぉ鏁�
+- 鍑哄嫟澶╂暟缁熻锛堟棭/涓�/鏅�/澶滐級涓嶄細缁熻璇峰亣浼戞伅鏃ユ湡
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java b/src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java
index ccb0590..aed7ab5 100644
--- a/src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java
+++ b/src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java
@@ -23,4 +23,9 @@
     private List<Map<String, Object>> list = new ArrayList<>();
 
     private List<Map<Object, Object>> headerList = new ArrayList<>();
+
+    /**
+     * 璇峰亣鏃ユ湡鍒楄〃锛堝鏍搁�氳繃鐨勮鍋囨棩鏈燂紝鏍煎紡锛歽yyy-MM-dd锛�
+     */
+    private List<String> holidayDates = new ArrayList<>();
 }
diff --git a/src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java b/src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java
index 8ae881f..5d2009e 100644
--- a/src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java
+++ b/src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java
@@ -31,4 +31,11 @@
     List<Map<String, Object>> performanceShiftYearList(@Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
 
     List<PerformanceShiftMapDto> performanceShiftList(@Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
+
+    /**
+     * 鏌ヨ鍛樺伐鐨勮鍋囦俊鎭紙浠巃pproval_instance鐨刦orm_config瑙f瀽锛�
+     * @param staffIds 鍛樺伐ID鍒楄〃
+     * @return Map: staff_id, form_config
+     */
+    List<Map<String, Object>> selectStaffHolidayDates(@Param("staffIds") List<Long> staffIds);
 }
diff --git a/src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java
index 81a3545..3da6ffb 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java
@@ -29,6 +29,7 @@
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
@@ -116,13 +117,89 @@
         IPage<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShiftPage(page, time, userName, sysDeptId);
         //鏌ヨ鎵�鏈夌彮娆�(鎵撳崱瑙勫垯)
         List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
+
+        // 鑾峰彇褰撴湀鏃堕棿鑼冨洿
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
+        LocalDate firstDayOfMonth = localDateTime.toLocalDate().withDayOfMonth(1);
+        LocalDate lastDayOfMonth = localDateTime.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
+
+        // 鏀堕泦鎵�鏈夊憳宸D
+        List<Long> staffIds = mapIPage.getRecords().stream()
+                .filter(dto -> dto.getUserId() != null)
+                .map(dto -> Long.valueOf(dto.getUserId()))
+                .distinct()
+                .collect(Collectors.toList());
+
+        // 鏌ヨ褰撴湀瀹℃牳閫氳繃鐨勮鍋囪褰曪紙浠巃pproval_instance鐨刦orm_config瑙f瀽锛�
+        Map<Long, List<String>> staffHolidayMap = new HashMap<>();
+        if (!staffIds.isEmpty()) {
+            // 閫氳繃鍏宠仈鏌ヨ鑾峰彇鍛樺伐璇峰亣淇℃伅
+            List<Map<String, Object>> holidayRecords = baseMapper.selectStaffHolidayDates(staffIds);
+
+            // 鏋勫缓鍛樺伐-璇峰亣鏃ユ湡鏄犲皠锛堜粠form_config JSON瑙f瀽锛�
+            for (Map<String, Object> record : holidayRecords) {
+                Long staffId = ((Number) record.get("staff_id")).longValue();
+                String formConfig = (String) record.get("form_config");
+
+                if (formConfig != null && !formConfig.isEmpty()) {
+                    try {
+                        // 瑙f瀽form_config JSON
+                        Map<String, Object> formMap = JackSonUtil.unmarshal(formConfig, Map.class);
+                        Object formPayloadObj = formMap.get("formPayload");
+
+                        if (formPayloadObj instanceof Map) {
+                            Map<String, Object> formPayload = (Map<String, Object>) formPayloadObj;
+                            Object dateRangeObj = formPayload.get("00"); // 璇峰亣鏃ユ湡瀛楁key涓�"00"
+
+                            if (dateRangeObj instanceof List) {
+                                List<String> dateRange = (List<String>) dateRangeObj;
+                                if (dateRange.size() >= 2) {
+                                    String startDateTime = dateRange.get(0);
+                                    String endDateTime = dateRange.get(1);
+
+                                    // 瑙f瀽鏃ユ湡锛堟牸寮忥細yyyy-MM-dd HH:mm:ss锛�
+                                    LocalDate startDate = LocalDate.parse(startDateTime.substring(0, 10));
+                                    LocalDate endDate = LocalDate.parse(endDateTime.substring(0, 10));
+
+                                    // 杩囨护褰撴湀鑼冨洿鍐呯殑璇峰亣鏃ユ湡
+                                    List<String> dates = getDatesBetween(startDate, endDate).stream()
+                                            .filter(date -> {
+                                                LocalDate d = LocalDate.parse(date);
+                                                return !d.isBefore(firstDayOfMonth) && !d.isAfter(lastDayOfMonth);
+                                            })
+                                            .collect(Collectors.toList());
+
+                                    if (!dates.isEmpty()) {
+                                        staffHolidayMap.computeIfAbsent(staffId, k -> new ArrayList<>()).addAll(dates);
+                                    }
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        // JSON瑙f瀽澶辫触锛岃烦杩囨璁板綍
+                    }
+                }
+            }
+        }
+
         for (PerformanceShiftMapDto i : mapIPage.getRecords()) {
+            // 璁剧疆璇峰亣鏃ユ湡鍒楄〃
+            if (i.getUserId() != null) {
+                Long staffId = Long.valueOf(i.getUserId());
+                List<String> holidayDates = staffHolidayMap.getOrDefault(staffId, new ArrayList<>());
+                i.setHolidayDates(holidayDates);
+            }
+
             List<String> shiftTimes = StrUtil.split(i.getShiftTime(), ";");
             if(CollUtil.isEmpty(shiftTimes)){
                 continue;
             }
             double totalAttendance = 0;//鎬诲嚭鍕ゅぉ鏁�
             List<Map<String, Object>> map = new ArrayList<>();
+            // 鑾峰彇璇ュ憳宸ョ殑璇峰亣鏃ユ湡闆嗗悎
+            Set<String> holidayDateSet = new HashSet<>(i.getHolidayDates());
             // 鍒嗗壊鏃ユ湡
             for (String shiftTime : shiftTimes) {
                 i.setShiftTime(null);
@@ -135,34 +212,51 @@
                 hashMap.put("id", shiftTimeAndShift.get(2));
                 hashMap.put("shift", shiftTimeAndShift.get(1));
                 hashMap.put("time", shiftTimeAndShift.get(0));
+
+                // 鎻愬彇鏃ユ湡閮ㄥ垎锛堟牸寮忥細yyyy-MM-dd HH:mm:ss -> yyyy-MM-dd锛�
+                String workTimeStr = shiftTimeAndShift.get(0);
+                String workDate = workTimeStr.length() >= 10 ? workTimeStr.substring(0, 10) : workTimeStr;
+
+                // 濡傛灉璇ユ棩鏈熸湁瀹℃牳閫氳繃鐨勮鍋囪褰曪紝璁剧疆isHoliday涓簍rue锛屽苟灏嗙彮娆¤涓轰紤鎭�
+                if (holidayDateSet.contains(workDate)) {
+                    hashMap.put("isHoliday", true);
+                    hashMap.put("shift", "浼戞伅");
+                } else {
+                    hashMap.put("isHoliday", false);
+                }
+
                 map.add(hashMap);
                 i.setList(map);
-                //姹囨�荤殑鍚勭彮娆$粺璁℃暟鎹�
-                for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
-                    if (!i.getMonthlyAttendance().containsKey(personalAttendanceLocationConfig.getShift())){
-                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), 0);
+
+                // 缁熻鐝鏁版嵁
+                if (holidayDateSet.contains(workDate)) {
+                    // 濡傛灉鏄鍋囨棩鏈燂紝缁熻涓轰紤鎭紙鍙粺璁′竴娆★級
+                    if (!i.getMonthlyAttendance().containsKey("浼戞伅")){
+                        i.getMonthlyAttendance().put("浼戞伅", 0);
                     }
-                    if (personalAttendanceLocationConfig.getShift().equals(shiftTimeAndShift.get(1))) {
-                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(personalAttendanceLocationConfig.getShift()).toString());
-                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), bigDecimal.add(new BigDecimal("1")));
+                    i.getMonthlyAttendance().put("浼戞伅", new BigDecimal(i.getMonthlyAttendance().get("浼戞伅").toString()).add(new BigDecimal("1")));
+                } else {
+                    // 涓嶆槸璇峰亣鏃ユ湡锛岀粺璁″師鐝
+                    for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
+                        if (!i.getMonthlyAttendance().containsKey(personalAttendanceLocationConfig.getShift())){
+                            i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), 0);
+                        }
+                        if (personalAttendanceLocationConfig.getShift().equals(shiftTimeAndShift.get(1))) {
+                            BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(personalAttendanceLocationConfig.getShift()).toString());
+                            i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), bigDecimal.add(new BigDecimal("1")));
+                        }
                     }
-                }
-                //缁熻鎬诲嚭鍕ゅぉ鏁�(鏃�/涓�/鏅�/澶�)閮界畻鍑哄嫟,鍏朵綑閮芥槸浼戞伅
-                if (shiftTimeAndShift.get(1).contains("鏃�") ||
+                    // 缁熻鎬诲嚭鍕ゅぉ鏁�(鏃�/涓�/鏅�/澶�)閮界畻鍑哄嫟
+                    if (shiftTimeAndShift.get(1).contains("鏃�") ||
                         shiftTimeAndShift.get(1).contains("涓�") ||
                         shiftTimeAndShift.get(1).contains("鏅�") ||
                         shiftTimeAndShift.get(1).contains("澶�")) {
-                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
+                        i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
+                    }
                 }
             }
         }
         // 鑾峰彇header鏃堕棿
-        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-        // 灏嗗瓧绗︿覆鏃堕棿杞崲涓� LocalDateTime 绫诲瀷鏃堕棿
-        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
-        LocalDate firstDayOfMonth = localDateTime.toLocalDate().withDayOfMonth(1);
-        LocalDate lastDayOfMonth = localDateTime.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
         List<LocalDateTime> localDateTimesBetween = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
         List<Object> list1 = new ArrayList<>();
         for (LocalDateTime dateTime : localDateTimesBetween) {
@@ -178,6 +272,21 @@
         return resultMap;
     }
 
+    /**
+     * 鑾峰彇涓や釜鏃ユ湡涔嬮棿鐨勬墍鏈夋棩鏈燂紙鏍煎紡锛歽yyy-MM-dd锛�
+     * 娉ㄦ剰锛氱粨鏉熸棩鏈熶笉鍖呭惈鍦ㄥ唴锛堣鍋囨棩鏈熻寖鍥存槸寮�濮嬫棩鏈熷埌缁撴潫鏃ユ湡锛屼笉鍚粨鏉熸棩鏈燂級
+     */
+    private List<String> getDatesBetween(LocalDate startDate, LocalDate endDate) {
+        List<String> dates = new ArrayList<>();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate current = startDate;
+        while (current.isBefore(endDate)) {
+            dates.add(current.format(formatter));
+            current = current.plusDays(1);
+        }
+        return dates;
+    }
+
     @Override
     public void performanceShiftUpdate(PersonalShift personalShift) {
         baseMapper.update(new PersonalShift(), Wrappers.<PersonalShift>lambdaUpdate()
diff --git a/src/main/resources/mapper/staff/PersonalShiftMapper.xml b/src/main/resources/mapper/staff/PersonalShiftMapper.xml
index 7d80392..9ece2d1 100644
--- a/src/main/resources/mapper/staff/PersonalShiftMapper.xml
+++ b/src/main/resources/mapper/staff/PersonalShiftMapper.xml
@@ -103,4 +103,22 @@
         order by s.create_time
     </select>
 
+    <!-- 鏌ヨ鍛樺伐鐨勮鍋囦俊鎭紙浠巃pproval_instance鐨刦orm_config瑙f瀽锛� -->
+    <!-- 鍏宠仈璺緞: staff_on_job.staff_no = sys_user.user_name, sys_user.user_id = approval_instance.applicant_id -->
+    <select id="selectStaffHolidayDates" resultType="java.util.Map">
+        SELECT
+            soj.id AS staff_id,
+            ai.form_config
+        FROM staff_on_job soj
+        INNER JOIN sys_user su ON su.user_name = soj.staff_no
+        INNER JOIN approval_instance ai ON ai.applicant_id = su.user_id
+        WHERE soj.id IN
+        <foreach collection="staffIds" item="staffId" open="(" separator="," close=")">
+            #{staffId}
+        </foreach>
+        AND ai.business_type IN (14, 19)
+        AND ai.status = 'APPROVED'
+        AND ai.deleted = 0
+    </select>
+
 </mapper>

--
Gitblit v1.9.3