智慧实物管理系统相关功能优化
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package com.zg.project.wisdom.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "qwen-ocr")
|
||||
@Data
|
||||
public class QwenOcrRemoteProperties {
|
||||
|
||||
/**
|
||||
* 远程 OCR 服务的完整地址
|
||||
* 例如:http://192.168.1.5:8087/ocr/extractErpByBase64
|
||||
*/
|
||||
private String baseUrl;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ public class AuditSignatureController extends BaseController
|
||||
* 上传签字图片到 MinIO
|
||||
*/
|
||||
@PostMapping("/upload")
|
||||
@Log(title = "签字图片上传", businessType = BusinessType.INSERT)
|
||||
public AjaxResult uploadSignature(@RequestParam("file") MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return AjaxResult.error("上传文件不能为空");
|
||||
@@ -56,6 +57,7 @@ public class AuditSignatureController extends BaseController
|
||||
}
|
||||
// 上传图片,接收base64格式 zhangjinbo
|
||||
@PostMapping("/uploadBase64")
|
||||
@Log(title = "签字图片上传", businessType = BusinessType.INSERT)
|
||||
public AjaxResult uploadSignatureBase64(@RequestBody Map<String, String> param) {
|
||||
String imgStr = param.get("imgStr");
|
||||
// return AjaxResult.success("上传成功").put("url", imgStr);
|
||||
@@ -150,6 +152,7 @@ public class AuditSignatureController extends BaseController
|
||||
* 审核操作(通过 / 驳回)
|
||||
*/
|
||||
@PostMapping("/audit")
|
||||
@Log(title = "审批记录", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult audit(@RequestBody AuditSignature audit) {
|
||||
try {
|
||||
auditSignatureService.audit(audit);
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.zg.project.wisdom.controller;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.zg.common.utils.StringUtils;
|
||||
import com.zg.project.wisdom.domain.dto.ExcelFieldMapping;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -123,4 +128,39 @@ public class GysJhController extends BaseController
|
||||
{
|
||||
return toAjax(gysJhService.deleteGysJhByIds(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 供应计划导入(带字段映射)
|
||||
*
|
||||
* 前端 form-data 结构示例:
|
||||
* - file: 选中的 excel 文件
|
||||
* - mapping[0].label = wlNo
|
||||
* - mapping[0].prop = E
|
||||
* - mapping[1].label = wlMs
|
||||
* - mapping[1].prop = F
|
||||
* ...
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:gysJh:import')")
|
||||
@Log(title = "供应计划导入", businessType = BusinessType.IMPORT)
|
||||
@PostMapping(
|
||||
value = "/importByMapping",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
|
||||
)
|
||||
public AjaxResult importByMapping(@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("mapping") String mappingJson) throws Exception {
|
||||
|
||||
if (file == null || file.isEmpty()) {
|
||||
return AjaxResult.error("导入文件不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(mappingJson)) {
|
||||
return AjaxResult.error("字段映射不能为空");
|
||||
}
|
||||
|
||||
// 把 JSON 字符串转成 List<ExcelFieldMapping>
|
||||
List<ExcelFieldMapping> mapping =
|
||||
JSON.parseArray(mappingJson, ExcelFieldMapping.class);
|
||||
|
||||
int rows = gysJhService.importByMapping(file, mapping);
|
||||
return AjaxResult.success("成功导入 " + rows + " 条记录");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.zg.project.wisdom.controller;
|
||||
|
||||
import com.zg.framework.aspectj.lang.annotation.Log;
|
||||
import com.zg.framework.aspectj.lang.enums.BusinessType;
|
||||
import com.zg.framework.web.domain.AjaxResult;
|
||||
import com.zg.project.wisdom.domain.dto.PhotoDeleteDTO;
|
||||
import com.zg.project.wisdom.service.PhotoService;
|
||||
@@ -38,6 +40,7 @@ public class PhotoController {
|
||||
* 返回:每张图片的可访问 URL 列表
|
||||
*/
|
||||
@PostMapping(value = "/upload/batch", consumes = "multipart/form-data")
|
||||
@Log(title = "照片上传", businessType = BusinessType.INSERT)
|
||||
public AjaxResult uploadBatch(@RequestPart("files") List<MultipartFile> files,
|
||||
@RequestParam("photoType") String photoType,
|
||||
@RequestParam("billNo") String billNo,
|
||||
@@ -88,12 +91,13 @@ public class PhotoController {
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/delete", consumes = "application/json")
|
||||
// @PreAuthorize("@ss.hasPermi('wisdom:photo:remove')")
|
||||
@Log(title = "照片删除", businessType = BusinessType.DELETE)
|
||||
public AjaxResult delete(@Validated @RequestBody PhotoDeleteDTO dto) {
|
||||
Map<String, Object> result = photoService.deleteByUrls(dto.getUrls());
|
||||
return AjaxResult.success("删除完成", result);
|
||||
}
|
||||
@GetMapping("/deleteById")
|
||||
@Log(title = "照片删除", businessType = BusinessType.DELETE)
|
||||
public AjaxResult deleteById(@RequestParam("id") Integer id){
|
||||
return AjaxResult.success(photoService.deleteById(id));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.github.pagehelper.PageHelper;
|
||||
import com.zg.project.wisdom.domain.dto.*;
|
||||
import com.zg.project.wisdom.domain.vo.DeliveryBillVO;
|
||||
import com.zg.project.wisdom.domain.vo.PcodeQtyVO;
|
||||
import com.zg.project.wisdom.service.QwenOcrRemoteService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -21,6 +22,7 @@ import com.zg.framework.web.controller.BaseController;
|
||||
import com.zg.framework.web.domain.AjaxResult;
|
||||
import com.zg.common.utils.poi.ExcelUtil;
|
||||
import com.zg.framework.web.page.TableDataInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 库存单据主Controller
|
||||
@@ -35,6 +37,9 @@ public class RkInfoController extends BaseController
|
||||
@Autowired
|
||||
private IRkInfoService rkInfoService;
|
||||
|
||||
@Autowired
|
||||
private QwenOcrRemoteService qwenOcrRemoteService;
|
||||
|
||||
|
||||
/**
|
||||
* 查询出库,借料,待审批,撤销出入库单据主列表
|
||||
@@ -47,7 +52,6 @@ public class RkInfoController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
// @PreAuthorize("@ss.hasPermi('wisdom:stock:stat')")
|
||||
@GetMapping("/pcode/{pcode}")
|
||||
public AjaxResult listRkInfoByPcode(@PathVariable String pcode) {
|
||||
List<RkInfo> rows = rkInfoService.listRkInfoByPcode(pcode);
|
||||
@@ -128,7 +132,6 @@ public class RkInfoController extends BaseController
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/warning/stock/over20")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:warning')")
|
||||
public AjaxResult getOverdueStockTopList() {
|
||||
int total = rkInfoService.countOverdueStock();
|
||||
List<RkInfo> list = rkInfoService.selectTopOverdueStock(20);
|
||||
@@ -144,7 +147,7 @@ public class RkInfoController extends BaseController
|
||||
* @return
|
||||
*/
|
||||
@PutMapping("/deleteByCkBillNo")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:deleteByBillNo')")
|
||||
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult deleteByCkBillNo(@RequestBody Map<String, String> body) {
|
||||
String billNoCk = body.get("billNoCk");
|
||||
if (billNoCk == null || billNoCk.trim().isEmpty()) {
|
||||
@@ -161,7 +164,7 @@ public class RkInfoController extends BaseController
|
||||
* 根据主键ID撤销入库
|
||||
*/
|
||||
@PostMapping("/cancel")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:deleteByBillNo')")
|
||||
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult deleteById(@RequestBody RkCancelDTO dto) {
|
||||
rkInfoService.deleteRkInfoById(dto);
|
||||
return AjaxResult.success("撤销成功");
|
||||
@@ -171,7 +174,7 @@ public class RkInfoController extends BaseController
|
||||
* 根据主键ID撤销出库
|
||||
*/
|
||||
@PostMapping("/cancelById/{id}")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:deleteByBillNo')")
|
||||
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult cancelOutStockById(@PathVariable Long id) {
|
||||
rkInfoService.cancelOutStockById(id);
|
||||
return AjaxResult.success("撤销出库成功");
|
||||
@@ -183,7 +186,7 @@ public class RkInfoController extends BaseController
|
||||
* 出入库撤销记录删除
|
||||
*/
|
||||
@PostMapping("/deleteByIds")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:deleteById')")
|
||||
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult deleteByIds(@RequestBody Long[] ids) {
|
||||
return AjaxResult.success(rkInfoService.deleteRkInfoByIds(ids));
|
||||
}
|
||||
@@ -192,7 +195,7 @@ public class RkInfoController extends BaseController
|
||||
* 根据ids恢复出库或入库
|
||||
*/
|
||||
@PostMapping("/revertByIds")
|
||||
@PreAuthorize("@ss.hasPermi('wisdom:stock:deleteById')")
|
||||
@Log(title = "库存单据主", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult revertByIds(@RequestBody Long[] ids) {
|
||||
rkInfoService.revertByIds(ids);
|
||||
return AjaxResult.success();
|
||||
@@ -302,14 +305,39 @@ public class RkInfoController extends BaseController
|
||||
|
||||
|
||||
@PostMapping("/updateDeliveryStatus")
|
||||
@Log(title = "更新配送状态", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult updateDeliveryStatus(@RequestBody RkDeliveryUpdateDTO dto) {
|
||||
|
||||
if (dto.getBillNoCk() == null) {
|
||||
return AjaxResult.error("出库单据号不能为空");
|
||||
if (dto.getIds() == null || dto.getIds().isEmpty()) {
|
||||
return AjaxResult.error("rk_info 主键ID集合不能为空");
|
||||
}
|
||||
if (dto.getIsDelivery() == null) {
|
||||
return AjaxResult.error("配送状态不能为空");
|
||||
}
|
||||
|
||||
int rows = rkInfoService.updateDeliveryStatus(dto.getBillNoCk(), dto.getIsDelivery());
|
||||
int rows = rkInfoService.updateDeliveryStatus(dto.getIds(), dto.getIsDelivery());
|
||||
return AjaxResult.success(rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片,识别采购订单号(ERP)
|
||||
*
|
||||
* 前端调用示例(multipart/form-data):
|
||||
* POST /wisdom/ocr/extractErp
|
||||
* file: <图片文件>
|
||||
*/
|
||||
@PostMapping("/extractErp")
|
||||
public AjaxResult extractErp(@RequestParam("file") MultipartFile file) {
|
||||
|
||||
if (file == null || file.isEmpty()) {
|
||||
return AjaxResult.error("上传文件不能为空");
|
||||
}
|
||||
|
||||
String erpOrderNo = qwenOcrRemoteService.extractErpOrderNo(file);
|
||||
|
||||
|
||||
return AjaxResult.success(erpOrderNo);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.zg.project.wisdom.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ExcelFieldMapping {
|
||||
|
||||
/** 后端字段名,如 wlNo、sapNo */
|
||||
private String label;
|
||||
|
||||
/** Excel 列,如 A、B、C、AA */
|
||||
private String prop;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.zg.project.wisdom.domain.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 供应计划导入DTO - 支持自定义映射关系
|
||||
*
|
||||
* @author zg
|
||||
* @date 2025-12-01
|
||||
*/
|
||||
public class GysJhImportDTO {
|
||||
|
||||
/**
|
||||
* 映射关系对象
|
||||
*/
|
||||
public static class MappingItem {
|
||||
private String label; // 字段标识名
|
||||
private String prop; // Excel列标识(如A、B、C等)
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getProp() {
|
||||
return prop;
|
||||
}
|
||||
|
||||
public void setProp(String prop) {
|
||||
this.prop = prop;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射关系数组
|
||||
*/
|
||||
private List<MappingItem> mapping;
|
||||
|
||||
/**
|
||||
* 操作人
|
||||
*/
|
||||
private String operName;
|
||||
|
||||
public List<MappingItem> getMapping() {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public void setMapping(List<MappingItem> mapping) {
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
public String getOperName() {
|
||||
return operName;
|
||||
}
|
||||
|
||||
public void setOperName(String operName) {
|
||||
this.operName = operName;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,14 @@ package com.zg.project.wisdom.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class RkDeliveryUpdateDTO {
|
||||
private String billNoCk;
|
||||
private Integer isDelivery; // 0否 1是 2配送中 3已完成
|
||||
|
||||
/** rk_info 主键ID集合 */
|
||||
private List<Long> ids;
|
||||
|
||||
/** 配送状态:0否 1是 2配送中 3已完成 */
|
||||
private Integer isDelivery;
|
||||
}
|
||||
@@ -86,15 +86,13 @@ public interface GysJhMapper
|
||||
void resetGysJhStatusBySapNos(List<String> sapNos);
|
||||
|
||||
/**
|
||||
* 根据sapNo和xmNo和wlNo查询
|
||||
* @param sapNo
|
||||
* @param xmNo
|
||||
* @param wlNo
|
||||
* @return
|
||||
* 【已注释】根据SAP订单号、项目号和物料号检查是否已存在(唯一性校验)
|
||||
* @param sapNo SAP订单号
|
||||
* @param xmNo 项目号
|
||||
* @param wlNo 物料号
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByUniqueKeys(@Param("sapNo") String sapNo,
|
||||
@Param("xmNo") String xmNo,
|
||||
@Param("wlNo") String wlNo);
|
||||
// boolean existsByUniqueKeys(@Param("sapNo") String sapNo, @Param("xmNo") String xmNo, @Param("wlNo") String wlNo);
|
||||
|
||||
/**
|
||||
* 批量修改状态
|
||||
|
||||
@@ -236,7 +236,8 @@ public interface RkInfoMapper
|
||||
* 修改出库单据状态
|
||||
*/
|
||||
|
||||
int updateDeliveryStatus(@Param("billNoCk") String billNoCk,
|
||||
|
||||
int updateDeliveryStatus(@Param("ids") List<Long> ids,
|
||||
@Param("isDelivery") Integer isDelivery);
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.zg.project.wisdom.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.zg.project.wisdom.domain.GysJh;
|
||||
import com.zg.project.wisdom.domain.dto.ExcelFieldMapping;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 供应计划Service接口
|
||||
@@ -73,4 +75,10 @@ public interface IGysJhService
|
||||
* @return
|
||||
*/
|
||||
List<GysJh> getBySapNo(String sapNo);
|
||||
|
||||
/**
|
||||
* 按字段映射导入供应计划
|
||||
*/
|
||||
int importByMapping(MultipartFile file, List<ExcelFieldMapping> mapping) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -175,12 +175,11 @@ public interface IRkInfoService
|
||||
void appendToExistingBill(PcRkInfoBatchDTO dto);
|
||||
|
||||
/**
|
||||
* 修改出库单的配送状态
|
||||
* @param billNoCk
|
||||
* @param isDelivery
|
||||
* @return
|
||||
* 按 rk_info 主键ID集合修改配送状态
|
||||
* @param ids rk_info.id 集合
|
||||
* @param isDelivery 配送状态
|
||||
*/
|
||||
int updateDeliveryStatus(String billNoCk, Integer isDelivery);
|
||||
int updateDeliveryStatus(List<Long> ids, Integer isDelivery);
|
||||
|
||||
/**
|
||||
* 获取指定出库单的配送信息
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.zg.project.wisdom.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zg.project.wisdom.config.QwenOcrRemoteProperties;
|
||||
import okhttp3.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class QwenOcrRemoteService {
|
||||
|
||||
private final QwenOcrRemoteProperties properties;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final OkHttpClient httpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(25, TimeUnit.SECONDS)
|
||||
.writeTimeout(25, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
public QwenOcrRemoteService(QwenOcrRemoteProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收上传的图片文件,转成 Base64 后,
|
||||
* 调用 250 上的 /ocr/extractErpByBase64 接口,返回采购订单号(ERP)
|
||||
*/
|
||||
public String extractErpOrderNo(MultipartFile file) {
|
||||
try {
|
||||
// 1. 读取文件字节
|
||||
byte[] bytes = file.getBytes();
|
||||
|
||||
// 2. 推断 contentType
|
||||
String contentType = file.getContentType();
|
||||
if (contentType == null || contentType.isEmpty()) {
|
||||
String name = file.getOriginalFilename();
|
||||
if (name != null && name.toLowerCase().endsWith(".png")) {
|
||||
contentType = "image/png";
|
||||
} else {
|
||||
contentType = "image/jpeg";
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 转成 Base64,并加上 data: 前缀
|
||||
String base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
String imageBase64 = "data:" + contentType + ";base64," + base64;
|
||||
|
||||
// 4. 组装 JSON 请求体:{ "imageBase64": "data:image/jpeg;base64,xxxx" }
|
||||
String json = objectMapper.createObjectNode()
|
||||
.put("imageBase64", imageBase64)
|
||||
.toString();
|
||||
|
||||
RequestBody body = RequestBody.create(
|
||||
MediaType.parse("application/json"),
|
||||
json
|
||||
);
|
||||
|
||||
String url = properties.getBaseUrl(); // yml 里配置的 /ocr/extractErpByBase64
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
// 5. 发送 HTTP 请求
|
||||
Response response = httpClient.newCall(request).execute();
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
String errorBody = response.body() != null ? response.body().string() : "no error body";
|
||||
throw new RuntimeException("调用远程 OCR 服务失败,HTTP 状态码:" +
|
||||
response.code() + ",错误信息:" + errorBody);
|
||||
}
|
||||
|
||||
String resp = Objects.requireNonNull(response.body()).string();
|
||||
JsonNode rootNode = objectMapper.readTree(resp);
|
||||
|
||||
// 远程服务返回:{ success: true, found: true/false, erpOrderNo: "0101398982" }
|
||||
JsonNode erpNode = rootNode.get("erpOrderNo");
|
||||
if (erpNode == null || erpNode.isNull()) {
|
||||
return "";
|
||||
}
|
||||
return erpNode.asText("");
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("调用远程 OCR 服务异常:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
package com.zg.project.wisdom.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import com.zg.common.exception.ServiceException;
|
||||
import com.zg.common.utils.DateUtils;
|
||||
import com.zg.common.utils.SecurityUtils;
|
||||
import com.zg.common.utils.StringUtils;
|
||||
import com.zg.project.wisdom.domain.dto.ExcelFieldMapping;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.zg.project.wisdom.mapper.GysJhMapper;
|
||||
import com.zg.project.wisdom.domain.GysJh;
|
||||
import com.zg.project.wisdom.service.IGysJhService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 供应计划Service业务层处理
|
||||
@@ -115,15 +123,15 @@ public class GysJhServiceImpl implements IGysJhService
|
||||
for (GysJh jh : jhList) {
|
||||
try {
|
||||
// 唯一性校验:订单号 + 项目号 + 物料号
|
||||
boolean exists = gysJhMapper.existsByUniqueKeys(jh.getSapNo(), jh.getXmNo(), jh.getWlNo());
|
||||
if (exists) {
|
||||
failureNum++;
|
||||
failureMsg.append("<br/>订单号:").append(jh.getSapNo())
|
||||
.append(",项目号:").append(jh.getXmNo())
|
||||
.append(",物料号:").append(jh.getWlNo())
|
||||
.append(" 已存在,跳过导入。");
|
||||
continue;
|
||||
}
|
||||
// boolean exists = gysJhMapper.existsByUniqueKeys(jh.getSapNo(), jh.getXmNo(), jh.getWlNo());
|
||||
// if (exists) {
|
||||
// failureNum++;
|
||||
// failureMsg.append("<br/>订单号:").append(jh.getSapNo())
|
||||
// .append(",项目号:").append(jh.getXmNo())
|
||||
// .append(",物料号:").append(jh.getWlNo())
|
||||
// .append(" 已存在,跳过导入。");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
jh.setCreateBy(operName);
|
||||
jh.setStatus("0");
|
||||
@@ -157,4 +165,225 @@ public class GysJhServiceImpl implements IGysJhService
|
||||
public List<GysJh> getBySapNo(String sapNo) {
|
||||
return gysJhMapper.getBySapNo(sapNo);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int importByMapping(MultipartFile file, List<ExcelFieldMapping> mapping) throws Exception {
|
||||
List<GysJh> list = parseExcel(file, mapping);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
for (GysJh item : list) {
|
||||
gysJhMapper.insertGysJh(item);
|
||||
}
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Excel -> GysJh 列表
|
||||
*/
|
||||
private List<GysJh> parseExcel(MultipartFile file, List<ExcelFieldMapping> mappingList) throws Exception {
|
||||
List<GysJh> result = new ArrayList<>();
|
||||
|
||||
try (InputStream is = file.getInputStream();
|
||||
Workbook workbook = WorkbookFactory.create(is)) {
|
||||
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
if (sheet == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 1. 把映射转换成:字段名 -> 列索引
|
||||
Map<String, Integer> fieldColumnIndex = new HashMap<>();
|
||||
for (ExcelFieldMapping m : mappingList) {
|
||||
if (m == null) {
|
||||
continue;
|
||||
}
|
||||
String label = m.getLabel(); // wlNo / jhAmt ...
|
||||
String prop = m.getProp(); // A / B / C ...
|
||||
if (StringUtils.isEmpty(label) || StringUtils.isEmpty(prop)) {
|
||||
continue;
|
||||
}
|
||||
fieldColumnIndex.put(label, excelColToIndex(prop));
|
||||
}
|
||||
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
|
||||
int firstRowNum = sheet.getFirstRowNum();
|
||||
int lastRowNum = sheet.getLastRowNum();
|
||||
|
||||
// 默认第一行是表头,从第二行开始读
|
||||
for (int rowNum = firstRowNum + 1; rowNum <= lastRowNum; rowNum++) {
|
||||
Row row = sheet.getRow(rowNum);
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 关键字段都为空则认为是空行,跳过
|
||||
String wlNoVal = getCellString(row, fieldColumnIndex.get("wlNo"), formatter);
|
||||
String sapNoVal = getCellString(row, fieldColumnIndex.get("sapNo"), formatter);
|
||||
if (StringUtils.isEmpty(wlNoVal) && StringUtils.isEmpty(sapNoVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GysJh item = new GysJh();
|
||||
// 序号可以用行号,也可以单独映射,看你需求
|
||||
item.setIndexNo((long) (rowNum));
|
||||
|
||||
// 按字段名逐个赋值
|
||||
for (Map.Entry<String, Integer> entry : fieldColumnIndex.entrySet()) {
|
||||
String field = entry.getKey();
|
||||
Integer colIndex = entry.getValue();
|
||||
String cellValue = getCellString(row, colIndex, formatter);
|
||||
if (StringUtils.isEmpty(cellValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (field) {
|
||||
case "wlNo":
|
||||
item.setWlNo(cellValue.trim());
|
||||
break;
|
||||
case "wlMs":
|
||||
item.setWlMs(cellValue.trim());
|
||||
break;
|
||||
case "xmNo":
|
||||
item.setXmNo(cellValue.trim());
|
||||
break;
|
||||
case "xmMs":
|
||||
item.setXmMs(cellValue.trim());
|
||||
break;
|
||||
case "dw":
|
||||
item.setDw(cellValue.trim());
|
||||
break;
|
||||
case "gysNo":
|
||||
item.setGysNo(cellValue.trim());
|
||||
break;
|
||||
case "gysMc":
|
||||
item.setGysMc(cellValue.trim());
|
||||
break;
|
||||
case "jhAmt":
|
||||
// BigDecimal
|
||||
item.setJhAmt(parseBigDecimal(cellValue));
|
||||
break;
|
||||
case "htDj":
|
||||
// BigDecimal
|
||||
item.setHtDj(parseBigDecimal(cellValue));
|
||||
break;
|
||||
case "jhQty":
|
||||
// Long!!这里用 Long,避免 BigDecimal 转 Long 的报错
|
||||
item.setJhQty(parseLong(cellValue));
|
||||
break;
|
||||
case "htQty":
|
||||
item.setHtQty(parseLong(cellValue));
|
||||
break;
|
||||
case "sapNo":
|
||||
item.setSapNo(cellValue.trim());
|
||||
break;
|
||||
default:
|
||||
// 以后扩展字段,就在上面加 case
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认字段
|
||||
item.setStatus("0");
|
||||
item.setIsDelete("0");
|
||||
// String username = SecurityUtils.getUsername();
|
||||
item.setCreateBy("大爷的!!!");
|
||||
item.setCreateTime(DateUtils.getNowDate());
|
||||
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel 列字母转索引(A -> 0, B -> 1, ..., AA -> 26)
|
||||
*/
|
||||
private int excelColToIndex(String col) {
|
||||
col = col.trim().toUpperCase();
|
||||
int index = 0;
|
||||
for (int i = 0; i < col.length(); i++) {
|
||||
char c = col.charAt(i);
|
||||
index = index * 26 + (c - 'A' + 1);
|
||||
}
|
||||
return index - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一获取单元格字符串
|
||||
*/
|
||||
private String getCellString(Row row, Integer colIndex, DataFormatter formatter) {
|
||||
if (row == null || colIndex == null || colIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
Cell cell = row.getCell(colIndex);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
String val = formatter.formatCellValue(cell);
|
||||
return StringUtils.isEmpty(val) ? null : val.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转 BigDecimal(空串返回 null)
|
||||
*/
|
||||
/**
|
||||
* 字符串转 BigDecimal(空串或"null"返回 null)
|
||||
*/
|
||||
private BigDecimal parseBigDecimal(String value) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String trimmedValue = value.trim();
|
||||
if ("null".equalsIgnoreCase(trimmedValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去掉千分位逗号
|
||||
String v = trimmedValue.replace(",", "").trim();
|
||||
// 再次检查处理后的值
|
||||
if (StringUtils.isEmpty(v) || "null".equalsIgnoreCase(v)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BigDecimal(v);
|
||||
} catch (NumberFormatException e) {
|
||||
// 记录详细错误信息以便调试
|
||||
// log.warn("解析BigDecimal失败,输入值: '{}'", value, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串转 Long(空串返回 null)
|
||||
*/
|
||||
private Long parseLong(String value) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmedValue = value.trim();
|
||||
if ("null".equalsIgnoreCase(trimmedValue)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String v = trimmedValue.replace(",", "").trim();
|
||||
if (StringUtils.isEmpty(v) || "null".equalsIgnoreCase(v)) {
|
||||
return null;
|
||||
}
|
||||
return Long.valueOf(v);
|
||||
} catch (NumberFormatException e) {
|
||||
// log.warn("无法解析为Long的值: '{}'", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1128,8 +1128,11 @@ public class RkInfoServiceImpl implements IRkInfoService
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateDeliveryStatus(String billNoCk, Integer isDelivery) {
|
||||
return rkInfoMapper.updateDeliveryStatus(billNoCk, isDelivery);
|
||||
public int updateDeliveryStatus(List<Long> ids, Integer isDelivery) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return rkInfoMapper.updateDeliveryStatus(ids, isDelivery);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -165,4 +165,8 @@ minio:
|
||||
|
||||
mock:
|
||||
agv-job-create-url: http://localhost:8086/mock/agv/task/create
|
||||
wcs-job-create-url: http://localhost:8086/mock/wcs/task/create
|
||||
wcs-job-create-url: http://localhost:8086/mock/wcs/task/create
|
||||
|
||||
#配送系统中调用大模型进行图片识别
|
||||
qwen-ocr:
|
||||
base-url: http://192.168.1.253:8087/ocr/extractErpByBase64
|
||||
@@ -76,14 +76,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
AND status != '1'
|
||||
</select>
|
||||
|
||||
<select id="existsByUniqueKeys" resultType="boolean">
|
||||
<!-- 【已注释】唯一性校验查询:基于SAP订单号、项目号、物料号判断是否已存在 -->
|
||||
<!-- <select id="existsByUniqueKeys" resultType="boolean">
|
||||
SELECT COUNT(1)
|
||||
FROM gys_jh
|
||||
WHERE sap_no = #{sapNo}
|
||||
AND xm_no = #{xmNo}
|
||||
AND wl_no = #{wlNo}
|
||||
AND is_delete != '1'
|
||||
</select>
|
||||
</select> -->
|
||||
|
||||
<select id="selectByIds" resultMap="GysJhResult">
|
||||
SELECT * FROM gys_jh
|
||||
@@ -230,4 +231,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{id}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
|
||||
</mapper>
|
||||
|
||||
|
||||
@@ -1223,8 +1223,10 @@
|
||||
<update id="updateDeliveryStatus">
|
||||
UPDATE rk_info
|
||||
SET is_delivery = #{isDelivery}
|
||||
WHERE bill_no_ck = #{billNoCk}
|
||||
AND is_delete = '0'
|
||||
WHERE is_delete = '0'
|
||||
AND id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user