新增图片模块
This commit is contained in:
173
src/main/java/com/zg/common/utils/LocalPhotoUtil.java
Normal file
173
src/main/java/com/zg/common/utils/LocalPhotoUtil.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package com.zg.common.utils;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 本地图片保存工具
|
||||
* - 根目录:photo.root(默认 D:\photo)
|
||||
* - 目录结构:{subDir}/yyyy-MM-dd/
|
||||
* - 文件命名:{billNo}_{sapNo}_{xmMs}_{yyyyMMddHHmmssSSS}_{index}.{ext}
|
||||
* - 返回:相对web路径(用于拼接 /photo/ 前缀生成可访问URL)与文件名
|
||||
*/
|
||||
@Component
|
||||
public class LocalPhotoUtil {
|
||||
|
||||
/** 本地根目录,可在 yml 配置:photo.root: D:\photo */
|
||||
@Value("${photo.root:D:\\\\photo}")
|
||||
private String rootPath;
|
||||
|
||||
/** 非法文件名字符(Windows) */
|
||||
private static final String ILLEGAL_CHARS = "\\/:*?\"<>|";
|
||||
|
||||
/** yyyy-MM-dd(目录) */
|
||||
private String today() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
}
|
||||
|
||||
/** yyyyMMddHHmmssSSS(文件戳) */
|
||||
private String nowStamp() {
|
||||
return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装基础文件名:billNo_sapNo_xmMs(不含后缀、不含时间戳/序号)
|
||||
*/
|
||||
public String buildBaseName(String billNo, String xmMs) {
|
||||
String b = defaultString(billNo);
|
||||
String x = defaultString(xmMs);
|
||||
String joined = String.join("_", b, x);
|
||||
return sanitize(joined);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存单个文件
|
||||
* @param file 文件
|
||||
* @param subDir 一级子目录(建议 "photo" 固定,也可按业务传入)
|
||||
* @param baseName 基础名(不含扩展名;可用 buildBaseName 生成)
|
||||
* @param index 序号(从1开始),用于同批次区分
|
||||
* @return SaveResult(webPath, fileName)
|
||||
*/
|
||||
public SaveResult saveOne(MultipartFile file, String subDir, String baseName, int index) throws Exception {
|
||||
String ext = resolveExt(file);
|
||||
String safeSub = sanitize(defaultString(subDir, ""));
|
||||
String dirPart = safeSub + File.separator + today() + File.separator;
|
||||
|
||||
String stamp = nowStamp();
|
||||
String safeBase = sanitize(baseName);
|
||||
// 最终文件名:base_时间戳_序号.ext
|
||||
String fileName = safeBase + "_" + stamp + "_" + index + ext;
|
||||
|
||||
File dir = new File(rootPath, dirPart);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new RuntimeException("创建目录失败:" + dir.getAbsolutePath());
|
||||
}
|
||||
|
||||
File dest = new File(dir, fileName);
|
||||
try (InputStream in = file.getInputStream()) {
|
||||
Files.copy(in, dest.toPath());
|
||||
}
|
||||
|
||||
SaveResult r = new SaveResult();
|
||||
r.setWebPath((dirPart + fileName).replace("\\", "/")); // e.g. photo/2025-09-01/xxx.jpg
|
||||
r.setFileName(fileName);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存(顺序与入参 files 一致)
|
||||
* @param files 文件列表
|
||||
* @param subDir 一级子目录(建议传 "photo")
|
||||
* @param baseName 基础名(不含扩展名)
|
||||
*/
|
||||
public List<SaveResult> saveBatch(List<MultipartFile> files, String subDir, String baseName) throws Exception {
|
||||
if (files == null || files.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<SaveResult> list = new ArrayList<>(files.size());
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
list.add(saveOne(files.get(i), subDir, baseName, i + 1));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:直接传业务字段(内部会调用 buildBaseName)
|
||||
* @param files 多文件
|
||||
* @param subDir 一级子目录(建议 "photo")
|
||||
* @param billNo 单据号
|
||||
* @param xmMs 项目描述
|
||||
*/
|
||||
public List<SaveResult> saveBatchByBiz(List<MultipartFile> files, String subDir,
|
||||
String billNo, String xmMs) throws Exception {
|
||||
String base = buildBaseName(billNo, xmMs);
|
||||
return saveBatch(files, subDir, base);
|
||||
}
|
||||
|
||||
/** 解析扩展名:优先取原文件名,其次按 contentType 兜底 */
|
||||
private String resolveExt(MultipartFile file) {
|
||||
String origin = file.getOriginalFilename();
|
||||
if (origin != null) {
|
||||
int dot = origin.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
String ext = origin.substring(dot);
|
||||
if (ext.length() <= 10) { // 简单防御:避免异常超长扩展名
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
String ct = file.getContentType();
|
||||
if ("image/png".equalsIgnoreCase(ct)) return ".png";
|
||||
if ("image/jpeg".equalsIgnoreCase(ct) || "image/jpg".equalsIgnoreCase(ct)) return ".jpg";
|
||||
return ".bin";
|
||||
}
|
||||
|
||||
/** 清洗文件名:去空白、替换非法字符、规避Windows保留名、限长 */
|
||||
public String sanitize(String name) {
|
||||
if (name == null) return "unnamed";
|
||||
String t = name.trim().replaceAll("\\s+", " ");
|
||||
StringBuilder sb = new StringBuilder(t.length());
|
||||
for (int i = 0; i < t.length(); i++) {
|
||||
char c = t.charAt(i);
|
||||
sb.append(ILLEGAL_CHARS.indexOf(c) >= 0 ? '_' : c);
|
||||
}
|
||||
String out = sb.toString();
|
||||
String upper = out.toUpperCase(Locale.ROOT);
|
||||
if (upper.matches("CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]")) {
|
||||
out = "_" + out;
|
||||
}
|
||||
// 限长,给时间戳与序号留空间
|
||||
if (out.length() > 100) {
|
||||
out = out.substring(0, 100);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** null 转空串;带默认值重载 */
|
||||
private String defaultString(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
private String defaultString(String s, String def) {
|
||||
return (s == null || s.isEmpty()) ? def : s;
|
||||
}
|
||||
|
||||
/** 返回值对象 */
|
||||
public static class SaveResult {
|
||||
/** 相对web路径,例如 photo/2025-09-01/xxx.jpg(用于拼接 /photo/ 前缀生成URL) */
|
||||
private String webPath;
|
||||
/** 最终文件名(含扩展名) */
|
||||
private String fileName;
|
||||
|
||||
public String getWebPath() { return webPath; }
|
||||
public void setWebPath(String webPath) { this.webPath = webPath; }
|
||||
public String getFileName() { return fileName; }
|
||||
public void setFileName(String fileName) { this.fileName = fileName; }
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,11 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
|
||||
.addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
|
||||
|
||||
|
||||
/**把 /photo/** 映射到 D:/photo/ */
|
||||
registry.addResourceHandler("/photo/**")
|
||||
.addResourceLocations("file:D:/photo/"); // 关键:file: 前缀 + 结尾 /
|
||||
|
||||
/** swagger配置 */
|
||||
registry.addResourceHandler("/swagger-ui/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
|
||||
|
||||
@@ -121,6 +121,8 @@ public class SecurityConfig
|
||||
"/system/config/**",
|
||||
"/AutoInventory/**",
|
||||
"/ws/**",
|
||||
"/photo/**",
|
||||
"/wisdom/stock/**",
|
||||
"/wisdom/**",
|
||||
"/mock/**",
|
||||
"/information/device/**",
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.zg.project.wisdom.controller;
|
||||
|
||||
import com.zg.framework.web.domain.AjaxResult;
|
||||
import com.zg.project.wisdom.domain.dto.PhotoDeleteDTO;
|
||||
import com.zg.project.wisdom.service.PhotoService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 【照片上传接口】
|
||||
* - 负责接收前端图片(单图/多图),并调用 Service 完成:落盘 + URL入库
|
||||
* - 依赖静态资源映射:/photo/** -> D:\photo\
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/photo")
|
||||
public class PhotoController {
|
||||
|
||||
@Resource
|
||||
private PhotoService photoService;
|
||||
|
||||
/**
|
||||
* 【多图上传】(推荐)
|
||||
* 提交方式:multipart/form-data
|
||||
* 参数:
|
||||
* - files:多文件(同名字段,可传多张)
|
||||
* - photoType:0(入库相关) / 1(出库相关)
|
||||
* - billNo:单据号
|
||||
* - xmMs:项目描述
|
||||
* - sapNo:订单号
|
||||
* 返回:每张图片的可访问 URL 列表
|
||||
*/
|
||||
@PostMapping(value = "/upload/batch", consumes = "multipart/form-data")
|
||||
public AjaxResult uploadBatch(@RequestPart("files") List<MultipartFile> files,
|
||||
@RequestParam("photoType") String photoType,
|
||||
@RequestParam("billNo") String billNo,
|
||||
@RequestParam("xmMs") String xmMs) {
|
||||
List<String> urls = photoService.uploadAndSave(files, photoType, billNo, xmMs);
|
||||
return AjaxResult.success("上传成功", urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【单图上传】(可选)
|
||||
* 与多图参数一致,只是文件字段为 file
|
||||
* 返回:单张图片的可访问 URL
|
||||
*/
|
||||
@PostMapping(value = "/upload", consumes = "multipart/form-data")
|
||||
public AjaxResult upload(@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("photoType") String photoType,
|
||||
@RequestParam("billNo") String billNo,
|
||||
@RequestParam("xmMs") String xmMs) {
|
||||
List<String> urls = photoService.uploadAndSave(Collections.singletonList(file), photoType, billNo, xmMs);
|
||||
return AjaxResult.success("上传成功", urls.isEmpty() ? null : urls.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 【查询:按 billNo(可选photoType)取照片URL列表】
|
||||
* GET /photo/list?billNo=...&photoType=0|1
|
||||
* - billNo 必填
|
||||
* - photoType 可不传:不传则返回该单据下所有类型的照片
|
||||
* 返回:纯 URL 列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(@RequestParam("billNo") String billNo,
|
||||
@RequestParam(value = "photoType", required = false) String photoType) {
|
||||
return AjaxResult.success(photoService.listUrlsByBill(billNo, photoType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 【删除:按 URL 列表】
|
||||
* @param dto
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/delete", consumes = "application/json")
|
||||
// @PreAuthorize("@ss.hasPermi('wisdom:photo:remove')")
|
||||
public AjaxResult delete(@Validated @RequestBody PhotoDeleteDTO dto) {
|
||||
Map<String, Object> result = photoService.deleteByUrls(dto.getUrls());
|
||||
return AjaxResult.success("删除完成", result);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zg.project.wisdom.controller;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -7,6 +8,8 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.zg.project.wisdom.domain.dto.*;
|
||||
import com.zg.project.wisdom.domain.vo.BillGroupVO;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -44,6 +47,17 @@ public class RkInfoController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@ApiOperation("按单据分组(bill_no)列表:若存在出库则同时返回 bill_no_ck")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:list')")
|
||||
@PostMapping("/bill/groups")
|
||||
public TableDataInfo billGroups(@RequestBody RkInfoQueryDTO query) {
|
||||
// 分页
|
||||
PageHelper.startPage(query.getPageNum(), query.getPageSize());
|
||||
// 查询
|
||||
List<RkInfo> rows = rkInfoService.selectGroupedByBill(query);
|
||||
|
||||
return getDataTable(rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出库存单据主列表
|
||||
|
||||
@@ -43,6 +43,17 @@ public class RkStatisticsController {
|
||||
return AjaxResult.success(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按仓库查询:场景列表 + 每个场景的可用库位明细
|
||||
* 示例:GET /warehouse/positions/available?warehouseCode=CK001
|
||||
*/
|
||||
@ApiOperation("按仓库查询各场景的可用库位(排除未出库占用)")
|
||||
@GetMapping("/warehouse/available")
|
||||
public AjaxResult listAvailableByWarehouse(@RequestParam String warehouseCode) {
|
||||
List<SceneAvailableVO> data = rkStatisticsService.listAvailableByWarehouse(warehouseCode);
|
||||
return AjaxResult.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前库存根据入库类型统计项目数,每个项目的条目数,实际入库总数,金额总和(仅未出库)
|
||||
*/
|
||||
|
||||
95
src/main/java/com/zg/project/wisdom/domain/StockPhoto.java
Normal file
95
src/main/java/com/zg/project/wisdom/domain/StockPhoto.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package com.zg.project.wisdom.domain;
|
||||
|
||||
import com.zg.framework.web.domain.BaseEntity;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 单据照片对象 stock_photo
|
||||
* 按 bill_no 聚合查询;仅存一个可访问的 URL;photo_type:0=入库、1=出库
|
||||
*/
|
||||
public class StockPhoto extends BaseEntity implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 主键ID */
|
||||
private Long id;
|
||||
|
||||
/** 单据号(对应 rk_info.bill_no,用于聚合查询) */
|
||||
private String billNo;
|
||||
|
||||
/** 照片业务类型:0=入库相关,1=出库相关 */
|
||||
private String photoType;
|
||||
|
||||
/** 实际文件名(含扩展名),示例:I202508110001_101360951_项目名_时间戳_序号.jpg */
|
||||
private String fileName;
|
||||
|
||||
/** 可直接访问的图片URL(形如 /photo/2025-09-01/xxx.jpg 或完整http地址) */
|
||||
private String url;
|
||||
|
||||
/** 删除标记:0=正常,1=已删除 */
|
||||
private String isDelete;
|
||||
|
||||
/* ========== Getter / Setter ========== */
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBillNo() {
|
||||
return billNo;
|
||||
}
|
||||
public void setBillNo(String billNo) {
|
||||
this.billNo = billNo;
|
||||
}
|
||||
|
||||
public String getPhotoType() {
|
||||
return photoType;
|
||||
}
|
||||
public void setPhotoType(String photoType) {
|
||||
this.photoType = photoType;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getIsDelete() {
|
||||
return isDelete;
|
||||
}
|
||||
public void setIsDelete(String isDelete) {
|
||||
this.isDelete = isDelete;
|
||||
}
|
||||
|
||||
/* ========== toString(RuoYi 风格) ========== */
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("id", getId())
|
||||
.append("billNo", getBillNo())
|
||||
.append("photoType", getPhotoType())
|
||||
.append("fileName", getFileName())
|
||||
.append("url", getUrl())
|
||||
.append("isDelete", getIsDelete())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.zg.project.wisdom.domain.dto;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/** 删除图片请求体:传 URL 列表 */
|
||||
public class PhotoDeleteDTO {
|
||||
|
||||
/** 要删除的图片地址(与 stock_photo.url 一致) */
|
||||
@NotEmpty
|
||||
private List<String> urls;
|
||||
|
||||
public List<String> getUrls() { return urls; }
|
||||
public void setUrls(List<String> urls) { this.urls = urls; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.zg.project.wisdom.domain.vo;// package com.zg.project.wisdom.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class BillGroupVO {
|
||||
/** 分组主键:统一用入库单号 bill_no(若为空会用 bill_no_ck 兜底) */
|
||||
private String billNo;
|
||||
|
||||
/** 出库单号(出库单据时有值,前端可展示/操作) */
|
||||
private String billNoCk;
|
||||
|
||||
/** 单据类型:IN / OUT */
|
||||
private String billType;
|
||||
|
||||
/** 单据时间:IN 取最早 rk_time;OUT 取最早 ly_time */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date billTime;
|
||||
|
||||
// ——主数据常用抬头字段(从首条提取即可)——
|
||||
private String xj;
|
||||
private String xmNo;
|
||||
private String xmMs;
|
||||
private String rkType; private String rkTypeName;
|
||||
private String ckType; private String ckTypeName;
|
||||
private String teamCode; private String teamName;
|
||||
private String cangkuName;
|
||||
private String lihuoYName;
|
||||
private String remark;
|
||||
|
||||
// ——聚合指标——
|
||||
private Integer itemCount; // 明细条数
|
||||
private BigDecimal sumQty; // 数量合计(sum real_qty)
|
||||
private BigDecimal sumAmount; // 金额合计(sum ht_dj * real_qty)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.zg.project.wisdom.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 可用库位简单视图
|
||||
*/
|
||||
@Data
|
||||
public class PcdeDetailSimpleVO {
|
||||
/** 库位编码 */
|
||||
private String pcode;
|
||||
/** 标签(如有) */
|
||||
private String tag;
|
||||
/** 编码ID(如有) */
|
||||
private String encodedId;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.zg.project.wisdom.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 场景下的可用库位
|
||||
*/
|
||||
@Data
|
||||
public class SceneAvailableVO {
|
||||
/** 场景编码(pcde_detail.scene,对应 scene_mapping.scene_code) */
|
||||
private String sceneCode;
|
||||
/** 场景名称(scene_mapping.scene_name) */
|
||||
private String sceneName;
|
||||
/** 可用库位数量 */
|
||||
private Integer availableCount;
|
||||
/** 可用库位明细 */
|
||||
private List<PcdeDetailSimpleVO> positions;
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import java.util.Map;
|
||||
import com.zg.project.Inventory.domain.vo.PcdeCntVO;
|
||||
import com.zg.project.Inventory.domain.vo.RkInfoMatchVO;
|
||||
import com.zg.project.wisdom.domain.RkInfo;
|
||||
import com.zg.project.wisdom.domain.StockPhoto;
|
||||
import com.zg.project.wisdom.domain.dto.RkInfoQueryDTO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,13 @@ public interface RkInfoMapper
|
||||
*/
|
||||
public List<RkInfo> selectRkInfoList(RkInfo rkInfo);
|
||||
|
||||
/**
|
||||
* 使用 selectRkInfoVo 作为子查询,外层按 bill_no 分组聚合
|
||||
* 不新增 resultMap / VO,直接用 RkInfoResult 映射需要的字段
|
||||
*/
|
||||
List<RkInfo> selectGroupedByBill(@Param("q") RkInfoQueryDTO query,
|
||||
@Param("needAudit") Integer needAudit);
|
||||
|
||||
|
||||
/**
|
||||
* 修改库存单据主
|
||||
|
||||
@@ -99,4 +99,16 @@ public interface RkStatisticsMapper {
|
||||
/** 出库:在 [startTs, endTs) 时间范围内、is_chuku=1 的原始行(仅最小过滤) */
|
||||
List<RkOutVO> selectOutRange(@Param("startTs") String startTs,
|
||||
@Param("endTs") String endTs);
|
||||
|
||||
/**
|
||||
* 外层:按场景汇总可用数量,并级联查询可用库位明细(返回 SceneAvailableVO 列表)
|
||||
*/
|
||||
List<SceneAvailableVO> selectAvailableByWarehouse(@Param("warehouseCode") String warehouseCode);
|
||||
|
||||
/**
|
||||
* 内层:指定仓库 + 场景,查询该场景下的可用库位明细
|
||||
*/
|
||||
List<PcdeDetailSimpleVO> selectAvailablePositionsByWarehouseAndScene(
|
||||
@Param("warehouseCode") String warehouseCode,
|
||||
@Param("sceneCode") String sceneCode);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.zg.project.wisdom.mapper;
|
||||
|
||||
import com.zg.project.wisdom.domain.StockPhoto;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 【单据照片Mapper】
|
||||
* - 负责 URL 的持久化(只存一条URL)
|
||||
*/
|
||||
public interface StockPhotoMapper {
|
||||
|
||||
/**
|
||||
* 新增(单条)
|
||||
*/
|
||||
int insert(StockPhoto photo);
|
||||
|
||||
/**
|
||||
* 批量新增(推荐用于多图一次上传)
|
||||
*/
|
||||
int batchInsert(@Param("list") List<StockPhoto> list);
|
||||
|
||||
/**
|
||||
* 按 bill_no(可选 photoType)查询图片列表
|
||||
*/
|
||||
List<StockPhoto> selectByBillNo(@Param("billNo") String billNo,
|
||||
@Param("photoType") String photoType);
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param urls
|
||||
* @return
|
||||
*/
|
||||
int softDeleteByUrls(@Param("urls") List<String> urls);
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.zg.project.wisdom.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.zg.project.Inventory.domain.dto.QueryDTO;
|
||||
import com.zg.project.Inventory.domain.vo.ChartDataVO;
|
||||
import com.zg.project.wisdom.domain.RkInfo;
|
||||
import com.zg.project.wisdom.domain.dto.*;
|
||||
import com.zg.project.wisdom.domain.vo.RkSubmitResultVO;
|
||||
import com.zg.project.wisdom.domain.vo.BillGroupVO;
|
||||
|
||||
/**
|
||||
* 库存单据主Service接口
|
||||
@@ -27,12 +26,17 @@ public interface IRkInfoService
|
||||
|
||||
/**
|
||||
* 查询库存单据主列表
|
||||
*
|
||||
*
|
||||
* @param rkInfo 库存单据主
|
||||
* @return 库存单据主集合
|
||||
*/
|
||||
public List<RkInfo> selectRkInfoList(RkInfo rkInfo);
|
||||
|
||||
/**
|
||||
* 按 bill_no 分组返回单据列表(复用 selectRkInfoVo,SQL 外层分组聚合)
|
||||
* @param query 与 /list 相同的查询条件
|
||||
*/
|
||||
List<RkInfo> selectGroupedByBill(RkInfoQueryDTO query);
|
||||
|
||||
/**
|
||||
* 修改库存单据主
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.zg.project.wisdom.service;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 【照片上传服务接口】
|
||||
* - 定义:多图落盘 + URL入库 的能力
|
||||
*/
|
||||
public interface PhotoService {
|
||||
|
||||
/**
|
||||
* 多图上传并落库,返回每张图片的可访问URL
|
||||
*
|
||||
* @param files 图片文件列表(必传)
|
||||
* @param photoType 照片业务类型:0=入库相关,1=出库相关(必填)
|
||||
* @param billNo 单据号(必填,用于关联)
|
||||
* @param xmMs 项目描述(用于文件命名,可为空)
|
||||
* @return URLs 列表(每个元素都是可直接 <img src> 的地址)
|
||||
*/
|
||||
List<String> uploadAndSave(List<MultipartFile> files,
|
||||
String photoType, String billNo,
|
||||
String xmMs);
|
||||
|
||||
|
||||
/**
|
||||
* 按 billNo(可选 photoType)查询照片 URL 列表
|
||||
*/
|
||||
List<String> listUrlsByBill(String billNo, String photoType);
|
||||
|
||||
/**
|
||||
* 按 URL 批量删除(仅数据库软删,不删除磁盘文件)
|
||||
* @param urls 待删URL列表
|
||||
* @return 结果统计:dbUpdated(数据库更新行数), total(传入数)
|
||||
*/
|
||||
Map<String, Object> deleteByUrls(List<String> urls);
|
||||
|
||||
}
|
||||
@@ -80,4 +80,11 @@ public interface RkStatisticsService {
|
||||
* 返回每个时间桶的一行数据(固定 7 行或 6 行)
|
||||
*/
|
||||
List<IOBucketVO> getIOBuckets(Integer range);
|
||||
|
||||
|
||||
/**
|
||||
* 根据仓库编码,返回各场景的可用库位(含数量与明细)
|
||||
* 规则:排除 rk_info 中未出库(is_chuku=0 或 NULL)的占用库位
|
||||
*/
|
||||
List<SceneAvailableVO> listAvailableByWarehouse(String warehouseCode);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.zg.project.wisdom.service.impl;
|
||||
|
||||
import com.zg.common.utils.LocalPhotoUtil;
|
||||
import com.zg.common.utils.LocalPhotoUtil.SaveResult;
|
||||
import com.zg.project.wisdom.domain.StockPhoto;
|
||||
import com.zg.project.wisdom.mapper.StockPhotoMapper;
|
||||
import com.zg.project.wisdom.service.PhotoService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 【照片上传服务实现】
|
||||
* 核心流程:
|
||||
* 1) 使用 LocalPhotoUtil 将文件保存到本地(按:photo/yyyy-MM-dd/)
|
||||
* 2) 拼接可访问URL(/photo/** 静态映射)
|
||||
* 3) 批量写入 stock_photo(只存一个 url)
|
||||
* 4) 返回 URL 列表
|
||||
*/
|
||||
@Service
|
||||
public class PhotoServiceImpl implements PhotoService {
|
||||
|
||||
@Resource
|
||||
private LocalPhotoUtil localPhotoUtil;
|
||||
|
||||
@Resource
|
||||
private StockPhotoMapper stockPhotoMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<String> uploadAndSave(List<MultipartFile> files,
|
||||
String photoType, String billNo,
|
||||
String xmMs) {
|
||||
|
||||
// ========= 1. 入参校验 =========
|
||||
if (files == null || files.isEmpty()) {
|
||||
throw new IllegalArgumentException("请选择要上传的图片");
|
||||
}
|
||||
if (!("0".equals(photoType) || "1".equals(photoType))) {
|
||||
throw new IllegalArgumentException("photoType 只能为 0(入库) 或 1(出库)");
|
||||
}
|
||||
if (StringUtils.isBlank(billNo)) {
|
||||
throw new IllegalArgumentException("billNo 不能为空");
|
||||
}
|
||||
|
||||
// 组装基础文件名:billNo_sapNo_xmMs(内部会做非法字符清洗)
|
||||
final String baseName = localPhotoUtil.buildBaseName(
|
||||
StringUtils.defaultString(billNo),
|
||||
StringUtils.defaultString(xmMs));
|
||||
|
||||
try {
|
||||
// ========= 2. 本地落盘 =========
|
||||
// 目录:D:\photo\photo\yyyy-MM-dd\
|
||||
List<SaveResult> saved = localPhotoUtil.saveBatch(files, "", baseName);
|
||||
|
||||
// ========= 3. 生成URL + 组装入库对象 =========
|
||||
List<String> urls = new ArrayList<String>(saved.size());
|
||||
List<StockPhoto> rows = new ArrayList<StockPhoto>(saved.size());
|
||||
|
||||
for (SaveResult s : saved) {
|
||||
// 根据当前上下文拼 URL,例如:http://host:port/photo/photo/2025-09-01/xxx.jpg
|
||||
String url = ServletUriComponentsBuilder.fromCurrentContextPath()
|
||||
.path("/photo/") // 与 StaticResourceConfig.addResourceHandlers 保持一致
|
||||
.path(s.getWebPath()) // 形如:photo/2025-09-01/xxx.jpg
|
||||
.toUriString();
|
||||
urls.add(url);
|
||||
|
||||
// 只存一个URL到表中(其余信息按你最简诉求不存)
|
||||
StockPhoto p = new StockPhoto();
|
||||
p.setBillNo(billNo);
|
||||
p.setPhotoType(photoType);
|
||||
p.setFileName(s.getFileName());
|
||||
p.setUrl(url);
|
||||
// 如需记录创建人:p.setCreateBy(SecurityUtils.getUserId().toString());
|
||||
rows.add(p);
|
||||
}
|
||||
|
||||
// ========= 4. 批量入库 =========
|
||||
if (!rows.isEmpty()) {
|
||||
stockPhotoMapper.batchInsert(rows);
|
||||
}
|
||||
|
||||
return urls;
|
||||
} catch (Exception e) {
|
||||
// Service 层抛出 RuntimeException,触发 @Transactional 回滚数据库写入
|
||||
// (注意:文件已落盘无法自动回滚,必要时可加失败清理逻辑)
|
||||
throw new RuntimeException("上传/保存失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listUrlsByBill(String billNo, String photoType) {
|
||||
if (StringUtils.isBlank(billNo)) {
|
||||
throw new IllegalArgumentException("billNo 不能为空");
|
||||
}
|
||||
return stockPhotoMapper.selectByBillNo(billNo,
|
||||
StringUtils.isBlank(photoType) ? null : photoType)
|
||||
.stream()
|
||||
.map(StockPhoto::getUrl)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param urls 待删URL列表
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> deleteByUrls(List<String> urls) {
|
||||
if (urls == null || urls.isEmpty()) {
|
||||
throw new IllegalArgumentException("urls 不能为空");
|
||||
}
|
||||
// 仅数据库软删
|
||||
int dbUpdated = stockPhotoMapper.softDeleteByUrls(urls);
|
||||
|
||||
Map<String, Object> ret = new HashMap<String, Object>(2);
|
||||
ret.put("total", urls.size());
|
||||
ret.put("dbUpdated", dbUpdated);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,14 @@ public class RkInfoServiceImpl implements IRkInfoService
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RkInfo> selectGroupedByBill(RkInfoQueryDTO query) {
|
||||
// 读取审核开关(1=开启;其它=关闭)
|
||||
boolean needAudit = "1".equals(configService.selectConfigByKey("rk.audit.enabled"));
|
||||
// 直接传给 Mapper;不改 DTO 结构,走多参数方式
|
||||
return rkInfoMapper.selectGroupedByBill(query, needAudit ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改库存单据主
|
||||
*
|
||||
|
||||
@@ -388,4 +388,30 @@ public class RkStatisticsServiceImpl implements RkStatisticsService {
|
||||
Set<String> inProjects = new HashSet<>();
|
||||
Set<String> outProjects = new HashSet<>();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SceneAvailableVO> listAvailableByWarehouse(String warehouseCode) {
|
||||
List<SceneAvailableVO> list = rkStatisticsMapper.selectAvailableByWarehouse(warehouseCode);
|
||||
|
||||
if (list == null || list.isEmpty()) {
|
||||
// 没有查到任何数据时,返回一个默认对象
|
||||
SceneAvailableVO vo = new SceneAvailableVO();
|
||||
// vo.setSceneCode("N/A"); // 默认场景编码
|
||||
// vo.setSceneName("无可用场景"); // 默认场景名称
|
||||
vo.setAvailableCount(0); // 可用数量为0
|
||||
vo.setPositions(new ArrayList<>()); // 空数组
|
||||
|
||||
list = new ArrayList<>();
|
||||
list.add(vo);
|
||||
} else {
|
||||
// 保证 positions 不为 null
|
||||
for (SceneAvailableVO vo : list) {
|
||||
if (vo.getPositions() == null) {
|
||||
vo.setPositions(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="sapNo != null and sapNo != ''">
|
||||
and sap_no like concat('%', #{sapNo}, '%')
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
and status = #{status}
|
||||
</if>
|
||||
|
||||
and (status is null or trim(status) != '1')
|
||||
|
||||
<if test="isDelete != null and isDelete != ''">
|
||||
and is_delete = #{isDelete}
|
||||
</if>
|
||||
|
||||
@@ -317,6 +317,244 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
ORDER BY ri.rk_time DESC
|
||||
</select>
|
||||
|
||||
<!--
|
||||
按单据分组(bill_no)列表
|
||||
复用 <sql id="selectRkInfoVo"> 作为子查询,外层分组聚合
|
||||
返回字段:bill_no、bill_no_ck + 你指定的通用字段(rk_type、wl_type、cangku、rk_time、lihuo_y、is_chuku、xj、
|
||||
xm_no、xm_ms、xm_no_ck、xm_ms_ck、wl_no、wl_ms、gys_no、sap_no)
|
||||
-->
|
||||
<select id="selectGroupedByBill" resultMap="RkInfoResult" parameterType="map">
|
||||
SELECT
|
||||
/* ============= 主键:取单据内任一(用最小 ID 代表) ============= */
|
||||
MIN(t.id) AS id,
|
||||
|
||||
/* ============= 单据号 ============= */
|
||||
t.bill_no AS bill_no, -- 入库单据号(分组键)
|
||||
ANY_VALUE(t.bill_no_ck) AS bill_no_ck, -- 出库单据号(若单据发生过出库则可能有值)
|
||||
|
||||
/* ============= 通用字段(代表值/聚合) ============= */
|
||||
ANY_VALUE(t.rk_type) AS rk_type, -- 入库类型编码(type_code)
|
||||
-- 入库类型名称(由 stock_in_type 反查)
|
||||
(SELECT si.type_name
|
||||
FROM stock_in_type si
|
||||
WHERE si.type_code = ANY_VALUE(t.rk_type)
|
||||
LIMIT 1) AS rk_type_name,
|
||||
|
||||
ANY_VALUE(t.wl_type) AS wl_type, -- 物资类型
|
||||
ANY_VALUE(t.cangku) AS cangku, -- 仓库(编码)
|
||||
MIN(t.rk_time) AS rk_time, -- 入库时间(单据内最早)
|
||||
ANY_VALUE(t.lihuo_y) AS lihuo_y, -- 理货员(ID)
|
||||
MAX(t.is_chuku) AS is_chuku, -- 是否出库(单据内只要有出库,用最大值反映)
|
||||
ANY_VALUE(t.xj) AS xj, -- 县局
|
||||
|
||||
ANY_VALUE(t.xm_no) AS xm_no, -- 入库项目号
|
||||
ANY_VALUE(t.xm_ms) AS xm_ms, -- 入库项目描述
|
||||
ANY_VALUE(t.xm_no_ck) AS xm_no_ck, -- 出库项目号
|
||||
ANY_VALUE(t.xm_ms_ck) AS xm_ms_ck, -- 出库项目描述
|
||||
|
||||
ANY_VALUE(t.wl_no) AS wl_no, -- 物料号
|
||||
ANY_VALUE(t.wl_ms) AS wl_ms, -- 物料描述
|
||||
ANY_VALUE(t.gys_no) AS gys_no, -- 供应商编码
|
||||
ANY_VALUE(t.sap_no) AS sap_no, -- SAP 订单号
|
||||
|
||||
/* ============= 出库附加信息 ============= */
|
||||
ANY_VALUE(t.ck_type) AS ck_type, -- 出库类型编码
|
||||
(SELECT s.type_name
|
||||
FROM stock_out_type s
|
||||
WHERE s.type_code = ANY_VALUE(t.ck_type)
|
||||
LIMIT 1) AS ck_type_name, -- 出库类型名称
|
||||
|
||||
MAX(t.ly_time) AS ly_time, -- 领用时间(单据内最新)
|
||||
|
||||
ANY_VALUE(t.ck_lihuo_y) AS ck_lihuo_y, -- 出库理货员ID
|
||||
(SELECT u.user_name
|
||||
FROM sys_user u
|
||||
WHERE u.user_id = ANY_VALUE(t.ck_lihuo_y)
|
||||
LIMIT 1) AS ck_lihuo_y_name -- 出库理货员姓名
|
||||
|
||||
FROM (
|
||||
<!-- 这里完全复用你已有的 SELECT + JOIN 片段,列别名与 RkInfoResult 一致 -->
|
||||
<include refid="selectRkInfoVo"/>
|
||||
) t
|
||||
|
||||
<where>
|
||||
<!-- ================= 与 /list 相同的筛选条件(把 ri.* 改为 t.*) ================= -->
|
||||
|
||||
<!-- is_chuku:优先列表,其次单值 -->
|
||||
<choose>
|
||||
<when test="q.isChukuList != null and q.isChukuList.size > 0">
|
||||
AND t.is_chuku IN
|
||||
<foreach collection="q.isChukuList" item="val" open="(" separator="," close=")">
|
||||
#{val}
|
||||
</foreach>
|
||||
</when>
|
||||
<when test="q.isChuku != null and q.isChuku != ''">
|
||||
AND t.is_chuku = #{q.isChuku}
|
||||
</when>
|
||||
</choose>
|
||||
|
||||
<!-- 关键词模糊 -->
|
||||
<if test="q.keyword != null and q.keyword != ''">
|
||||
AND (
|
||||
t.xm_no like concat('%', #{q.keyword}, '%')
|
||||
or t.xm_ms like concat('%', #{q.keyword}, '%')
|
||||
or t.wl_no like concat('%', #{q.keyword}, '%')
|
||||
or t.wl_ms like concat('%', #{q.keyword}, '%')
|
||||
or t.gys_no like concat('%', #{q.keyword}, '%')
|
||||
or t.gys_mc like concat('%', #{q.keyword}, '%')
|
||||
or t.sap_no like concat('%', #{q.keyword}, '%')
|
||||
or t.bill_no like concat('%', #{q.keyword}, '%')
|
||||
or t.bill_no_ck like concat('%', #{q.keyword}, '%')
|
||||
or t.ck_type like concat('%', #{q.keyword}, '%')
|
||||
or t.pcode like concat('%', #{q.keyword}, '%')
|
||||
)
|
||||
</if>
|
||||
|
||||
<!-- 维度筛选 -->
|
||||
<if test="q.rkType != null and q.rkType != ''">
|
||||
AND t.rk_type like concat('%', #{q.rkType}, '%')
|
||||
</if>
|
||||
<if test="q.wlType != null and q.wlType != ''">
|
||||
AND t.wl_type like concat('%', #{q.wlType}, '%')
|
||||
</if>
|
||||
<if test="q.cangku != null and q.cangku != ''">
|
||||
AND t.cangku like concat('%', #{q.cangku}, '%')
|
||||
</if>
|
||||
|
||||
<!-- ID 列表 -->
|
||||
<if test="q.ids != null and q.ids.size > 0">
|
||||
AND t.id IN
|
||||
<foreach collection="q.ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<!-- 入库时间范围(闭区间) -->
|
||||
<if test="q.startTime != null"><![CDATA[
|
||||
AND t.rk_time >= #{q.startTime}
|
||||
]]></if>
|
||||
<if test="q.endTime != null"><![CDATA[
|
||||
AND t.rk_time <= #{q.endTime}
|
||||
]]></if>
|
||||
|
||||
<!-- 领用时间范围(闭区间) -->
|
||||
<if test="q.lyStartTime != null"><![CDATA[
|
||||
AND t.ly_time >= #{q.lyStartTime}
|
||||
]]></if>
|
||||
<if test="q.lyEndTime != null"><![CDATA[
|
||||
AND t.ly_time <= #{q.lyEndTime}
|
||||
]]></if>
|
||||
|
||||
<!-- 其它模糊/等值 -->
|
||||
<if test="q.lihuoY != null and q.lihuoY != ''">
|
||||
AND t.lihuo_y like concat('%', #{q.lihuoY}, '%')
|
||||
</if>
|
||||
<if test="q.xj != null and q.xj != ''">
|
||||
AND t.xj like concat('%', #{q.xj}, '%')
|
||||
</if>
|
||||
<if test="q.billNo != null and q.billNo != ''">
|
||||
AND t.bill_no like concat('%', #{q.billNo}, '%')
|
||||
</if>
|
||||
<if test="q.billNoCk != null and q.billNoCk != ''">
|
||||
AND t.bill_no_ck like concat('%', #{q.billNoCk}, '%')
|
||||
</if>
|
||||
<if test="q.xmNo != null and q.xmNo != ''">
|
||||
AND t.xm_no like concat('%', #{q.xmNo}, '%')
|
||||
</if>
|
||||
<if test="q.xmMs != null and q.xmMs != ''">
|
||||
AND t.xm_ms like concat('%', #{q.xmMs}, '%')
|
||||
</if>
|
||||
<if test="q.wlNo != null and q.wlNo != ''">
|
||||
AND t.wl_no like concat('%', #{q.wlNo}, '%')
|
||||
</if>
|
||||
<if test="q.wlMs != null and q.wlMs != ''">
|
||||
AND t.wl_ms like concat('%', #{q.wlMs}, '%')
|
||||
</if>
|
||||
<if test="q.gysNo != null and q.gysNo != ''">
|
||||
AND t.gys_no like concat('%', #{q.gysNo}, '%')
|
||||
</if>
|
||||
<if test="q.gysMc != null and q.gysMc != ''">
|
||||
AND t.gys_mc like concat('%', #{q.gysMc}, '%')
|
||||
</if>
|
||||
|
||||
<if test="q.jhAmt != null">
|
||||
AND t.jh_amt = #{q.jhAmt}
|
||||
</if>
|
||||
<if test="q.htDj != null">
|
||||
AND t.ht_dj = #{q.htDj}
|
||||
</if>
|
||||
|
||||
<if test="q.sapNo != null and q.sapNo != ''">
|
||||
AND t.sap_no like concat('%', #{q.sapNo}, '%')
|
||||
</if>
|
||||
<if test="q.xh != null and q.xh != ''">
|
||||
AND t.xh like concat('%', #{q.xh}, '%')
|
||||
</if>
|
||||
<if test="q.jhQty != null">
|
||||
AND t.jh_qty = #{q.jhQty}
|
||||
</if>
|
||||
<if test="q.htQty != null">
|
||||
AND t.ht_qty = #{q.htQty}
|
||||
</if>
|
||||
<if test="q.dw != null and q.dw != ''">
|
||||
AND t.dw like concat('%', #{q.dw}, '%')
|
||||
</if>
|
||||
<if test="q.realQty != null">
|
||||
AND t.real_qty = #{q.realQty}
|
||||
</if>
|
||||
|
||||
<if test="q.pcode != null and q.pcode != ''">
|
||||
AND t.pcode like concat('%', #{q.pcode}, '%')
|
||||
</if>
|
||||
<if test="q.lyTime != null">
|
||||
AND t.ly_time = #{q.lyTime}
|
||||
</if>
|
||||
<if test="q.returnTime != null">
|
||||
AND t.return_time = #{q.returnTime}
|
||||
</if>
|
||||
<if test="q.trayCode != null and q.trayCode != ''">
|
||||
AND t.tray_code like concat('%', #{q.trayCode}, '%')
|
||||
</if>
|
||||
<if test="q.entityId != null and q.entityId != ''">
|
||||
AND t.entity_id like concat('%', #{q.entityId}, '%')
|
||||
</if>
|
||||
<if test="q.ckType != null and q.ckType != ''">
|
||||
AND t.ck_type like concat('%', #{q.ckType}, '%')
|
||||
</if>
|
||||
|
||||
<!-- 若查询出库单据:单值 is_chuku=1 或 列表包含 1,都要求已生成出库单号 -->
|
||||
<if test="(q.isChuku != null and q.isChuku == 1)
|
||||
or (q.isChukuList != null and q.isChukuList.size > 0 and q.isChukuList.contains(1))">
|
||||
AND t.bill_no_ck IS NOT NULL
|
||||
</if>
|
||||
|
||||
<!-- 删除标记(默认 0) -->
|
||||
<choose>
|
||||
<when test="q.isDelete != null and q.isDelete != ''">
|
||||
AND t.is_delete = #{q.isDelete}
|
||||
</when>
|
||||
<otherwise>
|
||||
AND t.is_delete = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
|
||||
<!-- 审核开启:剔除“审核失败”的明细(未审核/通过保留) -->
|
||||
<if test="needAudit != null and needAudit == 1">
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM audit_signature asg
|
||||
WHERE asg.rk_id = t.id
|
||||
AND asg.approver_id IS NOT NULL
|
||||
AND (asg.audit_result IS NOT NULL AND asg.audit_result != '1')
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
|
||||
GROUP BY t.bill_no
|
||||
ORDER BY MIN(t.rk_time) DESC
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
<select id="selectRkInfoById" parameterType="Long" resultMap="RkInfoResult">
|
||||
<include refid="selectRkInfoVo"/>
|
||||
|
||||
@@ -484,4 +484,59 @@
|
||||
AND ri.update_time <![CDATA[<]]> #{endTs})
|
||||
)
|
||||
</select>
|
||||
|
||||
|
||||
<!-- 外层聚合到 SceneAvailableVO,并级联 positions(PcdeDetailSimpleVO 列表) -->
|
||||
<resultMap id="SceneAvailableMap"
|
||||
type="com.zg.project.wisdom.domain.vo.SceneAvailableVO">
|
||||
<result property="sceneCode" column="sceneCode"/>
|
||||
<result property="sceneName" column="sceneName"/>
|
||||
<result property="availableCount" column="availableCount"/>
|
||||
<!-- 把外层 select 的 warehouseCode、sceneCode 作为参数传给内层查询 -->
|
||||
<collection property="positions"
|
||||
ofType="com.zg.project.wisdom.domain.vo.PcdeDetailSimpleVO"
|
||||
select="selectAvailablePositionsByWarehouseAndScene"
|
||||
column="{warehouseCode=warehouseCode,sceneCode=sceneCode}"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 外层:按场景统计可用数量(并输出 warehouseCode、sceneCode 提供给内层) -->
|
||||
<select id="selectAvailableByWarehouse" parameterType="string"
|
||||
resultMap="SceneAvailableMap">
|
||||
SELECT
|
||||
#{warehouseCode} AS warehouseCode, -- 传给内层用
|
||||
p.scene AS sceneCode,
|
||||
sm.scene_name AS sceneName,
|
||||
COUNT(*) AS availableCount
|
||||
FROM pcde_detail p
|
||||
LEFT JOIN scene_mapping sm
|
||||
ON sm.scene_code = p.scene
|
||||
LEFT JOIN rk_info ri
|
||||
ON ri.pcode = p.pcode
|
||||
AND ri.is_delete = 0
|
||||
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL) -- 未出库 => 占用
|
||||
WHERE p.is_delete = '0'
|
||||
AND p.warehouse = #{warehouseCode}
|
||||
AND ri.id IS NULL -- 无占用 => 可用
|
||||
GROUP BY p.scene, sm.scene_name
|
||||
ORDER BY p.scene
|
||||
</select>
|
||||
|
||||
<!-- 内层:指定仓库 + 场景,查询该场景的可用库位明细 -->
|
||||
<select id="selectAvailablePositionsByWarehouseAndScene"
|
||||
resultType="com.zg.project.wisdom.domain.vo.PcdeDetailSimpleVO">
|
||||
SELECT
|
||||
p.pcode,
|
||||
p.tag,
|
||||
p.encoded_id AS encodedId
|
||||
FROM pcde_detail p
|
||||
LEFT JOIN rk_info ri
|
||||
ON ri.pcode = p.pcode
|
||||
AND ri.is_delete = 0
|
||||
AND (ri.is_chuku = 0 OR ri.is_chuku IS NULL)
|
||||
WHERE p.is_delete = '0'
|
||||
AND p.warehouse = #{warehouseCode}
|
||||
AND p.scene = #{sceneCode}
|
||||
AND ri.id IS NULL
|
||||
ORDER BY p.pcode
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
72
src/main/resources/mybatis/wisdom/StockPhotoMapper.xml
Normal file
72
src/main/resources/mybatis/wisdom/StockPhotoMapper.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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 XML】
|
||||
- 只存一个可访问URL
|
||||
- 常用:批量插入 + 按 bill_no 查询
|
||||
-->
|
||||
<mapper namespace="com.zg.project.wisdom.mapper.StockPhotoMapper">
|
||||
|
||||
<!-- 实体映射 -->
|
||||
<resultMap id="StockPhotoMap" type="com.zg.project.wisdom.domain.StockPhoto">
|
||||
<id column="id" property="id"/>
|
||||
<result column="bill_no" property="billNo"/>
|
||||
<result column="photo_type" property="photoType"/>
|
||||
<result column="file_name" property="fileName"/>
|
||||
<result column="url" property="url"/>
|
||||
<result column="is_delete" property="isDelete"/>
|
||||
<result column="create_by" property="createBy"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_by" property="updateBy"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 单条插入:用于调试或单图场景 -->
|
||||
<insert id="insert" parameterType="com.zg.project.wisdom.domain.StockPhoto"
|
||||
useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO stock_photo (
|
||||
bill_no, photo_type, file_name, url,
|
||||
is_delete, create_by, create_time
|
||||
) VALUES (
|
||||
#{billNo}, #{photoType}, #{fileName}, #{url},
|
||||
'0', #{createBy}, NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 批量插入:多图上传时使用 -->
|
||||
<insert id="batchInsert" parameterType="java.util.List">
|
||||
INSERT INTO stock_photo (
|
||||
bill_no, photo_type, file_name, url,
|
||||
is_delete, create_by, create_time
|
||||
) VALUES
|
||||
<foreach collection="list" item="it" separator=",">
|
||||
(#{it.billNo}, #{it.photoType}, #{it.fileName}, #{it.url},
|
||||
'0', #{it.createBy}, NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 按 bill_no 查询(可选按 photoType 过滤) -->
|
||||
<select id="selectByBillNo" resultMap="StockPhotoMap">
|
||||
SELECT *
|
||||
FROM stock_photo
|
||||
WHERE bill_no = #{billNo}
|
||||
<if test="photoType != null and photoType != ''">
|
||||
AND photo_type = #{photoType}
|
||||
</if>
|
||||
AND is_delete = '0'
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<update id="softDeleteByUrls">
|
||||
UPDATE stock_photo
|
||||
SET is_delete = '1',
|
||||
update_time = NOW()
|
||||
WHERE url IN
|
||||
<foreach collection="urls" item="u" open="(" separator="," close=")">
|
||||
#{u}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user