ソースを参照

修改
1、获得院内动态接口内容调整

liangwenxuan 3 週間 前
親
コミット
26f42cec03

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/OperationAnalysisController.java

@@ -35,6 +35,8 @@ public class OperationAnalysisController {
     @GetMapping("/dynamics")
     @Operation(summary = "获得院内动态",
             description = "统计今日新收、今日退院、请假人数、当天请假人数、轮候待住、预订在途," +
+                    "运营周期汇总(本周上周六~本周五、本月 1 号~今天的入住 / 退住 / 净增长、期初数与期末数)," +
+                    "院区床位结构(总床位、在住床位、入住率、空闲床位、临时占用)," +
                     "以及近 12 个月已完成退住趋势、护理等级分布;按机构分组返回,每个机构各一份。")
     @TenantIgnore
     @PermitAll

+ 58 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/operationanalysis/OperationAnalysisOverviewDynamicsRespVO.java

@@ -1,10 +1,14 @@
 package cn.iocoder.yudao.module.system.controller.admin.biz.vo.operationanalysis;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.time.LocalDate;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
 /**
  * 运营分析 - 概况 - 院内动态 RespVO
  *
@@ -39,6 +43,60 @@ public class OperationAnalysisOverviewDynamicsRespVO {
     @Schema(description = "预订在途", example = "6")
     private Long reservationCount;
 
+    // ========== 运营周期汇总 ==========
+
+    @Schema(description = "本周开始日期(上周六)", example = "2026-09-12")
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDate periodBegin;
+
+    @Schema(description = "本周结束日期(本周五)", example = "2026-09-18")
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDate periodEnd;
+
+    @Schema(description = "本周期初数(期末数 - 本周净增长,最小为 0)", example = "606")
+    private Long periodBeginCount;
+
+    @Schema(description = "本周入住数", example = "2")
+    private Long weeklyCheckInCount;
+
+    @Schema(description = "本周退住数", example = "1")
+    private Long weeklyCheckOutCount;
+
+    @Schema(description = "本周期末数(当前在住人数)", example = "607")
+    private Long periodEndCount;
+
+    @Schema(description = "本周净增长长者数(本周入住数 - 本周退住数)", example = "1")
+    private Long weeklyNetGrowthCount;
+
+    @Schema(description = "本月入住数", example = "26")
+    private Long monthlyCheckInCount;
+
+    @Schema(description = "本月退住数", example = "1")
+    private Long monthlyCheckOutCount;
+
+    @Schema(description = "本月净增长长者数(本月入住数 - 本月退住数)", example = "25")
+    private Long monthlyNetGrowthCount;
+
+    @Schema(description = "本周期末院内空余床位数(同空闲床位数)", example = "193")
+    private Long periodEndEmptyBedCount;
+
+    // ========== 院区床位结构 ==========
+
+    @Schema(description = "总床位数", example = "799")
+    private Long totalBedCount;
+
+    @Schema(description = "在住床位数(床位状态 1 - 已住)", example = "606")
+    private Long occupiedBedCount;
+
+    @Schema(description = "入住率(在住床位 / 总床位,保留 2 位小数)", example = "75.84%")
+    private String occupancyRate;
+
+    @Schema(description = "空闲床位数(床位状态 0 - 空闲)", example = "190")
+    private Long emptyBedCount;
+
+    @Schema(description = "临时占用床位数(床位状态 2 - 临时占用)", example = "3")
+    private Long temporaryOccupationCount;
+
     @Schema(description = "近 12 个月已完成退住趋势(含当月,无数据补 0)")
     private List<MonthlyRetreatTrendVO> retreatTrend;
 

+ 41 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/OperationAnalysisMapper.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.dal.mysql.biz;
 
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationNurseLevelCountBO;
+import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantBedCountBO;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantCountBO;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantMonthCountBO;
 import org.apache.ibatis.annotations.Mapper;
@@ -103,4 +104,44 @@ public interface OperationAnalysisMapper {
      */
     List<OperationNurseLevelCountBO> selectNurseLevelDistribution(@Param("tenantIds") Collection<Long> tenantIds);
 
+    /**
+     * 区间入住数:以长者档案的入住时间为准
+     *
+     * @param tenantIds 机构ID
+     * @param begin     区间起始日 00:00:00(含)
+     * @param end       区间结束日 23:59:59(含)
+     * @return 按机构分组的入住人数
+     */
+    List<OperationTenantCountBO> selectRangeCheckInCount(@Param("tenantIds") Collection<Long> tenantIds,
+                                                        @Param("begin") Date begin,
+                                                        @Param("end") Date end);
+
+    /**
+     * 区间退住数:以退住登记表的退住时间为准,且必须是「已完成」的退住
+     *
+     * @param tenantIds 机构ID
+     * @param begin     区间起始日(含)
+     * @param end       区间结束日(含)
+     * @return 按机构分组的退住人数
+     */
+    List<OperationTenantCountBO> selectRangeCheckOutCount(@Param("tenantIds") Collection<Long> tenantIds,
+                                                         @Param("begin") LocalDate begin,
+                                                         @Param("end") LocalDate end);
+
+    /**
+     * 在住人数:长者档案的在住状态为在住
+     *
+     * @param tenantIds 机构ID
+     * @return 按机构分组的在住人数
+     */
+    List<OperationTenantCountBO> selectInStatusCount(@Param("tenantIds") Collection<Long> tenantIds);
+
+    /**
+     * 床位结构:按机构统计总床位、在住床位、空闲床位、临时占用
+     *
+     * @param tenantIds 机构ID
+     * @return 按机构分组的床位结构
+     */
+    List<OperationTenantBedCountBO> selectBedStructure(@Param("tenantIds") Collection<Long> tenantIds);
+
 }

+ 40 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/bo/OperationTenantBedCountBO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.system.dal.mysql.biz.bo;
+
+import lombok.Data;
+
+/**
+ * 运营分析 —— 按机构(租户)分组的床位结构 BO
+ *
+ * <p>床位状态:0 - 空闲,1 - 已住,2 - 临时占用。</p>
+ *
+ * @author kyj
+ */
+@Data
+public class OperationTenantBedCountBO {
+
+    /**
+     * 机构ID(租户ID)
+     */
+    private Long tenantId;
+
+    /**
+     * 总床位数
+     */
+    private Long totalBedCount;
+
+    /**
+     * 在住床位数(status = 1)
+     */
+    private Long occupiedBedCount;
+
+    /**
+     * 空闲床位数(status = 0)
+     */
+    private Long emptyBedCount;
+
+    /**
+     * 临时占用床位数(status = 2)
+     */
+    private Long temporaryOccupationCount;
+
+}

+ 8 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoServiceImpl.java

@@ -2004,9 +2004,13 @@ public class ElderlyInfoServiceImpl implements ElderlyInfoService {
                     .in(BuildBedDO::getTenantId, reqVO.getTenantIds())
                     .eq(BuildBedDO::getOrgType, 1));
 
-            // 入住率
-            BigDecimal checkInRate = BigDecimal.valueOf(Long.valueOf(checkInBedCount))
-                    .divide(BigDecimal.valueOf(Long.valueOf(bedCount)), 2, BigDecimal.ROUND_HALF_UP).multiply(BigDecimal.valueOf(100));
+            // 入住率:在住数 / 总床位,先放大再保留 2 位小数,如 75.84%
+            long totalBed = Long.parseLong(bedCount);
+            long checkInBed = Long.parseLong(checkInBedCount);
+            String checkInRate = totalBed <= 0 ? "0.00%" : BigDecimal.valueOf(checkInBed)
+                    .multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(totalBed), 2, RoundingMode.HALF_UP)
+                    .toPlainString() + "%";
 
             // 员工数
             Long employeeCount = userMapper.selectCount(new LambdaQueryWrapperX<AdminUserDO>()
@@ -2020,7 +2024,7 @@ public class ElderlyInfoServiceImpl implements ElderlyInfoService {
             overviewSummary.setBedCount(bedCount);
             overviewSummary.setPresetCount("0");
             overviewSummary.setEmptyBedCount(freeBedCount.toString());
-            overviewSummary.setOccupancyRate(checkInRate.toString() + "%");
+            overviewSummary.setOccupancyRate(checkInRate);
             overviewSummary.setUserCount(employeeCount.toString());
             overviewSummary.setCaregiverCount(caregiverCount.toString());
         }

+ 126 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/OperationAnalysisServiceImpl.java

@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.tenant.GroupTenantDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.OperationAnalysisMapper;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationNurseLevelCountBO;
+import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantBedCountBO;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantCountBO;
 import cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantMonthCountBO;
 import cn.iocoder.yudao.module.system.dal.mysql.tenant.GroupTenantMapper;
@@ -22,11 +23,15 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DayOfWeek;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.YearMonth;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -50,7 +55,11 @@ import java.util.stream.Collectors;
  *     <li>轮候待住 —— 轮候入住 {@code status = 10}(审批完成待入住);</li>
  *     <li>预订在途 —— 床位预约 {@code status = 2}(已缴费);</li>
  *     <li>近 12 个月已完成退住趋势 —— 按月统计「已完成」的退住,含当月,无数据补 0;</li>
- *     <li>护理等级分布 —— 在住长者({@code in_status = 1})按护理等级分组。</li>
+ *     <li>护理等级分布 —— 在住长者({@code in_status = 1})按护理等级分组;</li>
+ *     <li>运营周期汇总 —— 本周(上周六 ~ 本周五)与本月(1 号 ~ 今天)的入住 / 退住 / 净增长,
+ *     期初数由「期末数 - 本周净增长」倒推,期末数取当前在住人数;</li>
+ *     <li>院区床位结构 —— 机构下 {@code org_build_bed} 的总床位、在住床位(status = 1)、
+ *     空闲床位(status = 0)、临时占用(status = 2),入住率 = 在住床位 / 总床位。</li>
  * </ol>
  *
  * @author kyj
@@ -95,12 +104,17 @@ public class OperationAnalysisServiceImpl implements OperationAnalysisService {
 
         LocalDate today = LocalDate.now();
         Date dayBegin = toDate(today.atStartOfDay());
-        Date dayEnd = toDate(today.plusDays(1).atStartOfDay().minusNanos(1));
+        Date dayEnd = toDayEnd(today);
         Date now = new Date();
         // 近 12 个月(含当月):起始月 1 号(含)~ 次月 1 号(不含)
         YearMonth currentMonth = YearMonth.now();
         LocalDate trendBegin = currentMonth.minusMonths(TREND_MONTHS - 1).atDay(1);
         LocalDate trendEnd = currentMonth.plusMonths(1).atDay(1);
+        // 本周:上周六(含)~ 本周五(含);本月:1 号(含)~ 今天(含)
+        LocalDate weekBegin = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.SATURDAY));
+        LocalDate weekEnd = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY));
+        LocalDate monthBegin = today.withDayOfMonth(1);
+        LocalDate monthEnd = today;
 
         // 1、六个指标:按机构分组,各一次查询
         Map<Long, Long> newCheckInMap = toTenantCountMap(
@@ -116,6 +130,23 @@ public class OperationAnalysisServiceImpl implements OperationAnalysisService {
         Map<Long, Long> reservationMap = toTenantCountMap(
                 operationAnalysisMapper.selectReservationCount(tenantIds));
 
+        // 1.1、运营周期汇总:本周 / 本月的入住与退住、当前在住人数
+        Map<Long, Long> weeklyCheckInMap = toTenantCountMap(operationAnalysisMapper
+                .selectRangeCheckInCount(tenantIds, toDate(weekBegin.atStartOfDay()), toDayEnd(weekEnd)));
+        Map<Long, Long> weeklyCheckOutMap = toTenantCountMap(operationAnalysisMapper
+                .selectRangeCheckOutCount(tenantIds, weekBegin, weekEnd));
+        Map<Long, Long> monthlyCheckInMap = toTenantCountMap(operationAnalysisMapper
+                .selectRangeCheckInCount(tenantIds, toDate(monthBegin.atStartOfDay()), toDayEnd(monthEnd)));
+        Map<Long, Long> monthlyCheckOutMap = toTenantCountMap(operationAnalysisMapper
+                .selectRangeCheckOutCount(tenantIds, monthBegin, monthEnd));
+        Map<Long, Long> inStatusMap = toTenantCountMap(operationAnalysisMapper.selectInStatusCount(tenantIds));
+
+        // 1.2、院区床位结构:按机构分组,一次查询
+        Map<Long, OperationTenantBedCountBO> bedStructureMap = operationAnalysisMapper
+                .selectBedStructure(tenantIds).stream()
+                .filter(bo -> bo.getTenantId() != null)
+                .collect(Collectors.toMap(OperationTenantBedCountBO::getTenantId, bo -> bo, (a, b) -> a));
+
         // 2、趋势与分布:先按机构分组
         Map<Long, List<OperationTenantMonthCountBO>> trendGroup = operationAnalysisMapper
                 .selectRetreatTrend(tenantIds, trendBegin, trendEnd).stream()
@@ -138,6 +169,9 @@ public class OperationAnalysisServiceImpl implements OperationAnalysisService {
             vo.setTodayAskLeaveCount(todayAskLeaveMap.getOrDefault(tenantId, 0L));
             vo.setWaitingCount(waitingMap.getOrDefault(tenantId, 0L));
             vo.setReservationCount(reservationMap.getOrDefault(tenantId, 0L));
+            fillOperationSummary(vo, weeklyCheckInMap, weeklyCheckOutMap, monthlyCheckInMap,
+                    monthlyCheckOutMap, inStatusMap, weekBegin, weekEnd);
+            fillBedStructure(vo, bedStructureMap.get(tenantId));
             vo.setRetreatTrend(buildRetreatTrend(trendGroup.get(tenantId)));
             vo.setNurseLevelDistribution(buildNurseLevelDistribution(nurseLevelGroup.get(tenantId)));
             result.add(vo);
@@ -145,6 +179,86 @@ public class OperationAnalysisServiceImpl implements OperationAnalysisService {
         return result;
     }
 
+    /**
+     * 填充运营周期汇总:本周 / 本月的入住、退住、净增长与期初 / 期末数
+     *
+     * @param vo                 待填充的机构数据
+     * @param weeklyCheckInMap   本周入住数
+     * @param weeklyCheckOutMap  本周退住数
+     * @param monthlyCheckInMap  本月入住数
+     * @param monthlyCheckOutMap 本月退住数
+     * @param inStatusMap        当前在住人数
+     * @param weekBegin          本周开始日期
+     * @param weekEnd            本周结束日期
+     */
+    private void fillOperationSummary(OperationAnalysisOverviewDynamicsRespVO vo,
+                                      Map<Long, Long> weeklyCheckInMap,
+                                      Map<Long, Long> weeklyCheckOutMap,
+                                      Map<Long, Long> monthlyCheckInMap,
+                                      Map<Long, Long> monthlyCheckOutMap,
+                                      Map<Long, Long> inStatusMap,
+                                      LocalDate weekBegin, LocalDate weekEnd) {
+        Long tenantId = vo.getTenantId();
+        Long weeklyCheckIn = weeklyCheckInMap.getOrDefault(tenantId, 0L);
+        Long weeklyCheckOut = weeklyCheckOutMap.getOrDefault(tenantId, 0L);
+        Long monthlyCheckIn = monthlyCheckInMap.getOrDefault(tenantId, 0L);
+        Long monthlyCheckOut = monthlyCheckOutMap.getOrDefault(tenantId, 0L);
+
+        // 期末数取当前在住人数,期初数由「期末数 - 本周净增长」倒推
+        Long periodEndCount = inStatusMap.getOrDefault(tenantId, 0L);
+        Long weeklyNetGrowth = weeklyCheckIn - weeklyCheckOut;
+        Long periodBeginCount = Math.max(0L, periodEndCount - weeklyNetGrowth);
+
+        vo.setPeriodBegin(weekBegin);
+        vo.setPeriodEnd(weekEnd);
+        vo.setPeriodBeginCount(periodBeginCount);
+        vo.setWeeklyCheckInCount(weeklyCheckIn);
+        vo.setWeeklyCheckOutCount(weeklyCheckOut);
+        vo.setPeriodEndCount(periodEndCount);
+        vo.setWeeklyNetGrowthCount(weeklyNetGrowth);
+        vo.setMonthlyCheckInCount(monthlyCheckIn);
+        vo.setMonthlyCheckOutCount(monthlyCheckOut);
+        vo.setMonthlyNetGrowthCount(monthlyCheckIn - monthlyCheckOut);
+    }
+
+    /**
+     * 填充院区床位结构:总床位、在住床位、入住率、空闲床位、临时占用
+     *
+     * @param vo  待填充的机构数据
+     * @param bed 该机构的床位统计,可能为 null(表示无床位数据)
+     */
+    private void fillBedStructure(OperationAnalysisOverviewDynamicsRespVO vo, OperationTenantBedCountBO bed) {
+        Long totalBedCount = bed == null ? 0L : nullToZero(bed.getTotalBedCount());
+        Long occupiedBedCount = bed == null ? 0L : nullToZero(bed.getOccupiedBedCount());
+        Long emptyBedCount = bed == null ? 0L : nullToZero(bed.getEmptyBedCount());
+        Long temporaryOccupationCount = bed == null ? 0L : nullToZero(bed.getTemporaryOccupationCount());
+
+        vo.setTotalBedCount(totalBedCount);
+        vo.setOccupiedBedCount(occupiedBedCount);
+        vo.setOccupancyRate(calcOccupancyRate(occupiedBedCount, totalBedCount));
+        vo.setEmptyBedCount(emptyBedCount);
+        vo.setTemporaryOccupationCount(temporaryOccupationCount);
+        // 本周期末院内空余床位数,与空闲床位数口径一致
+        vo.setPeriodEndEmptyBedCount(emptyBedCount);
+    }
+
+    /**
+     * 计算入住率:在住床位 / 总床位,保留 2 位小数,如 {@code 75.84%}
+     *
+     * @param occupiedBedCount 在住床位数
+     * @param totalBedCount    总床位数
+     * @return 入住率百分比字符串,总床位为 0 时返回 {@code 0.00%}
+     */
+    private static String calcOccupancyRate(Long occupiedBedCount, Long totalBedCount) {
+        if (totalBedCount == null || totalBedCount <= 0) {
+            return "0.00%";
+        }
+        return BigDecimal.valueOf(nullToZero(occupiedBedCount))
+                .multiply(BigDecimal.valueOf(100))
+                .divide(BigDecimal.valueOf(totalBedCount), 2, RoundingMode.HALF_UP)
+                .toPlainString() + "%";
+    }
+
     /**
      * 解析要统计的机构ID:优先使用入参,否则取当前登录机构;集团账号展开为旗下所有机构
      *
@@ -243,6 +357,16 @@ public class OperationAnalysisServiceImpl implements OperationAnalysisService {
         return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
     }
 
+    /**
+     * 取当天最后一刻(23:59:59.999999999)
+     *
+     * @param date 日期
+     * @return 当天最后一刻
+     */
+    private static Date toDayEnd(LocalDate date) {
+        return toDate(date.plusDays(1).atStartOfDay().minusNanos(1));
+    }
+
     private static Long nullToZero(Long value) {
         return value == null ? 0L : value;
     }

+ 72 - 0
yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/OperationAnalysisMapper.xml

@@ -156,4 +156,76 @@
         ORDER BY e.tenant_id, nl.sort IS NULL, nl.sort, nl.id
     </select>
 
+    <!-- 区间入住数:长者档案的入住时间在区间内 -->
+    <select id="selectRangeCheckInCount"
+            resultType="cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantCountBO">
+        SELECT t.tenant_id AS tenantId,
+               COUNT(*)    AS count
+        FROM elderly_info t
+        WHERE t.deleted = 0
+          AND t.org_type = 1
+          AND t.check_in_time &gt;= #{begin}
+          AND t.check_in_time &lt;= #{end}
+          AND t.tenant_id IN
+        <foreach collection="tenantIds" item="tenantId" open="(" separator="," close=")">
+            #{tenantId}
+        </foreach>
+        GROUP BY t.tenant_id
+    </select>
+
+    <!-- 区间退住数:退住登记表的退住时间在区间内,且已完成 -->
+    <select id="selectRangeCheckOutCount"
+            resultType="cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantCountBO">
+        SELECT t.tenant_id AS tenantId,
+               COUNT(*)    AS count
+        FROM elderly_retreat_record t
+        WHERE t.deleted = 0
+          AND t.apply_type = 1
+          AND t.current_flag = 0
+          AND t.org_type = 1
+          AND t.retreat_date &gt;= #{begin}
+          AND t.retreat_date &lt;= #{end}
+          AND
+        <include refid="retreatFinishedCondition"/>
+          AND t.tenant_id IN
+        <foreach collection="tenantIds" item="tenantId" open="(" separator="," close=")">
+            #{tenantId}
+        </foreach>
+        GROUP BY t.tenant_id
+    </select>
+
+    <!-- 在住人数:长者档案的在住状态为在住 -->
+    <select id="selectInStatusCount"
+            resultType="cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantCountBO">
+        SELECT t.tenant_id AS tenantId,
+               COUNT(*)    AS count
+        FROM elderly_info t
+        WHERE t.deleted = 0
+          AND t.org_type = 1
+          AND t.in_status = 1
+          AND t.tenant_id IN
+        <foreach collection="tenantIds" item="tenantId" open="(" separator="," close=")">
+            #{tenantId}
+        </foreach>
+        GROUP BY t.tenant_id
+    </select>
+
+    <!-- 床位结构:按机构统计总床位、在住床位(status = 1)、空闲床位(status = 0)、临时占用(status = 2)。
+         该表无 deleted 字段,故不做删除过滤。 -->
+    <select id="selectBedStructure"
+            resultType="cn.iocoder.yudao.module.system.dal.mysql.biz.bo.OperationTenantBedCountBO">
+        SELECT t.tenant_id                                          AS tenantId,
+               COUNT(*)                                             AS totalBedCount,
+               COUNT(CASE WHEN t.status = 1 THEN 1 END)             AS occupiedBedCount,
+               COUNT(CASE WHEN t.status = 0 THEN 1 END)             AS emptyBedCount,
+               COUNT(CASE WHEN t.status = 2 THEN 1 END)             AS temporaryOccupationCount
+        FROM org_build_bed t
+        WHERE t.org_type = 1
+          AND t.tenant_id IN
+        <foreach collection="tenantIds" item="tenantId" open="(" separator="," close=")">
+            #{tenantId}
+        </foreach>
+        GROUP BY t.tenant_id
+    </select>
+
 </mapper>