统计相关接口开发

This commit is contained in:
2025-08-26 17:01:43 +08:00
parent 7d595c1f9f
commit acf191df81
37 changed files with 1905 additions and 11 deletions

View File

@@ -115,6 +115,7 @@ public class SecurityConfig
"/register",
"/captchaImage",
"/user/**",
"/stat/**",
"/query/jh/**",
"/wisdom/signature/**",
"/system/config/**",

View File

@@ -35,7 +35,7 @@ public class SysNoticeController extends BaseController
/**
* 获取通知公告列表
*/
@PreAuthorize("@ss.hasPermi('system:notice:list')")
// @PreAuthorize("@ss.hasPermi('system:notice:list')")
@GetMapping("/list")
public TableDataInfo list(SysNotice notice)
{

View File

@@ -5,10 +5,8 @@ import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.zg.project.wisdom.domain.dto.PcRkInfoBatchDTO;
import com.zg.project.wisdom.domain.dto.RefundRequestDTO;
import com.zg.project.wisdom.domain.dto.RkCancelDTO;
import com.zg.project.wisdom.domain.dto.StockOutDTO;
import com.github.pagehelper.PageHelper;
import com.zg.project.wisdom.domain.dto.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -40,9 +38,8 @@ public class RkInfoController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('wisdom:stock:list')")
@PostMapping("/list")
public TableDataInfo list(@RequestBody RkInfo rkInfo)
{
startPage();
public TableDataInfo list(@RequestBody RkInfoQueryDTO rkInfo) {
PageHelper.startPage(rkInfo.getPageNum(), rkInfo.getPageSize());
List<RkInfo> list = rkInfoService.selectRkInfoList(rkInfo);
return getDataTable(list);
}

View File

@@ -0,0 +1,170 @@
package com.zg.project.wisdom.controller;
import com.zg.framework.web.domain.AjaxResult;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.service.RkStatisticsService;
import com.zg.project.wisdom.service.WarehouseStatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@Api(tags = "库存统计")
@RestController
@RequestMapping("/stat")
public class RkStatisticsController {
@Resource
private RkStatisticsService rkStatisticsService;
@Resource
private WarehouseStatService warehouseStatService;
/** 库龄统计(未出库:>10 / >20 / >30 天value 为项目数(按 xm_no 去重) */
@ApiOperation("库龄统计(未出库:>10 / >20 / >30 天)")
@GetMapping("/age")
public AjaxResult ageStats() {
List<Map<String, Object>> stats = rkStatisticsService.getAgeStatsAsList();
return AjaxResult.success(stats);
}
/**
* 仓库-场景库位使用统计(总仓库数、每仓库场景数、每场景库位/使用率/空闲率)
* @return
*/
@GetMapping("/warehouseScene")
public AjaxResult warehouseScene() {
WarehouseSceneStatsResp resp = warehouseStatService.getWarehouseSceneStats();
return AjaxResult.success(resp);
}
/**
* 当前库存根据入库类型统计项目数,每个项目的条目数,实际入库总数,金额总和(仅未出库)
*/
@GetMapping("/type/summary")
public AjaxResult typeSummary() {
List<RkSummaryVO> list = rkStatisticsService.getTypeSummary();
return AjaxResult.success(list);
}
/**
* 县局汇总仅未出库county 非空)
*/
@GetMapping("/county/summary")
public AjaxResult countySummary() {
List<RkSummaryVO> list = rkStatisticsService.getCountySummary();
return AjaxResult.success(list);
}
/**
* 一个接口:一周天级统计(返回入库+出库)根据供电中心统计
* - 如果 start/end 都传了,就用传入值;
* - 否则end=今天start=本周周一;
*/
@ApiOperation("一周天级统计(入库+出库合并返回)")
@GetMapping("/week/daily")
public AjaxResult weekDaily(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
Map<String, Object> data = rkStatisticsService.weekDaily(start, end);
return AjaxResult.success(data);
}
/**
* 库龄统计(>30天 & >60天项目数、条目数、总金额 + 明细列表
*/
@ApiOperation("库龄统计:>30天 & >60天含汇总与明细")
@GetMapping("/age/count")
public AjaxResult age3060() {
List<Map<String, Object>> data = rkStatisticsService.getAge3060();
return AjaxResult.success(data);
}
@ApiOperation("导出库龄明细(>30天与>60天合并导出")
@PostMapping("/age/export")
public void exportAge3060(HttpServletResponse response) {
rkStatisticsService.exportAge3060(response);
}
/**
* 入库类型饼图数据接口
* 返回每个入库类型的项目数、条目数、总数量、总金额
*/
@ApiOperation("入库类型饼图数据")
@GetMapping("/type/pie")
public AjaxResult typePie() {
List<RkTypePieVO> list = rkStatisticsService.getTypePie();
return AjaxResult.success(list);
}
/**
* 按入库类型统计(带时间范围)
* 统计维度:项目数、条目数、总数量、总金额
* 时间字段rk_time自然日闭区间 [start, end]
* 参数格式:推荐 yyyy-MM-dd如 2025-01-01
*/
@ApiOperation("按入库类型统计(带时间范围:项目数/条目数/总数量/总金额)")
@GetMapping("/type/range")
public AjaxResult typeSummaryRange(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
List<RkTypePieVO> list = rkStatisticsService.getTypeSummaryByRange(start, end);
return AjaxResult.success(list);
}
@ApiOperation("按出库类型统计(带时间范围:项目数/条目数/总数量/总金额)")
@GetMapping("/type/out/range")
public AjaxResult outTypeSummaryRange(@RequestParam(required = false) String start,
@RequestParam(required = false) String end) {
List<RkTypePieVO> list = rkStatisticsService.getOutTypeSummaryByRange(start, end);
return AjaxResult.success(list);
}
@ApiOperation("本月出入库汇总(项目数/条目数/总数量/总金额)")
@GetMapping("/month/summary")
public AjaxResult monthSummary() {
RkMonthInOutSummaryVO vo = rkStatisticsService.getThisMonthInOutSummary();
return AjaxResult.success(vo);
}
@ApiOperation("应到未到统计(未到/部分未到)")
@GetMapping("/undelivered")
public AjaxResult undelivered() {
GysJhUndeliveredVO vo = rkStatisticsService.getUndeliveredSummary();
return AjaxResult.success(vo);
}
@ApiOperation("当前库存按物资类型统计(项目数/条目数/总数量/总金额)")
@GetMapping("/stock/wlType")
public AjaxResult stockByWlType() {
List<WlTypeStockStatVO> list = rkStatisticsService.getCurrentStockByWlType();
return AjaxResult.success(list);
}
/**
* 当前库存:入库时间 >30天项目数 / 条目数 / 总数量 / 总金额)
* 口径仅统计未删除、未出库is_chuku=0 或 NULL、rk_time 非空且 DATEDIFF(CURDATE(), DATE(rk_time)) > 30 的记录
*/
@ApiOperation("当前库存:入库时间>30天项目数/条目数/总数量/总金额)")
@GetMapping("/age/gt30")
public AjaxResult ageGt30() {
RkTypePieVO vo = rkStatisticsService.getAgeGt30Summary();
return AjaxResult.success(vo);
}
/**
* range: 0=最近一周(7天, 按日)2=最近半年(6个月, 按月)
* 返回每个时间桶一行bucket, 入/出库项目数&条目数)
*/
@ApiOperation("出/入库项目数与条目数(按时间桶返回)")
@GetMapping("/io/buckets")
public AjaxResult ioBuckets(@RequestParam Integer range) {
List<IOBucketVO> rows = rkStatisticsService.getIOBuckets(range);
return AjaxResult.success(rows);
}
}

View File

@@ -0,0 +1,18 @@
package com.zg.project.wisdom.domain.dto;
import com.zg.project.wisdom.domain.RkInfo;
import lombok.Data;
/**
* 库存单据分页查询 DTO
* 继承 RkInfo额外加分页参数
*/
@Data
public class RkInfoQueryDTO extends RkInfo {
/** 页码 */
private Integer pageNum;
/** 每页条数 */
private Integer pageSize;
}

View File

@@ -0,0 +1,17 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
/** 最终返回结构(汇总 + 明细) */
@Data
public class Age3060ResultVO {
/** >30 天的汇总 */
private AgeSummaryVO.Slot gt30;
/** >60 天的汇总 */
private AgeSummaryVO.Slot gt60;
/** >30 天明细 */
private List<RkAgeDetailVO> gt30List;
/** >60 天明细 */
private List<RkAgeDetailVO> gt60List;
}

View File

@@ -0,0 +1,24 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 汇总 VO包含两个 Slotgt30 / gt60 */
@Data
public class AgeSummaryVO {
@Data
public static class Slot {
/** 仅用于 resultMap 绑定(无业务含义) */
private Integer dummyId;
/** 项目数DISTINCT xm_no */
private Integer projectCount;
/** 条目数COUNT(*) */
private Integer goodsCount;
/** 总金额SUM(ht_dj * real_qty) */
private BigDecimal sumAmount;
}
private Slot gt30;
private Slot gt60;
}

View File

@@ -0,0 +1,32 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 周天级统计(按县局) */
@Data
public class DayCountyStatVO {
/**
* 日期(如果 SQL 未分日统计,该字段可能为 null
* 示例2025-08-19
*/
private String day;
/** 县局(若为空或空串会被归类为“未知县局”) */
private String xj;
/** 项目数(去重 xm_no 计数) */
private Integer projectCount;
/** 条目数明细记录总数COUNT(*) */
private Integer itemCount;
/** 数量总和SUM(real_qty) */
private Long totalQty;
/** 合同金额总和SUM(ht_dj * real_qty) */
private BigDecimal amountHt;
/** 计划金额总和(仅入库时有值,出库恒为 0 */
private BigDecimal amountPlan;
}

View File

@@ -0,0 +1,18 @@
// com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 供应计划-应到未到/部分未到 汇总 */
@Data
public class GysJhUndeliveredSummaryVO {
/** 去重项目数(按 xm_no 去重) */
private Integer projectCount;
/** 条目数(记录行数) */
private Integer itemCount;
/** 总数量jh_qty 合计,表示剩余未到数量) */
private Long totalQty;
/** 总金额ht_dj * jh_qty 合计) */
private BigDecimal amountPlan;
}

View File

@@ -0,0 +1,13 @@
// com.zg.project.wisdom.domain.vo.GysJhUndeliveredVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/** 返回体:未到 + 部分未到 + 合计 */
@Data
public class GysJhUndeliveredVO {
/** 未到货status='0' */
private GysJhUndeliveredSummaryVO undelivered;
/** 部分未到货status='2' */
private GysJhUndeliveredSummaryVO partialUndelivered;
}

View File

@@ -0,0 +1,26 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/**
* 出/入库统计结果 VO
* 每个时间桶(按日/按月)一行
*/
@Data
public class IOBucketVO {
/** 时间桶:
* range=0最近一周返回 MM-dd
* range=2最近半年返回 yyyy-MM
*/
private String bucket;
/** 入库项目数 */
private Integer inProjectCount;
/** 入库条目数 */
private Integer inItemCount;
/** 出库项目数 */
private Integer outProjectCount;
/** 出库条目数 */
private Integer outItemCount;
}

View File

@@ -0,0 +1,53 @@
package com.zg.project.wisdom.domain.vo;
import com.zg.framework.aspectj.lang.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class RkAgeDetailVO {
/* ---------- 仅用于导出分组显示,不落库 ---------- */
@Excel(name = "库龄分组") // “>30天”/“>60天”
private String ageBucket;
@Excel(name = "ID")
private Long id;
/** 项目号 / 项目描述 */
@Excel(name = "项目号")
private String xmNo;
@Excel(name = "项目描述")
private String xmMs;
/** 入库时间 & 库龄天数 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "入库时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date rkTime;
@Excel(name = "库龄(天)")
private Integer kuLingDays;
/** 单价、数量、行金额 */
@Excel(name = "单价")
private BigDecimal htDj;
@Excel(name = "数量")
private BigDecimal realQty;
@Excel(name = "行金额")
private BigDecimal amount;
/** 库位 / 托盘 / 单据号(便于追踪) */
@Excel(name = "库位")
private String pcode;
@Excel(name = "托盘")
private String trayCode;
@Excel(name = "单据号")
private String billNo;
}

View File

@@ -0,0 +1,21 @@
// com/zg/project/wisdom/vo/stat/RkAgeStatVO.java
package com.zg.project.wisdom.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "RkAgeStatVO", description = "库龄统计结果(仅未出库)")
public class RkAgeStatVO {
@ApiModelProperty("大于10天的数量")
private Integer gt10;
@ApiModelProperty("大于20天的数量")
private Integer gt20;
@ApiModelProperty("大于30天的数量")
private Integer gt30;
}

View File

@@ -0,0 +1,11 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.time.LocalDateTime;
/** 入库原始数据(用于代码侧分桶) */
@Data
public class RkInVO {
private String xmNo;
private LocalDateTime rkTime;
}

View File

@@ -0,0 +1,12 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/** 月度入/出库两段结果 */
@Data
public class RkMonthInOutSummaryVO {
/** 入库汇总rk_time */
private RkMonthSummaryVO inSummary;
/** 出库汇总ly_time, is_chuku=1 */
private RkMonthSummaryVO outSummary;
}

View File

@@ -0,0 +1,18 @@
// com.zg.project.wisdom.domain.vo.RkMonthSummaryVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 月度汇总(项目数/条目数/总数量/总金额) */
@Data
public class RkMonthSummaryVO {
/** 去重项目数(按 xm_no 去重) */
private Integer projectCount;
/** 条目数(明细行数) */
private Integer itemCount;
/** 总数量real_qty 合计) */
private Long totalQty;
/** 总金额ht_dj * real_qty 合计) */
private BigDecimal amountHt;
}

View File

@@ -0,0 +1,13 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.time.LocalDateTime;
/** 出库原始数据(用于代码侧分桶) */
@Data
public class RkOutVO {
private String xmNoCk;
private String xmNo;
private LocalDateTime lyTime;
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,24 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 县局维度汇总结果 */
@Data
public class RkSummaryVO {
/** 分组名称(如县局名、入库类型等) */
private String groupName;
/** 项目数(去重 xm_no */
private Integer projectCount;
/** 条目总数COUNT(*) */
private Integer goodsCountTotal;
/** 总金额 = SUM(ht_dj * real_qty) */
private BigDecimal sumAmount;
/** 总数量 = SUM(real_qty) */
private BigDecimal sumQty;
}

View File

@@ -0,0 +1,30 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 入库类型饼状图统计 VO
* 每个入库类型对应一条记录,用于前端饼状图展示
*/
@Data
public class RkTypePieVO {
/** 入库类型编码stock_in_type.type_code */
private String rkTypeCode;
/** 入库类型名称stock_in_type.type_name */
private String rkTypeName;
/** 项目数量(去重 xm_no */
private Integer projectCount;
/** 条目总数COUNT(*) */
private Integer goodsCountTotal;
/** 总数量SUM real_qty */
private BigDecimal sumQty;
/** 总金额SUM ht_dj * real_qty */
private BigDecimal sumAmount;
}

View File

@@ -0,0 +1,31 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 入库类型 + 项目 明细VO
* 用于统计每种入库类型下的每个项目:
* - 项目信息
* - 货物数量
* - 合同单价合计
* - 实际入库数量合计
* - 金额合计
*/
@Data
public class RkTypeProjectDetailVO {
/** 入库类型rk_type */
private Integer rkType;
/** 项目号xm_no */
private String projectNo;
/** 项目名称/描述xm_ms */
private String projectName;
/** 该项目下的货物条数 */
private Integer goodsCount;
/** 合同单价合计 */
private BigDecimal sumHtDj;
/** 实际入库数量合计 */
private BigDecimal sumRealQty;
/** 金额合计 = ht_dj * real_qty */
private BigDecimal sumAmount;
}

View File

@@ -0,0 +1,24 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
/**
* 某入库类型下,单个项目的货物条目数
*/
@Data
public class RkTypeProjectGoodsCountVO {
/** 入库类型(冗余用于归并) */
private Integer rkType;
/** 县局类型统计时可为null */
private String county;
/** 项目号xm_no */
private String projectNo;
/** 项目名称/描述xm_ms取一个示例值 */
private String projectName;
/** 该项目的货物条目数COUNT(*) */
private Integer goodsCount;
}

View File

@@ -0,0 +1,44 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 入库类型汇总VO仅统计未出库 is_chuku = 0
* 每种入库类型下聚合:
* - projectCount项目数量去重xm_no
* - goodsCountTotal货物条目总数记录条数
* - sumHtDj合同单价合计
* - sumRealQty实际入库数量合计
* - sumAmount金额合计 = ht_dj * real_qty
* - avgGoodsPerProject每项目平均条目数 = goodsCountTotal / projectCount保留2位小数
* - projectGoodsCounts每个项目的“条目数清单”你要的关键补充
*/
@Data
public class RkTypeSummaryVO {
/** 入库类型rk_type */
private String rkType;
/** 县局类型统计时可为null */
private String county;
/** 项目数量去重xm_no */
private Integer projectCount;
/** 货物条目总数(该类型下的记录条数) */
private Integer goodsCountTotal;
/** 合同单价合计 */
private BigDecimal sumHtDj;
/** 实际入库数量合计 */
private BigDecimal sumRealQty;
/** 金额合计 = ht_dj * real_qty */
private BigDecimal sumAmount;
/** 每个项目的条目数清单(仅含条目数;如需金额/数量可再扩展) */
private List<RkTypeProjectGoodsCountVO> projectGoodsCounts;
}

View File

@@ -0,0 +1,16 @@
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
@Data
public class SceneUsageVO {
private String sceneCode;
private String sceneName;
private Integer totalPositions; // 该场景库位总数
private Integer usedPositions; // 已使用(未出库)
private Integer freePositions; // 空闲 = total - used
private String usageRatePercent; // 使用率xx.xx
private String freeRatePercent; // 空闲率xx.xx
}

View File

@@ -0,0 +1,11 @@
// src/main/java/com/zg/project/wisdom/domain/vo/stat/WarehouseSceneStatsResp.java
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class WarehouseSceneStatsResp {
private Integer totalWarehouses; // 启用仓库总数
private List<WarehouseStatVO> items;
}

View File

@@ -0,0 +1,20 @@
// src/main/java/com/zg/project/wisdom/domain/vo/stat/WarehouseStatVO.java
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class WarehouseStatVO {
private String warehouseCode;
private String warehouseName;
private Integer sceneCount; // 场景数量
private Integer totalPositions; // 仓库汇总-总库位
private Integer usedPositions; // 仓库汇总-已使用
private Integer freePositions; // 仓库汇总-空闲
private String usageRatePercent; // 仓库汇总-使用率
private String freeRatePercent; // 仓库汇总-空闲率
private List<SceneUsageVO> scenes; // 场景明细
}

View File

@@ -0,0 +1,20 @@
// com.zg.project.wisdom.domain.vo.WlTypeStockStatVO
package com.zg.project.wisdom.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/** 当前库存-按物资类型统计 */
@Data
public class WlTypeStockStatVO {
/** 物资类型wl_type */
private String typeName;
/** 去重项目数(按 xm_no */
private Integer projectCount;
/** 条目数(明细行数) */
private Integer itemCount;
/** 总数量real_qty 合计) */
private Long totalQty;
/** 总金额ht_dj * real_qty 合计) */
private BigDecimal amountHt;
}

View File

@@ -0,0 +1,102 @@
package com.zg.project.wisdom.mapper;
import com.zg.project.wisdom.domain.vo.*;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface RkStatisticsMapper {
/** 库龄统计(未出库):按项目数计数(去重 xm_no */
RkAgeStatVO selectAgeStats();
/**
* 入库类型汇总(仅未出库)
* @return 汇总结果列表
*/
List<RkTypeSummaryVO> selectRkTypeSummary();
/**
* 按入库类型 + 项目 统计【每项目的条目数】
* (用于回填到类型汇总的 projectGoodsCounts 中)
*/
List<RkTypeProjectGoodsCountVO> selectProjectGoodsCountByType();
/** 入库类型汇总未出库rk_type 非空) */
List<RkSummaryVO> selectTypeSummary();
/**
* 县局汇总(未出库,县局非空)
*/
List<RkSummaryVO> selectCountySummary();
/** 入库(每天+县局):按 rk_time */
List<DayCountyStatVO> selectWeekDailyIn(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/** 出库(每天+县局):按 ly_time */
List<DayCountyStatVO> selectWeekDailyOut(@Param("startDate") String startDate,
@Param("endDate") String endDate);
/** 汇总(>30 & >60项目数、条目数、总金额 */
AgeSummaryVO selectAge3060Summary();
/** 明细列表:返回超过 minDays 天的明细minDays 取 30 或 60 */
List<RkAgeDetailVO> selectAgeDetails(@Param("minDays") int minDays);
/**
* 按入库类型统计饼状图数据
* @return 每个入库类型的项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> selectTypePie();
/**
* 按入库类型统计(联查 stock_in_type时间范围过滤
* 仅统计未删除、未出库
*/
List<RkTypePieVO> selectTypeSummaryByRange(@Param("start") String start,
@Param("end") String end);
/**
* 按出库类型统计(时间范围;仅 is_chuku=1
* 维度:项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> selectOutTypeSummaryByRange(@Param("start") String start,
@Param("end") String end);
/** 本月入库汇总(按 rk_time 过滤) */
RkMonthSummaryVO selectMonthlyInSummary(@Param("start") String start,
@Param("end") String end);
/** 本月出库汇总is_chuku=1 且按 ly_time 过滤) */
RkMonthSummaryVO selectMonthlyOutSummary(@Param("start") String start,
@Param("end") String end);
/** 应到未到status='0' */
GysJhUndeliveredSummaryVO selectUndelivered();
/** 部分未到status='2' */
GysJhUndeliveredSummaryVO selectPartialUndelivered();
/** 当前库存:按物资类型统计(过滤 wl_type 空/无) */
List<WlTypeStockStatVO> selectCurrentStockByWlType();
/** 当前库存:入库时间 >30 天的四项汇总SQL 聚合计算) */
RkTypePieVO selectAgeGt30Summary();
/** 入库:在 [startTs, endTs) 时间范围内的原始行(仅最小过滤) */
List<RkInVO> selectInRange(@Param("startTs") String startTs,
@Param("endTs") String endTs);
/** 出库:在 [startTs, endTs) 时间范围内、is_chuku=1 的原始行(仅最小过滤) */
List<RkOutVO> selectOutRange(@Param("startTs") String startTs,
@Param("endTs") String endTs);
}

View File

@@ -0,0 +1,20 @@
// src/main/java/com/zg/project/wisdom/mapper/WarehouseStatMapper.java
package com.zg.project.wisdom.mapper;
import com.zg.project.wisdom.domain.vo.SceneUsageVO;
import com.zg.project.wisdom.domain.vo.WarehouseStatVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface WarehouseStatMapper {
/** 启用仓库列表(只取 code、name用 VO 承载,不引入新类) */
List<WarehouseStatVO> selectEnabledWarehouses();
/** 指定仓库下的场景库位统计(总库位 & 未出库占用库位) */
List<SceneUsageVO> selectSceneUsageByWarehouse(@Param("warehouseCode") String warehouseCode);
/** 启用仓库总数(也可用 selectEnabledWarehouses().size() */
Integer countEnabledWarehouses();
}

View File

@@ -0,0 +1,83 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.vo.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/** 首页统计服务 */
public interface RkStatisticsService {
/** 库龄统计(仅未出库) */
// RkAgeStatVO getAgeStats();
/** 新增:返回 Map */
List<Map<String, Object>> getAgeStatsAsList();
/** 入库类型汇总(未出库) */
List<RkSummaryVO> getTypeSummary();
/**
* 县局汇总(未出库)
*/
List<RkSummaryVO> getCountySummary();
/**
* 一周天级统计(一个接口返回入库+出库)
* @param start 可空yyyy-MM-dd
* @param end 可空yyyy-MM-dd
* @return { "in": List<DayCountyStatVO>, "out": List<DayCountyStatVO> }
*/
Map<String, Object> weekDaily(String start, String end);
/**
* 库龄统计(>30天 & >60天项目数、条目数、总金额 + 两个明细列表
*/
Age3060ResultVO getAge3060Detail();
List<Map<String, Object>> getAge3060();
/** 导出库龄明细(>30 与 >60 合并导出) */
void exportAge3060(HttpServletResponse response);
/**
* 获取入库类型饼状图数据
* @return 入库类型饼图统计列表
*/
List<RkTypePieVO> getTypePie();
/**
* 按入库类型统计(时间范围)
* @param start 开始日期(建议 yyyy-MM-dd
* @param end 结束日期(建议 yyyy-MM-dd
* @return 每个入库类型的项目数、条目数、总数量、总金额
*/
List<RkTypePieVO> getTypeSummaryByRange(String start, String end);
/**
* 按出库类型统计(时间范围)
* 口径仅统计已出库is_chuku = 1排除删除出库类型非空
* 时间字段ly_time自然日闭区间 [start, end],参数建议 yyyy-MM-dd
*/
List<RkTypePieVO> getOutTypeSummaryByRange(String start, String end);
/** 本月入/出库汇总(项目数/条目数/总数量/总金额) */
RkMonthInOutSummaryVO getThisMonthInOutSummary();
/** 供应计划:应到未到统计(未到、部分未到、合计) */
GysJhUndeliveredVO getUndeliveredSummary();
/** 当前库存:按物资类型统计 */
List<WlTypeStockStatVO> getCurrentStockByWlType();
/** 当前库存 “入库时间 >30 天”的四项汇总 */
RkTypePieVO getAgeGt30Summary();
/**
* range: 0=最近一周(日)2=最近半年(月)
* 返回每个时间桶的一行数据(固定 7 行或 6 行)
*/
List<IOBucketVO> getIOBuckets(Integer range);
}

View File

@@ -0,0 +1,9 @@
package com.zg.project.wisdom.service;
import com.zg.project.wisdom.domain.vo.WarehouseSceneStatsResp;
public interface WarehouseStatService {
WarehouseSceneStatsResp getWarehouseSceneStats();
}

View File

@@ -178,10 +178,10 @@ public class DdTaskServiceImpl implements IDdTaskService {
wcsParam.put("Quantity", "");
} else {
String materialCode = task.getMid();
String quantity = task.getNum().toString();
// String quantity = task.getNum().toString();
String specification = mtdMapper.selectWlmsByWlbh(materialCode);
wcsParam.put("MaterialCode", materialCode);
wcsParam.put("Quantity", quantity);
// wcsParam.put("Quantity", quantity);
wcsParam.put("Specification", specification == null ? "" : specification);
}

View File

@@ -647,7 +647,7 @@ public class RkInfoServiceImpl implements IRkInfoService
update.setBillNoCk(billNo);
update.setCkType(dto.getCkType());
update.setTeamCode(dto.getTeamCode());
update.setLyTime(now); // ❗使用后端生成时间
update.setLyTime(now);
update.setCkLihuoY(dto.getCkLihuoY());
update.setCkRemark(item.getCkRemark());
update.setXmNoCk(dto.getXmNoCk());

View File

@@ -0,0 +1,391 @@
package com.zg.project.wisdom.service.impl;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.zg.common.exception.ServiceException;
import com.zg.common.utils.DateUtils;
import com.zg.common.utils.StringUtils;
import com.zg.common.utils.poi.ExcelUtil;
import com.zg.project.wisdom.domain.vo.*;
import com.zg.project.wisdom.mapper.GysJhMapper;
import com.zg.project.wisdom.mapper.RkStatisticsMapper;
import com.zg.project.wisdom.service.RkStatisticsService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
@Service
public class RkStatisticsServiceImpl implements RkStatisticsService {
@Resource
private RkStatisticsMapper rkStatisticsMapper;
@Override
public List<Map<String, Object>> getAgeStatsAsList() {
// 这里的 gt10/gt20/gt30 均为“项目数”COUNT DISTINCT xm_no
RkAgeStatVO vo = rkStatisticsMapper.selectAgeStats();
List<Map<String, Object>> list = new ArrayList<>(3);
list.add(Map.of("name", ">10天", "value", vo.getGt10()));
list.add(Map.of("name", ">20天", "value", vo.getGt20()));
list.add(Map.of("name", ">30天", "value", vo.getGt30()));
return list;
}
/**
* 库存类型统计(仅未出库)
*/
@Override
public List<RkSummaryVO> getTypeSummary() {
List<RkSummaryVO> list = rkStatisticsMapper.selectTypeSummary();
return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
}
/**
* 县局统计(仅未出库)
*/
@Override
public List<RkSummaryVO> getCountySummary() {
List<RkSummaryVO> list = rkStatisticsMapper.selectCountySummary();
return CollectionUtils.isEmpty(list) ? Collections.emptyList() : list;
}
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public Map<String, Object> weekDaily(String start, String end) {
// 如果携带了 start 和 end就直接用否则end=今天start=本周周一
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
LocalDate today = LocalDate.now();
LocalDate monday = today.with(DayOfWeek.MONDAY);
start = monday.format(F);
end = today.format(F);
}
Map<String, Object> ret = new HashMap<>(2);
ret.put("in", rkStatisticsMapper.selectWeekDailyIn(start, end));
ret.put("out", rkStatisticsMapper.selectWeekDailyOut(start, end));
return ret;
}
@Override
public Age3060ResultVO getAge3060Detail() {
// 1) 汇总(同时返回 >30 / >60 的项目数、条目数、总金额)
AgeSummaryVO summary = rkStatisticsMapper.selectAge3060Summary();
// 2) 明细(>30
List<RkAgeDetailVO> gt30List = rkStatisticsMapper.selectAgeDetails(30);
// 3) 明细(>60
List<RkAgeDetailVO> gt60List = rkStatisticsMapper.selectAgeDetails(60);
// 4) 组装返回
Age3060ResultVO vo = new Age3060ResultVO();
vo.setGt30(summary.getGt30());
vo.setGt60(summary.getGt60());
vo.setGt30List(gt30List);
vo.setGt60List(gt60List);
return vo;
}
@Override
public List<Map<String, Object>> getAge3060() {
// 1) 汇总(同时返回 >30 / >60 的项目数、条目数、总金额)
AgeSummaryVO summary = rkStatisticsMapper.selectAge3060Summary();
// 2) 用 List<Map> 封装成数组结构
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> gt30 = new HashMap<>();
gt30.put("name", ">30天");
gt30.put("value", summary.getGt30().getGoodsCount()); // 这里取“货物数量”作 value
list.add(gt30);
Map<String, Object> gt60 = new HashMap<>();
gt60.put("name", ">60天");
gt60.put("value", summary.getGt60().getGoodsCount());
list.add(gt60);
return list;
}
/**
* 导出库龄明细(>30天 与 >60天 合并导出为一张表)
* - 所有业务逻辑均在 Service 层完成
* - 使用若依 ExcelUtil + @Excel 注解导出
*/
@Override
public void exportAge3060(HttpServletResponse response) {
// 1) 调用已有统计逻辑
Age3060ResultVO res = this.getAge3060Detail();
// 2) 合并两组明细,加上导出用的分组字段
List<RkAgeDetailVO> out = new ArrayList<>();
if (res.getGt30List() != null) {
for (RkAgeDetailVO row : res.getGt30List()) {
normalizeRowForExport(row, ">30天");
out.add(row);
}
}
if (res.getGt60List() != null) {
for (RkAgeDetailVO row : res.getGt60List()) {
normalizeRowForExport(row, ">60天");
out.add(row);
}
}
// 3) 导出 Excel
ExcelUtil<RkAgeDetailVO> util = new ExcelUtil<>(RkAgeDetailVO.class);
util.exportExcel(response, out, "库龄明细");
}
private void normalizeRowForExport(RkAgeDetailVO row, String bucket) {
row.setAgeBucket(bucket);
if (row.getAmount() == null) {
BigDecimal dj = row.getHtDj() == null ? BigDecimal.ZERO : row.getHtDj();
BigDecimal qty = row.getRealQty() == null ? BigDecimal.ZERO : row.getRealQty();
row.setAmount(dj.multiply(qty));
}
}
/**
* 获取入库类型饼状图数据
*/
@Override
public List<RkTypePieVO> getTypePie() {
return rkStatisticsMapper.selectTypePie();
}
@Override
public List<RkTypePieVO> getTypeSummaryByRange(String start, String end) {
// 基础入参校验(避免全表扫描/人为误操作)
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
throw new ServiceException("start/end 不能为空建议格式yyyy-MM-dd");
}
return rkStatisticsMapper.selectTypeSummaryByRange(start, end);
}
@Override
public List<RkTypePieVO> getOutTypeSummaryByRange(String start, String end) {
if (StringUtils.isBlank(start) || StringUtils.isBlank(end)) {
throw new ServiceException("start/end 不能为空建议格式yyyy-MM-dd");
}
return rkStatisticsMapper.selectOutTypeSummaryByRange(start, end);
}
private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public RkMonthInOutSummaryVO getThisMonthInOutSummary() {
// 本月起止: [YYYY-MM-01 00:00:00, 下月01 00:00:00)
LocalDate firstDay = LocalDate.now().withDayOfMonth(1);
LocalDate nextMonthFirst = firstDay.plusMonths(1);
String start = firstDay.atStartOfDay().format(DF);
String end = nextMonthFirst.atStartOfDay().format(DF);
RkMonthSummaryVO inSummary = rkStatisticsMapper.selectMonthlyInSummary(start, end);
RkMonthSummaryVO outSummary = rkStatisticsMapper.selectMonthlyOutSummary(start, end);
// null 兜底
if (inSummary == null) {
inSummary = new RkMonthSummaryVO();
}
if (outSummary == null) {
outSummary = new RkMonthSummaryVO();
}
RkMonthInOutSummaryVO vo = new RkMonthInOutSummaryVO();
vo.setInSummary(inSummary);
vo.setOutSummary(outSummary);
return vo;
}
@Override
public GysJhUndeliveredVO getUndeliveredSummary() {
// 分别查询未到status=0、部分未到status=2
GysJhUndeliveredSummaryVO undelivered = rkStatisticsMapper.selectUndelivered();
GysJhUndeliveredSummaryVO partial = rkStatisticsMapper.selectPartialUndelivered();
// 对象兜底
if (undelivered == null) undelivered = new GysJhUndeliveredSummaryVO();
if (partial == null) partial = new GysJhUndeliveredSummaryVO();
// 字段兜底(防止仍有 null 透出)
if (undelivered.getProjectCount() == null) undelivered.setProjectCount(0);
if (undelivered.getItemCount() == null) undelivered.setItemCount(0);
if (undelivered.getTotalQty() == null) undelivered.setTotalQty(0L);
if (undelivered.getAmountPlan() == null) undelivered.setAmountPlan(BigDecimal.ZERO);
if (partial.getProjectCount() == null) partial.setProjectCount(0);
if (partial.getItemCount() == null) partial.setItemCount(0);
if (partial.getTotalQty() == null) partial.setTotalQty(0L);
if (partial.getAmountPlan() == null) partial.setAmountPlan(BigDecimal.ZERO);
// 返回
GysJhUndeliveredVO vo = new GysJhUndeliveredVO();
vo.setUndelivered(undelivered);
vo.setPartialUndelivered(partial);
return vo;
}
@Override
public List<WlTypeStockStatVO> getCurrentStockByWlType() {
List<WlTypeStockStatVO> list = rkStatisticsMapper.selectCurrentStockByWlType();
return list == null ? Collections.emptyList() : list;
}
@Override
public RkTypePieVO getAgeGt30Summary() {
RkTypePieVO vo = rkStatisticsMapper.selectAgeGt30Summary();
if (vo == null) vo = new RkTypePieVO();
// 兜底,避免前端拿到 null
if (vo.getProjectCount() == null) vo.setProjectCount(0);
if (vo.getGoodsCountTotal() == null) vo.setGoodsCountTotal(0);
if (vo.getSumQty() == null) vo.setSumQty(BigDecimal.ZERO);
if (vo.getSumAmount() == null) vo.setSumAmount(BigDecimal.ZERO);
// 本接口不设置 rkTypeCode / rkTypeName
vo.setRkTypeCode(null);
vo.setRkTypeName(null);
return vo;
}
private static final DateTimeFormatter TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("MM-dd");
private static final DateTimeFormatter MON_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
@Override
public List<IOBucketVO> getIOBuckets(Integer range) {
if (range == null || (range != 0 && range != 2)) {
throw new IllegalArgumentException("range 仅支持 0(最近一周) 或 2(最近半年)");
}
Date now = DateUtils.getNowDate();
LocalDateTime nowLdt = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
if (range == 0) {
// ===== 最近一周(按日),输出 MM-dd =====
LocalDate endDay = nowLdt.toLocalDate();
LocalDate startDay = endDay.minusDays(6);
LocalDateTime startTs = startDay.atStartOfDay();
LocalDateTime endTs = endDay.plusDays(1).atStartOfDay();
List<RkInVO> inRows = rkStatisticsMapper.selectInRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
List<RkOutVO> outRows = rkStatisticsMapper.selectOutRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
// 改为以 LocalDate 作为桶键,防止键格式与输出格式不一致导致匹配失败
LinkedHashMap<LocalDate, BucketAcc> acc = new LinkedHashMap<>();
for (int i = 0; i < 7; i++) {
LocalDate d = startDay.plusDays(i);
acc.put(d, new BucketAcc());
}
// 入库聚合(按 LocalDate
for (RkInVO r : inRows) {
if (r.getRkTime() == null) continue;
LocalDate d = r.getRkTime().toLocalDate();
BucketAcc b = acc.get(d);
if (b == null) continue;
b.inItemCount++;
if (StringUtils.isNotBlank(r.getXmNo())) {
b.inProjects.add(r.getXmNo().trim());
}
}
// 出库聚合(按 LocalDately_time 优先,否则 update_time
for (RkOutVO r : outRows) {
LocalDateTime t = (r.getLyTime() != null) ? r.getLyTime() : r.getUpdateTime();
if (t == null) continue;
LocalDate d = t.toLocalDate();
BucketAcc b = acc.get(d);
if (b == null) continue;
b.outItemCount++;
String proj = StringUtils.isNotBlank(r.getXmNoCk()) ? r.getXmNoCk().trim() : r.getXmNo();
if (StringUtils.isNotBlank(proj)) b.outProjects.add(proj);
}
ArrayList<IOBucketVO> out = new ArrayList<>(7);
for (Map.Entry<LocalDate, BucketAcc> e : acc.entrySet()) {
IOBucketVO row = new IOBucketVO();
row.setBucket(e.getKey().format(DAY_FMT)); // 仅返回 MM-dd
row.setInProjectCount(e.getValue().inProjects.size());
row.setInItemCount(e.getValue().inItemCount);
row.setOutProjectCount(e.getValue().outProjects.size());
row.setOutItemCount(e.getValue().outItemCount);
out.add(row);
}
return out;
} else {
// ===== 最近半年(按月),输出 yyyy-MM =====
YearMonth endYm = YearMonth.from(nowLdt.toLocalDate());
YearMonth startYm = endYm.minusMonths(5);
LocalDateTime startTs = startYm.atDay(1).atStartOfDay();
LocalDateTime endTs = endYm.plusMonths(1).atDay(1).atStartOfDay();
List<RkInVO> inRows = rkStatisticsMapper.selectInRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
List<RkOutVO> outRows = rkStatisticsMapper.selectOutRange(startTs.format(TS_FMT), endTs.format(TS_FMT));
LinkedHashMap<YearMonth, BucketAcc> acc = new LinkedHashMap<>();
for (int i = 0; i < 6; i++) {
YearMonth ym = startYm.plusMonths(i);
acc.put(ym, new BucketAcc());
}
// 入库按月
for (RkInVO r : inRows) {
if (r.getRkTime() == null) continue;
YearMonth ym = YearMonth.from(r.getRkTime().toLocalDate());
BucketAcc b = acc.get(ym);
if (b == null) continue;
b.inItemCount++;
if (StringUtils.isNotBlank(r.getXmNo())) {
b.inProjects.add(r.getXmNo().trim());
}
}
// 出库按月
for (RkOutVO r : outRows) {
LocalDateTime t = (r.getLyTime() != null) ? r.getLyTime() : r.getUpdateTime();
if (t == null) continue;
YearMonth ym = YearMonth.from(t.toLocalDate());
BucketAcc b = acc.get(ym);
if (b == null) continue;
b.outItemCount++;
String proj = StringUtils.isNotBlank(r.getXmNoCk()) ? r.getXmNoCk().trim() : r.getXmNo();
if (StringUtils.isNotBlank(proj)) b.outProjects.add(proj);
}
ArrayList<IOBucketVO> out = new ArrayList<>(6);
for (Map.Entry<YearMonth, BucketAcc> e : acc.entrySet()) {
IOBucketVO row = new IOBucketVO();
row.setBucket(e.getKey().format(MON_FMT)); // yyyy-MM
row.setInProjectCount(e.getValue().inProjects.size());
row.setInItemCount(e.getValue().inItemCount);
row.setOutProjectCount(e.getValue().outProjects.size());
row.setOutItemCount(e.getValue().outItemCount);
out.add(row);
}
return out;
}
}
/** 桶内累加器 */
private static class BucketAcc {
int inItemCount = 0;
int outItemCount = 0;
Set<String> inProjects = new HashSet<>();
Set<String> outProjects = new HashSet<>();
}
}

View File

@@ -0,0 +1,80 @@
package com.zg.project.wisdom.service.impl;
import com.zg.project.wisdom.domain.vo.SceneUsageVO;
import com.zg.project.wisdom.domain.vo.WarehouseSceneStatsResp;
import com.zg.project.wisdom.domain.vo.WarehouseStatVO;
import com.zg.project.wisdom.mapper.WarehouseStatMapper;
import com.zg.project.wisdom.service.WarehouseStatService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
@Service
public class WarehouseStatServiceImpl implements WarehouseStatService {
@Resource
private WarehouseStatMapper warehouseStatMapper;
@Override
public WarehouseSceneStatsResp getWarehouseSceneStats() {
// 1) 启用仓库列表 & 总数
List<WarehouseStatVO> warehouses = warehouseStatMapper.selectEnabledWarehouses();
int totalWarehouses = warehouses == null ? 0 : warehouses.size();
// 2) 逐仓库查询场景统计并计算仓库级汇总与百分比
if (warehouses != null) {
for (WarehouseStatVO wh : warehouses) {
String code = wh.getWarehouseCode();
// 场景列表(每个场景含总库位、已使用)
List<SceneUsageVO> scenes = warehouseStatMapper.selectSceneUsageByWarehouse(code);
if (scenes == null) scenes = new ArrayList<>();
// 补充每个场景的空闲与百分比
int whTotal = 0, whUsed = 0, whFree = 0;
for (SceneUsageVO s : scenes) {
int t = nvl(s.getTotalPositions());
int u = nvl(s.getUsedPositions());
int f = Math.max(t - u, 0);
s.setFreePositions(f);
s.setUsageRatePercent(pct(u, t));
s.setFreeRatePercent(pct(f, t));
whTotal += t;
whUsed += u;
whFree += f;
}
// 仓库级汇总
wh.setScenes(scenes);
wh.setSceneCount(scenes.size());
wh.setTotalPositions(whTotal);
wh.setUsedPositions(whUsed);
wh.setFreePositions(whFree);
wh.setUsageRatePercent(pct(whUsed, whTotal));
wh.setFreeRatePercent(pct(whFree, whTotal));
}
}
// 3) 组装响应
WarehouseSceneStatsResp resp = new WarehouseSceneStatsResp();
resp.setTotalWarehouses(totalWarehouses);
resp.setItems(warehouses == null ? new ArrayList<>() : warehouses);
return resp;
}
private static int nvl(Integer v) { return v == null ? 0 : v; }
private static String pct(int part, int total) {
if (total <= 0 || part <= 0) return "0.00";
return new BigDecimal(part)
.multiply(new BigDecimal("100"))
.divide(new BigDecimal(total), 2, RoundingMode.HALF_UP)
.toPlainString();
}
}

View File

@@ -0,0 +1,487 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zg.project.wisdom.mapper.RkStatisticsMapper">
<!-- 库龄统计未出库按项目数统计COUNT DISTINCT xm_no -->
<select id="selectAgeStats" resultType="com.zg.project.wisdom.domain.vo.RkAgeStatVO">
SELECT
/* >10 天的项目数(至少一条记录满足 >10 天的 xm_no 个数) */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 10
THEN ri.xm_no END) AS gt10,
/* >20 天的项目数 */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 20
THEN ri.xm_no END) AS gt20,
/* >30 天的项目数 */
COUNT(DISTINCT CASE
WHEN ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 30
THEN ri.xm_no END) AS gt30
FROM rk_info ri
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
</select>
<!--
入库类型汇总SQL
统计每种入库类型(rk_type)下:
- 项目数量去重xm_no
- 合同单价合计(sumHtDj)
- 实际入库数量合计(sumRealQty)
- 金额合计(sumAmount = ht_dj * real_qty)
条件:
- 未删除(is_delete = 0)
- 未出库(is_chuku = 0)
-->
<select id="selectRkTypeSummary" resultType="com.zg.project.wisdom.domain.vo.RkTypeSummaryVO">
SELECT
ri.rk_type AS rkType,
COUNT(DISTINCT ri.xm_no) AS projectCount,
SUM(IFNULL(ri.ht_dj, 0)) AS sumHtDj,
SUM(IFNULL(ri.real_qty, 0)) AS sumRealQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
GROUP BY ri.rk_type
ORDER BY rkType
</select>
<!--
2) 入库类型 × 项目:统计每个项目的“条目数”
字段:
- rkType入库类型
- projectNoxm_no
- projectNameMAX(xm_ms)
- goodsCountCOUNT(*)
过滤is_delete=0 AND is_chuku=0
-->
<select id="selectProjectGoodsCountByType"
resultType="com.zg.project.wisdom.domain.vo.RkTypeProjectGoodsCountVO">
SELECT
ri.rk_type AS rkType,
ri.xm_no AS projectNo,
MAX(ri.xm_ms) AS projectName,
COUNT(*) AS goodsCount
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
GROUP BY ri.rk_type, ri.xm_no
ORDER BY rkType, projectNo
</select>
<!-- 入库类型汇总(未出库 & 过滤 NULL/空串/“无”) -->
<select id="selectTypeSummary" resultType="com.zg.project.wisdom.domain.vo.RkSummaryVO">
SELECT
sit.type_name AS groupName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
-- 入库类型过滤:剔除 NULL / 空串 / '无'
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) &lt;&gt; ''
AND ri.rk_type &lt;&gt; '无'
-- 名称做一层过滤,避免出现“无”或空的分组
AND sit.type_name IS NOT NULL
AND TRIM(sit.type_name) &lt;&gt; ''
AND sit.type_name &lt;&gt; '无'
GROUP BY sit.type_name
ORDER BY groupName
</select>
<!-- 县局汇总(未出库 & county 非空) -->
<select id="selectCountySummary" resultType="com.zg.project.wisdom.domain.vo.RkSummaryVO">
SELECT
ri.xj AS groupName, -- 县局
COUNT(DISTINCT ri.xm_no) AS projectCount, -- 项目数
COUNT(*) AS goodsCountTotal, -- 条目总数
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount -- 总金额
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.xj IS NOT NULL
AND TRIM(ri.xj) &lt;&gt; ''
GROUP BY ri.xj
ORDER BY groupName
</select>
<!-- 入库(整段时间 + 县局rk_time 在区间内xj 为空归类为“未知县局” -->
<select id="selectWeekDailyIn" resultType="com.zg.project.wisdom.domain.vo.DayCountyStatVO">
SELECT
COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局') AS xj,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt,
SUM(COALESCE(ri.jh_amt, 0)) AS amountPlan
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time BETWEEN #{startDate} AND #{endDate}
GROUP BY COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局')
ORDER BY xj
</select>
<!-- 出库(整段时间 + 县局ly_time 在区间内;只统计已出库 is_chuku=1xj 为空归类为“未知县局” -->
<select id="selectWeekDailyOut" resultType="com.zg.project.wisdom.domain.vo.DayCountyStatVO">
SELECT
COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局') AS xj,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt,
0 AS amountPlan
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND ri.ly_time BETWEEN #{startDate} AND #{endDate}
GROUP BY COALESCE(NULLIF(TRIM(ri.xj), ''), '未知县局')
ORDER BY xj
</select>
<!-- ======================
结果映射:汇总 AgeSummaryVO
====================== -->
<resultMap id="AgeSummaryMap" type="com.zg.project.wisdom.domain.vo.AgeSummaryVO">
<!-- 通过两个 association 绑定到内部 Slot 对象 -->
<association property="gt30" javaType="com.zg.project.wisdom.domain.vo.AgeSummaryVO$Slot">
<id property="dummyId" column="gt30_dummy_id"/>
<result property="projectCount" column="gt30ProjectCount"/>
<result property="goodsCount" column="gt30GoodsCount"/>
<result property="sumAmount" column="gt30Amount"/>
</association>
<association property="gt60" javaType="com.zg.project.wisdom.domain.vo.AgeSummaryVO$Slot">
<id property="dummyId" column="gt60_dummy_id"/>
<result property="projectCount" column="gt60ProjectCount"/>
<result property="goodsCount" column="gt60GoodsCount"/>
<result property="sumAmount" column="gt60Amount"/>
</association>
</resultMap>
<!-- 汇总查询:>30 / >60 的项目数、条目数、总金额 -->
<select id="selectAge3060Summary" resultMap="AgeSummaryMap">
SELECT
1 AS gt30_dummy_id,
(SELECT COUNT(DISTINCT ri.xm_no)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30ProjectCount,
(SELECT COUNT(*)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30GoodsCount,
(SELECT SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0))
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 30) AS gt30Amount,
1 AS gt60_dummy_id,
(SELECT COUNT(DISTINCT ri.xm_no)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60ProjectCount,
(SELECT COUNT(*)
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60GoodsCount,
(SELECT SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0))
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > 60) AS gt60Amount
FROM dual
</select>
<!-- ======================
结果映射:明细 RkAgeDetailVO
====================== -->
<resultMap id="RkAgeDetailMap" type="com.zg.project.wisdom.domain.vo.RkAgeDetailVO">
<id property="id" column="id"/>
<result property="xmNo" column="xmNo"/>
<result property="xmMs" column="xmMs"/>
<result property="rkTime" column="rkTime"/>
<result property="kuLingDays" column="kuLingDays"/>
<result property="htDj" column="htDj"/>
<result property="realQty" column="realQty"/>
<result property="amount" column="amount"/>
<result property="pcode" column="pcode"/>
<result property="trayCode" column="trayCode"/>
<result property="billNo" column="billNo"/>
</resultMap>
<!-- 明细查询:超过 minDays 天的所有未出库记录 -->
<select id="selectAgeDetails" parameterType="int" resultMap="RkAgeDetailMap">
SELECT
ri.id,
ri.xm_no AS xmNo,
ri.xm_ms AS xmMs,
ri.rk_time AS rkTime,
DATEDIFF(CURDATE(), ri.rk_time) AS kuLingDays,
ri.ht_dj AS htDj,
ri.real_qty AS realQty,
IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0) AS amount,
ri.pcode AS pcode,
ri.tray_code AS trayCode,
ri.bill_no AS billNo
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), ri.rk_time) > #{minDays}
ORDER BY ri.rk_time ASC
</select>
<resultMap id="RkTypePieMap" type="com.zg.project.wisdom.domain.vo.RkTypePieVO">
<result property="rkTypeCode" column="rkTypeCode"/> <!-- 类型编码 -->
<result property="rkTypeName" column="rkTypeName"/> <!-- 类型名称 -->
<result property="projectCount" column="projectCount"/> <!-- 项目数 -->
<result property="goodsCountTotal" column="goodsCountTotal"/> <!-- 条目总数 -->
<result property="sumQty" column="sumQty"/> <!-- 总数量 -->
<result property="sumAmount" column="sumAmount"/> <!-- 总金额 -->
</resultMap>
<!-- 入库类型饼图统计(联查 stock_in_type未删除 + 未出库rk_type 非空) -->
<select id="selectTypePie" resultMap="RkTypePieMap">
SELECT
COALESCE(sit.type_code, ri.rk_type) AS rkTypeCode,
COALESCE(sit.type_name, ri.rk_type) AS rkTypeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
SUM(IFNULL(ri.real_qty, 0)) AS sumQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
AND ri.is_chuku = 0
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) != ''
GROUP BY COALESCE(sit.type_code, ri.rk_type),
COALESCE(sit.type_name, ri.rk_type)
ORDER BY sumAmount DESC
</select>
<!-- 按入库类型统计(时间范围;只统计类型有效,过滤 NULL/空/“无”) -->
<select id="selectTypeSummaryByRange" resultMap="RkTypePieMap">
<![CDATA[
SELECT
COALESCE(sit.type_code, ri.rk_type) AS rkTypeCode,
COALESCE(sit.type_name, ri.rk_type) AS rkTypeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS goodsCountTotal,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_in_type sit
ON ri.rk_type = sit.type_code
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL) -- 当前库存口径(保持你原条件)
-- 入库类型过滤:剔除 NULL / 空串 / '无'
AND ri.rk_type IS NOT NULL
AND TRIM(ri.rk_type) <> ''
AND ri.rk_type <> '无'
-- 若字典表匹配到了名称且名称为“无”,也剔除;未匹配(NULL)则保留
AND (sit.type_name IS NULL OR (TRIM(sit.type_name) <> '' AND sit.type_name <> '无'))
-- 时间范围:闭区间 [start, end]
AND ri.rk_time >= #{start}
AND ri.rk_time < DATE_ADD(#{end}, INTERVAL 1 DAY)
GROUP BY COALESCE(sit.type_code, ri.rk_type), COALESCE(sit.type_name, ri.rk_type)
ORDER BY sumAmount DESC
]]>
</select>
<select id="selectOutTypeSummaryByRange"
resultType="com.zg.project.wisdom.domain.vo.RkTypePieVO">
<![CDATA[
SELECT
COALESCE(sot.type_code, ri.ck_type) AS rkTypeCode,
COALESCE(sot.type_name, ri.ck_type) AS rkTypeName,
COUNT(DISTINCT COALESCE(NULLIF(TRIM(ri.xm_no_ck), ''), ri.xm_no)) AS projectCount,
COUNT(*) AS goodsCountTotal,
SUM(IFNULL(ri.real_qty, 0)) AS sumQty,
SUM(IFNULL(ri.ht_dj, 0) * IFNULL(ri.real_qty, 0)) AS sumAmount
FROM rk_info ri
LEFT JOIN stock_out_type sot ON ri.ck_type = sot.type_code
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
-- 过滤出库类型为 NULL / 空串 / '无'
AND ri.ck_type IS NOT NULL
AND TRIM(ri.ck_type) <> ''
AND ri.ck_type <> '无'
AND (sot.type_name IS NULL OR TRIM(sot.type_name) <> '无')
-- 时间范围:闭区间 [start, end]
AND ri.ly_time >= #{start}
AND ri.ly_time < DATE_ADD(#{end}, INTERVAL 1 DAY)
GROUP BY COALESCE(sot.type_code, ri.ck_type), COALESCE(sot.type_name, ri.ck_type)
ORDER BY sumAmount DESC
]]>
</select>
<!-- 入库rk_time 在区间内(不限制是否已出库,统计本月入库流水) -->
<select id="selectMonthlyInSummary" resultType="com.zg.project.wisdom.domain.vo.RkMonthSummaryVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time &gt;= #{start}
AND ri.rk_time &lt; #{end}
</select>
<!-- 出库is_chuku=1ly_time 在区间内 -->
<select id="selectMonthlyOutSummary" resultType="com.zg.project.wisdom.domain.vo.RkMonthSummaryVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(ri.real_qty, 0)) AS totalQty,
SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)) AS amountHt
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND ri.ly_time IS NOT NULL
AND ri.ly_time &gt;= #{start}
AND ri.ly_time &lt; #{end}
</select>
<!-- 未到货status='0' -->
<select id="selectUndelivered" resultType="com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO">
SELECT
COUNT(DISTINCT jh.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(jh.jh_qty, 0)) AS totalQty,
/* 金额 = 有效单价 × 剩余未到数量jh_qty */
SUM(
COALESCE(
NULLIF(jh.ht_dj, 0),
CASE WHEN COALESCE(jh.ht_qty, 0) > 0
THEN jh.jh_amt / jh.ht_qty
ELSE 0
END
) * COALESCE(jh.jh_qty, 0)
) AS amountPlan
FROM gys_jh jh
WHERE jh.is_delete = '0'
AND jh.status = '0'
</select>
<!-- 部分未到status='2' -->
<select id="selectPartialUndelivered" resultType="com.zg.project.wisdom.domain.vo.GysJhUndeliveredSummaryVO">
SELECT
COUNT(DISTINCT jh.xm_no) AS projectCount,
COUNT(*) AS itemCount,
SUM(COALESCE(jh.jh_qty, 0)) AS totalQty,
SUM(
COALESCE(
NULLIF(jh.ht_dj, 0),
CASE WHEN COALESCE(jh.ht_qty, 0) > 0
THEN jh.jh_amt / jh.ht_qty
ELSE 0
END
) * COALESCE(jh.jh_qty, 0)
) AS amountPlan
FROM gys_jh jh
WHERE jh.is_delete = '0'
AND jh.status = '2'
</select>
<!-- 当前库存:按物资类型统计
- 仅统计未出库is_chuku = 0 或 NULL
- 仅统计未删除is_delete = 0
- 过滤 wl_type 为空/空白/'无'
-->
<select id="selectCurrentStockByWlType"
resultType="com.zg.project.wisdom.domain.vo.WlTypeStockStatVO">
SELECT
mt.type_name AS typeName,
COUNT(DISTINCT ri.xm_no) AS projectCount,
COUNT(*) AS itemCount,
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS totalQty,
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS amountHt
FROM rk_info ri
JOIN material_type mt
ON mt.type_code = ri.wl_type
AND mt.status = 1
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
AND ri.wl_type IS NOT NULL
AND TRIM(ri.wl_type) != ''
AND ri.wl_type != '无'
AND mt.type_name != '无'
GROUP BY mt.type_code, mt.type_name
ORDER BY amountHt DESC
</select>
<!-- 当前库存:入库时间 > 30 天(仅未出库 & 未删除 & rk_time 非空) -->
<select id="selectAgeGt30Summary"
resultType="com.zg.project.wisdom.domain.vo.RkTypePieVO">
SELECT
COUNT(DISTINCT ri.xm_no) AS projectCount, -- 去重项目数
COUNT(*) AS goodsCountTotal, -- 条目数
COALESCE(SUM(COALESCE(ri.real_qty, 0)), 0) AS sumQty, -- 数量合计
COALESCE(SUM(COALESCE(ri.ht_dj, 0) * COALESCE(ri.real_qty, 0)), 0) AS sumAmount -- 金额合计
FROM rk_info ri
WHERE ri.is_delete = 0
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
AND ri.rk_time IS NOT NULL
AND DATEDIFF(CURDATE(), DATE(ri.rk_time)) > 30
</select>
<!-- 入库原始行 -->
<select id="selectInRange" resultType="com.zg.project.wisdom.domain.vo.RkInVO">
SELECT
ri.xm_no AS xmNo,
ri.rk_time AS rkTime
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.rk_time IS NOT NULL
AND ri.rk_time <![CDATA[>=]]> #{startTs}
AND ri.rk_time <![CDATA[<]]> #{endTs}
</select>
<!-- 出库原始行(有效时间=ly_time 优先,否则 update_time项目号=xm_no_ck 优先,否则 xm_no -->
<select id="selectOutRange" resultType="com.zg.project.wisdom.domain.vo.RkOutVO">
SELECT
ri.xm_no_ck AS xmNoCk,
ri.xm_no AS xmNo,
ri.ly_time AS lyTime,
ri.update_time AS updateTime
FROM rk_info ri
WHERE ri.is_delete = 0
AND ri.is_chuku = 1
AND (
(ri.ly_time IS NOT NULL
AND ri.ly_time <![CDATA[>=]]> #{startTs}
AND ri.ly_time <![CDATA[<]]> #{endTs})
OR (ri.ly_time IS NULL AND ri.update_time IS NOT NULL
AND ri.update_time <![CDATA[>=]]> #{startTs}
AND ri.update_time <![CDATA[<]]> #{endTs})
)
</select>
</mapper>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zg.project.wisdom.mapper.WarehouseStatMapper">
<!-- 1) 启用仓库列表 -->
<resultMap id="WarehouseLiteMap" type="com.zg.project.wisdom.domain.vo.WarehouseStatVO">
<result property="warehouseCode" column="warehouse_code"/>
<result property="warehouseName" column="warehouse_name"/>
</resultMap>
<select id="selectEnabledWarehouses" resultMap="WarehouseLiteMap">
SELECT wi.warehouse_code, wi.warehouse_name
FROM warehouse_info wi
WHERE wi.status = 1
ORDER BY wi.warehouse_code
</select>
<!-- 2) 指定仓库的场景库位统计(总库位/已使用) -->
<resultMap id="SceneUsageMap" type="com.zg.project.wisdom.domain.vo.SceneUsageVO">
<result property="sceneCode" column="scene_code"/>
<result property="sceneName" column="scene_name"/>
<result property="totalPositions" column="total_positions"/>
<result property="usedPositions" column="used_positions"/>
</resultMap>
<select id="selectSceneUsageByWarehouse" resultMap="SceneUsageMap">
SELECT
sm.scene_code,
sm.scene_name,
COUNT(DISTINCT pd.pcode) AS total_positions,
COUNT(DISTINCT CASE WHEN ri.is_chuku = 0 THEN pd.pcode END) AS used_positions
FROM pcde_detail pd
LEFT JOIN scene_mapping sm
ON pd.scene = sm.scene_code
LEFT JOIN rk_info ri
ON pd.pcode = ri.pcode
WHERE pd.warehouse = #{warehouseCode}
AND (pd.is_delete IS NULL OR pd.is_delete = 0)
GROUP BY sm.scene_code, sm.scene_name
ORDER BY sm.scene_code
</select>
<!-- 3) 启用仓库总数 -->
<select id="countEnabledWarehouses" resultType="int">
SELECT COUNT(*) FROM warehouse_info wi WHERE wi.status = 1
</select>
</mapper>