From 7686d70e59bc837023fe1d5daf9612d47d3d8651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=98=8A=E5=A4=A9?= <15984976+n2319_0@user.noreply.gitee.com> Date: Thu, 21 May 2026 14:22:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=80=E5=94=AE=E5=8F=91=E8=B4=A7=EF=BC=8C?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dbg/product-import-image.env | 2 + .dbg/trae-debug-log-default.ndjson | 36 + gear-admin/src/main/resources/application.yml | 4 +- gear-mat/pom.xml | 4 + .../mat/controller/MatProductController.java | 1419 ++++++++++++++++- .../gear/mat/domain/bo/MatMaterialOutBo.java | 5 + .../mat/domain/bo/MatProductImportBo.java | 3 + .../mat/domain/bo/MatPurchaseInDetailBo.java | 5 + .../impl/MatMaterialOutServiceImpl.java | 1 + .../service/impl/MatProductServiceImpl.java | 4 + .../impl/MatPurchaseInDetailServiceImpl.java | 1 + .../GearShippingOrderDetailController.java | 60 + .../GearStockIoOrderController.java | 37 +- .../com/gear/oa/domain/GearOrderDetail.java | 4 + .../oa/domain/GearShippingOrderDetail.java | 44 + .../gear/oa/domain/bo/GearOrderDetailBo.java | 4 + .../gear/oa/domain/bo/GearReceivableBo.java | 2 + .../domain/bo/GearShippingOrderDetailBo.java | 40 + .../domain/vo/GearShippingOrderDetailVo.java | 38 + .../mapper/GearShippingOrderDetailMapper.java | 8 + .../oa/mapper/MatMaterialSimpleMapper.java | 5 +- .../IGearShippingOrderDetailService.java | 24 + .../oa/service/IGearStockIoOrderService.java | 2 + .../oa/service/impl/GearOrderServiceImpl.java | 44 +- .../impl/GearReceivableServiceImpl.java | 29 +- .../GearShippingOrderDetailServiceImpl.java | 94 ++ .../impl/GearShippingOrderServiceImpl.java | 35 - .../impl/GearStockIoOrderServiceImpl.java | 30 +- .../mapper/oa/GearOrderDetailMapper.xml | 10 +- .../resources/mapper/oa/GearOrderMapper.xml | 30 +- gear-ui3/src/api/mat/product.js | 6 +- gear-ui3/src/api/oms/shippingOrder.js | 42 + gear-ui3/src/utils/request.js | 4 + gear-ui3/src/views/mat/auxiliary/index.vue | 14 + gear-ui3/src/views/mat/dashboard/index.vue | 71 +- gear-ui3/src/views/mat/product/detail.vue | 2 +- gear-ui3/src/views/mat/product/index.vue | 112 +- .../src/views/mat/productOutsource/index.vue | 116 +- gear-ui3/src/views/mat/raw/index.vue | 14 + .../src/views/oms/order/panels/orderPage.vue | 298 +++- gear-ui3/src/views/oms/salesman/index.vue | 460 +++++- gear-ui3/src/views/shipping/order/index.vue | 250 ++- gear-ui3/src/views/wms/stockIoOrder/in.vue | 101 +- gear-ui3/src/views/wms/stockIoOrder/out.vue | 101 +- .../stockIoOrder/panels/stockIoOrderPage.vue | 101 +- script/sql/mysql/item/gear_shipping_order.sql | 23 + 46 files changed, 3250 insertions(+), 489 deletions(-) create mode 100644 .dbg/product-import-image.env create mode 100644 .dbg/trae-debug-log-default.ndjson create mode 100644 gear-oa/src/main/java/com/gear/oa/controller/GearShippingOrderDetailController.java create mode 100644 gear-oa/src/main/java/com/gear/oa/domain/GearShippingOrderDetail.java create mode 100644 gear-oa/src/main/java/com/gear/oa/domain/bo/GearShippingOrderDetailBo.java create mode 100644 gear-oa/src/main/java/com/gear/oa/domain/vo/GearShippingOrderDetailVo.java create mode 100644 gear-oa/src/main/java/com/gear/oa/mapper/GearShippingOrderDetailMapper.java create mode 100644 gear-oa/src/main/java/com/gear/oa/service/IGearShippingOrderDetailService.java create mode 100644 gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderDetailServiceImpl.java diff --git a/.dbg/product-import-image.env b/.dbg/product-import-image.env new file mode 100644 index 0000000..5b83c3d --- /dev/null +++ b/.dbg/product-import-image.env @@ -0,0 +1,2 @@ +DEBUG_SERVER_URL=http://127.0.0.1:7777/event +DEBUG_SESSION_ID=product-import-image diff --git a/.dbg/trae-debug-log-default.ndjson b/.dbg/trae-debug-log-default.ndjson new file mode 100644 index 0000000..d7d528f --- /dev/null +++ b/.dbg/trae-debug-log-default.ndjson @@ -0,0 +1,36 @@ +{"ts": 1779294580277, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}} +{"ts": 1779294580300, "event": "import_header", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}} +{"ts": 1779294580378, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "rowsWithPictures": 56, "totalPictures": 65}} +{"ts": 1779294580412, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}} +{"ts": 1779294580415, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}} +{"ts": 1779294580417, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}} +{"ts": 1779294580420, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}} +{"ts": 1779294580423, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}} +{"ts": 1779294642453, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "resultSize": 236, "rowsWithProductImages": 55}} +{"ts": 1779294704871, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}} +{"ts": 1779294704873, "event": "import_header", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}} +{"ts": 1779294704910, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "rowsWithPictures": 56, "totalPictures": 65}} +{"ts": 1779294704913, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}} +{"ts": 1779294704916, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}} +{"ts": 1779294704918, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}} +{"ts": 1779294704921, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}} +{"ts": 1779294704925, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}} +{"ts": 1779294767240, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "resultSize": 236, "rowsWithProductImages": 55}} +{"ts": 1779294901849, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}} +{"ts": 1779294901852, "event": "import_header", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}} +{"ts": 1779294901883, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "rowsWithPictures": 56, "totalPictures": 65}} +{"ts": 1779294901886, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}} +{"ts": 1779294901889, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}} +{"ts": 1779294901893, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}} +{"ts": 1779294901895, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}} +{"ts": 1779294901897, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}} +{"ts": 1779294980062, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "resultSize": 236, "rowsWithProductImages": 55}} +{"ts": 1779295230641, "event": "import_open_workbook", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "fileSize": 204246016, "workbookClass": "org.apache.poi.hssf.usermodel.HSSFWorkbook"}} +{"ts": 1779295230643, "event": "import_header", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "sheetClass": "org.apache.poi.hssf.usermodel.HSSFSheet", "lastRowNum": 333, "headerIndex": {"序号": 0, "产品名称": 1, "产品图片": 2, "产品规格": 3, "价格": 4, "产品型号": 5}, "colProductName": 1, "colSpec": 3, "colModel": 5, "colUnitPrice": -1, "colRemark": -1, "colImages": 2, "xssfCellImagesPart": null}} +{"ts": 1779295230672, "event": "import_pictures_map", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "rowsWithPictures": 56, "totalPictures": 65}} +{"ts": 1779295230676, "event": "import_image_miss_row", "payload": {"rowIndex": 1, "productName": "床头柜", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_DE28F1DE70D7472299A0EB95E12ABDE8\",1)"}} +{"ts": 1779295230679, "event": "import_image_miss_row", "payload": {"rowIndex": 2, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_D1DA8BA74D034AC0892AF25D577A3DFC\",1)"}} +{"ts": 1779295230682, "event": "import_image_miss_row", "payload": {"rowIndex": 3, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_C5A9E5DB6F484CD4B0ED9E78B40B850B\",1)"}} +{"ts": 1779295230684, "event": "import_image_miss_row", "payload": {"rowIndex": 4, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_E9A371D0A3D74FE8AFF9840431B05F3E\",1)"}} +{"ts": 1779295230686, "event": "import_image_miss_row", "payload": {"rowIndex": 5, "productName": "置物架", "cellType": "FORMULA", "hasDispImgFormula": true, "cellText": "_xlfn.DISPIMG(\"ID_690D368FCF9F4BFAB19C8654A6C61FFD\",1)"}} +{"ts": 1779295298724, "event": "import_done_parse", "payload": {"fileName": "询价-产品目录(整理)2604.xls", "resultSize": 236, "rowsWithProductImages": 55}} diff --git a/gear-admin/src/main/resources/application.yml b/gear-admin/src/main/resources/application.yml index 6985b7f..a782e09 100644 --- a/gear-admin/src/main/resources/application.yml +++ b/gear-admin/src/main/resources/application.yml @@ -76,9 +76,9 @@ spring: servlet: multipart: # 单个文件大小 - max-file-size: 10MB + max-file-size: 512MB # 设置总上传的文件大小 - max-request-size: 20MB + max-request-size: 512MB # 服务模块 devtools: restart: diff --git a/gear-mat/pom.xml b/gear-mat/pom.xml index 1eeb908..ad2319c 100644 --- a/gear-mat/pom.xml +++ b/gear-mat/pom.xml @@ -15,6 +15,10 @@ com.gear gear-common + + com.gear + gear-system + commons-collections commons-collections diff --git a/gear-mat/src/main/java/com/gear/mat/controller/MatProductController.java b/gear-mat/src/main/java/com/gear/mat/controller/MatProductController.java index 70c5377..20ec691 100644 --- a/gear-mat/src/main/java/com/gear/mat/controller/MatProductController.java +++ b/gear-mat/src/main/java/com/gear/mat/controller/MatProductController.java @@ -3,8 +3,11 @@ package com.gear.mat.controller; import java.util.List; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; @@ -19,6 +22,7 @@ import com.gear.common.core.validate.AddGroup; import com.gear.common.core.validate.EditGroup; import com.gear.common.core.validate.QueryGroup; import com.gear.common.enums.BusinessType; +import com.gear.common.utils.StringUtils; import com.gear.common.utils.poi.ExcelUtil; import com.gear.mat.domain.vo.MatProductVo; import com.gear.mat.domain.vo.MatProductWithMaterialsVo; @@ -26,10 +30,48 @@ import com.gear.mat.domain.bo.MatProductBo; import com.gear.mat.domain.bo.MatProductImportBo; import com.gear.mat.service.IMatProductService; import com.gear.common.core.page.TableDataInfo; +import com.gear.system.domain.vo.SysOssVo; +import com.gear.system.service.ISysOssService; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.web.multipart.MultipartFile; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.parsers.DocumentBuilderFactory; /** * 产品基础信息 @@ -38,12 +80,14 @@ import java.util.Collections; * @date 2026-01-30 */ @Validated +@Slf4j @RequiredArgsConstructor @RestController @RequestMapping("/mat/product") public class MatProductController extends BaseController { private final IMatProductService iMatProductService; + private final ISysOssService sysOssService; /** * 查询产品基础信息列表 @@ -83,10 +127,1377 @@ public class MatProductController extends BaseController { if (file == null || file.isEmpty()) { return R.fail("导入文件不能为空"); } - try (InputStream is = file.getInputStream()) { - List list = ExcelUtil.importExcel(is, MatProductImportBo.class); - String msg = iMatProductService.importByExcel(list, updateSupport); - return R.ok(msg); + Map inspect = new LinkedHashMap<>(); + List list = readImportData(file, inspect); + String msg = iMatProductService.importByExcel(list, updateSupport); + if (!inspect.isEmpty()) { + log.info("产品导入图片检测 fileName={} updateSupport={} {}", file.getOriginalFilename(), updateSupport, formatInspectSummary(inspect)); + } + return R.ok(msg); + } + + @PostMapping("/importInspect") + public R> importInspect(@RequestPart("file") MultipartFile file) throws Exception { + if (file == null || file.isEmpty()) { + return R.fail("导入文件不能为空"); + } + String filename = file.getOriginalFilename(); + Map out = new LinkedHashMap<>(); + out.put("fileName", filename); + out.put("fileSize", file.getSize()); + try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) { + out.put("workbookClass", workbook == null ? null : workbook.getClass().getName()); + Sheet sheet = workbook.getNumberOfSheets() > 0 ? workbook.getSheetAt(0) : null; + if (sheet == null) { + out.put("err", "no_sheet"); + return R.ok(out); + } + out.put("sheetName", sheet.getSheetName()); + out.put("sheetClass", sheet.getClass().getName()); + out.put("lastRowNum", sheet.getLastRowNum()); + DataFormatter formatter = new DataFormatter(); + FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); + Row headerRow = sheet.getRow(0); + if (headerRow == null) { + out.put("err", "no_header"); + return R.ok(out); + } + Map headerIndex = readHeaderIndex(headerRow, formatter, evaluator); + int colProductName = getCol(headerIndex, "产品名称", -1); + int colImages = getCol(headerIndex, "产品图片", -1); + out.put("headerIndex", headerIndex); + out.put("colProductName", colProductName); + out.put("colImages", colImages); + out.put("xssfCellImagesPart", detectXssfCellImagesPart(workbook)); + + Map> drawingByRow = extractPicturesByRow(sheet, colImages); + out.put("drawingRows", drawingByRow.size()); + out.put("drawingPictures", sumPictures(drawingByRow)); + + Map> drawingByName = Collections.emptyMap(); + if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { + drawingByName = extractPicturesByName((org.apache.poi.xssf.usermodel.XSSFSheet) sheet); + } + out.put("drawingNames", drawingByName == null ? 0 : drawingByName.size()); + + Map> cellByRow = Collections.emptyMap(); + if (workbook instanceof XSSFWorkbook) { + cellByRow = extractCellImagesByRow((XSSFWorkbook) workbook, sheet, colImages); + } + out.put("cellRows", cellByRow.size()); + out.put("cellPictures", sumPictures(cellByRow)); + + int lastRowNum = sheet.getLastRowNum(); + int dispRows = 0; + int matchedRows = 0; + List> missing = new ArrayList<>(); + for (int r = 1; r <= lastRowNum; r++) { + String dispName = extractDispImgNameFromRow(sheet, r, colImages); + if (StringUtils.isBlank(dispName)) continue; + dispRows++; + boolean hasDrawingRow = drawingByRow.containsKey(r) && !drawingByRow.get(r).isEmpty(); + boolean hasCellRow = cellByRow.containsKey(r) && !cellByRow.get(r).isEmpty(); + boolean hasDrawingName = false; + if (drawingByName != null && StringUtils.isNotBlank(dispName)) { + List bins = drawingByName.get(dispName); + if (bins == null) bins = drawingByName.get(dispName.toLowerCase()); + hasDrawingName = bins != null && !bins.isEmpty(); + } + boolean ok = hasDrawingRow || hasCellRow || hasDrawingName; + if (ok) matchedRows++; + if (!ok && missing.size() < 80) { + Map it = new LinkedHashMap<>(); + it.put("rowIndex", r); + if (colProductName >= 0) { + Row row = sheet.getRow(r); + String pn = row == null ? "" : StringUtils.trim(cellString(row.getCell(colProductName), formatter, evaluator)); + it.put("productName", pn); + } + it.put("dispName", dispName); + it.put("hasDrawingRow", hasDrawingRow); + it.put("hasCellRow", hasCellRow); + it.put("hasDrawingName", hasDrawingName); + missing.add(it); + } + } + out.put("dispimgRows", dispRows); + out.put("matchedRows", matchedRows); + out.put("missingRowsSample", missing); + return R.ok(out); + } + } + + private List readImportData(MultipartFile file, Map inspectOut) throws Exception { + String filename = file.getOriginalFilename(); + String lower = filename == null ? "" : filename.toLowerCase(); + boolean maybeExcel = lower.endsWith(".xlsx") || lower.endsWith(".xls"); + if (!maybeExcel) { + try (InputStream is = file.getInputStream()) { + return ExcelUtil.importExcel(is, MatProductImportBo.class); + } + } + + try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) { + Map dbgOpen = new LinkedHashMap<>(); + dbgOpen.put("fileName", filename); + dbgOpen.put("fileSize", file.getSize()); + dbgOpen.put("workbookClass", workbook == null ? null : workbook.getClass().getName()); + debugPost("import_open_workbook", dbgOpen); + Sheet sheet = workbook.getNumberOfSheets() > 0 ? workbook.getSheetAt(0) : null; + if (sheet == null) { + Map dbg = new LinkedHashMap<>(); + dbg.put("fileName", filename); + debugPost("import_no_sheet", dbg); + return Collections.emptyList(); + } + DataFormatter formatter = new DataFormatter(); + FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); + + Row headerRow = sheet.getRow(0); + if (headerRow == null) { + Map dbg = new LinkedHashMap<>(); + dbg.put("fileName", filename); + dbg.put("sheetClass", sheet.getClass().getName()); + debugPost("import_no_header", dbg); + return Collections.emptyList(); + } + Map headerIndex = readHeaderIndex(headerRow, formatter, evaluator); + int colProductName = getCol(headerIndex, "产品名称", -1); + int colSpec = getCol(headerIndex, "产品规格", -1); + int colModel = getCol(headerIndex, "产品型号", -1); + int colUnitPrice = getCol(headerIndex, "产品单价", -1); + int colRemark = getCol(headerIndex, "备注", -1); + int colImages = getCol(headerIndex, "产品图片", -1); + + Map dbgHeader = new LinkedHashMap<>(); + dbgHeader.put("fileName", filename); + dbgHeader.put("sheetClass", sheet.getClass().getName()); + dbgHeader.put("lastRowNum", sheet.getLastRowNum()); + dbgHeader.put("headerIndex", headerIndex); + dbgHeader.put("colProductName", colProductName); + dbgHeader.put("colSpec", colSpec); + dbgHeader.put("colModel", colModel); + dbgHeader.put("colUnitPrice", colUnitPrice); + dbgHeader.put("colRemark", colRemark); + dbgHeader.put("colImages", colImages); + dbgHeader.put("xssfCellImagesPart", detectXssfCellImagesPart(workbook)); + debugPost("import_header", dbgHeader); + + Map> picturesByRow = extractPicturesByRow(sheet, colImages); + Map> picturesByName = Collections.emptyMap(); + if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { + try { + picturesByName = extractPicturesByName((org.apache.poi.xssf.usermodel.XSSFSheet) sheet); + } catch (Exception e) { + picturesByName = Collections.emptyMap(); + } + } else if (sheet instanceof org.apache.poi.hssf.usermodel.HSSFSheet) { + try { + picturesByName = extractPicturesByName((org.apache.poi.hssf.usermodel.HSSFSheet) sheet); + } catch (Exception e) { + picturesByName = Collections.emptyMap(); + } + } + if (workbook instanceof XSSFWorkbook) { + try { + Map> embedded = extractCellImagesByRow((XSSFWorkbook) workbook, sheet, colImages); + if (!embedded.isEmpty()) { + mergePicturesByRow(picturesByRow, embedded); + } + } catch (Exception e) { + Map dbg = new LinkedHashMap<>(); + dbg.put("fileName", filename); + dbg.put("err", e.getClass().getSimpleName()); + debugPost("import_cellimages_err", dbg); + } + } + Map dbgPic = new LinkedHashMap<>(); + dbgPic.put("fileName", filename); + dbgPic.put("rowsWithPictures", picturesByRow.size()); + dbgPic.put("totalPictures", sumPictures(picturesByRow)); + dbgPic.put("picturesByName", picturesByName == null ? 0 : picturesByName.size()); + debugPost("import_pictures_map", dbgPic); + List result = new ArrayList<>(); + Map uploadCache = new HashMap<>(); + if (inspectOut != null) { + inspectOut.putAll(buildInspectSummary(workbook, sheet, colProductName, colImages, formatter, evaluator, picturesByRow, picturesByName)); + inspectOut.put("extractedRows", picturesByRow == null ? 0 : picturesByRow.size()); + inspectOut.put("extractedPictures", sumPictures(picturesByRow)); + inspectOut.put("extractedNames", picturesByName == null ? 0 : picturesByName.size()); + inspectOut.put("pictureAnchorSample", buildPictureAnchorSample(workbook, sheet, 12)); + } + + int lastRowNum = sheet.getLastRowNum(); + int missLogged = 0; + for (int r = 1; r <= lastRowNum; r++) { + Row row = sheet.getRow(r); + if (row == null) continue; + + String productName = colProductName >= 0 ? cellString(row.getCell(colProductName), formatter, evaluator) : ""; + productName = StringUtils.trim(productName); + if (StringUtils.isBlank(productName)) { + continue; + } + + MatProductImportBo bo = new MatProductImportBo(); + bo.setProductName(productName); + if (colSpec >= 0) { + bo.setSpec(truncate(cellString(row.getCell(colSpec), formatter, evaluator), 255)); + } + if (colModel >= 0) { + bo.setModel(truncate(cellString(row.getCell(colModel), formatter, evaluator), 255)); + } + if (colUnitPrice >= 0) { + bo.setUnitPrice(cellDecimal(row.getCell(colUnitPrice), formatter, evaluator)); + } + if (colRemark >= 0) { + bo.setRemark(cellString(row.getCell(colRemark), formatter, evaluator)); + } + + String imgCell = colImages >= 0 ? cellString(row.getCell(colImages), formatter, evaluator) : ""; + imgCell = StringUtils.trim(imgCell); + boolean needsPicture = StringUtils.isBlank(imgCell) + || containsDispImgFormula(row.getCell(colImages)) + || imgCell.toUpperCase().contains("DISPIMG"); + if (!needsPicture) { + bo.setProductImages(imgCell); + } else { + String dispName = colImages >= 0 ? extractDispImgNameFromRow(sheet, r, colImages) : null; + List bins = getPicturesForRow(picturesByRow, r); + if (bins.isEmpty() && colImages >= 0 && picturesByName != null && !picturesByName.isEmpty()) { + if (StringUtils.isNotBlank(dispName)) { + List byName = picturesByName.get(dispName); + if (byName == null) byName = picturesByName.get(dispName.toLowerCase()); + if (byName == null) byName = picturesByName.get(normalizeDispId(dispName)); + if (byName != null && !byName.isEmpty()) { + bins = byName; + } + } + } + if (bins.isEmpty() && missLogged < 5) { + Cell c = colImages >= 0 ? row.getCell(colImages) : null; + Map dbgMiss = new LinkedHashMap<>(); + dbgMiss.put("rowIndex", r); + dbgMiss.put("productName", productName); + dbgMiss.put("cellType", c == null ? null : c.getCellType().name()); + dbgMiss.put("hasDispImgFormula", containsDispImgFormula(c)); + dbgMiss.put("cellText", imgCell); + dbgMiss.put("dispName", dispName); + debugPost("import_image_miss_row", dbgMiss); + missLogged++; + } + if (!bins.isEmpty()) { + List ossIds = new ArrayList<>(); + int i = 1; + for (ImageBin bin : bins) { + String ext = StringUtils.isBlank(bin.ext) ? "png" : bin.ext; + String ct = StringUtils.isBlank(bin.contentType) ? guessImageContentType(ext) : bin.contentType; + String on = productName + "_" + r + "_" + (i++) + "." + ext; + String cacheKey = buildUploadCacheKey(dispName, bin, ossIds.size() + 1, bins.size()); + String cached = uploadCache.get(cacheKey); + if (StringUtils.isNotBlank(cached)) { + ossIds.add(cached); + continue; + } + MultipartFile mf = new ByteArrayMultipartFile(bin.data, on, ct); + SysOssVo vo = sysOssService.upload(mf); + if (vo != null && vo.getOssId() != null) { + String oid = String.valueOf(vo.getOssId()); + ossIds.add(oid); + uploadCache.put(cacheKey, oid); + } + } + if (!ossIds.isEmpty()) { + bo.setProductImages(String.join(",", ossIds)); + } + } + } + + result.add(bo); + } + Map dbgDone = new LinkedHashMap<>(); + dbgDone.put("fileName", filename); + dbgDone.put("resultSize", result.size()); + dbgDone.put("rowsWithProductImages", countWithImages(result)); + debugPost("import_done_parse", dbgDone); + return result; + } + } + + private static String formatInspectSummary(Map inspect) { + int dispRows = asInt(inspect.get("dispimgRows")); + int matchedRows = asInt(inspect.get("matchedRows")); + int missingRows = asInt(inspect.get("missingRows")); + int wbPictures = asInt(inspect.get("workbookPictures")); + int sheetPictures = asInt(inspect.get("sheetPictures")); + int sheetShapes = asInt(inspect.get("sheetShapes")); + int extractedRows = asInt(inspect.get("extractedRows")); + int extractedPictures = asInt(inspect.get("extractedPictures")); + int extractedNames = asInt(inspect.get("extractedNames")); + int dispUnique = asInt(inspect.get("dispimgUnique")); + int dispIdInPictures = asInt(inspect.get("dispimgIdInPictures")); + StringBuilder sb = new StringBuilder(); + sb.append("图片检测:DISPIMG行=").append(dispRows) + .append(",命中=").append(matchedRows) + .append(",缺失=").append(missingRows) + .append(";workbookPictures=").append(wbPictures) + .append(",sheetPictures=").append(sheetPictures) + .append(",sheetShapes=").append(sheetShapes) + .append(";extractedPictures=").append(extractedPictures) + .append(",extractedRows=").append(extractedRows) + .append(",extractedNames=").append(extractedNames) + .append(";dispUnique=").append(dispUnique) + .append(",dispIdInPictures=").append(dispIdInPictures); + Object sample = inspect.get("missingRowsSample"); + if (sample instanceof List && !((List) sample).isEmpty()) { + sb.append(";缺失示例="); + int n = 0; + for (Object it : (List) sample) { + if (!(it instanceof Map)) continue; + Map m = (Map) it; + if (n > 0) sb.append(" | "); + sb.append("row=").append(m.get("rowIndex")) + .append(",name=").append(m.get("productName")) + .append(",id=").append(m.get("dispName")); + n++; + if (n >= 10) break; + } + } + String suggestion = Objects.toString(inspect.get("suggestion"), ""); + suggestion = StringUtils.trim(suggestion); + if (StringUtils.isNotBlank(suggestion)) { + sb.append(";建议=").append(suggestion); + } + Object anchorSample = inspect.get("pictureAnchorSample"); + if (anchorSample instanceof List && !((List) anchorSample).isEmpty()) { + sb.append(";图片锚点示例=").append(String.valueOf(anchorSample)); + } + Object picIdSample = inspect.get("picturesIdSample"); + if (picIdSample instanceof List && !((List) picIdSample).isEmpty()) { + sb.append(";图片ID示例=").append(String.valueOf(picIdSample)); + } + return sb.toString(); + } + + private static int asInt(Object v) { + if (v == null) return 0; + if (v instanceof Number) return ((Number) v).intValue(); + try { + return Integer.parseInt(String.valueOf(v)); + } catch (Exception e) { + return 0; + } + } + + private static Map buildInspectSummary(Workbook workbook, + Sheet sheet, + int colProductName, + int colImages, + DataFormatter formatter, + FormulaEvaluator evaluator, + Map> picturesByRow, + Map> picturesByName) { + Map out = new LinkedHashMap<>(); + if (sheet == null || colImages < 0) return out; + int workbookPictures = 0; + try { + List all = workbook == null ? null : workbook.getAllPictures(); + workbookPictures = all == null ? 0 : all.size(); + } catch (Exception ignore) { + workbookPictures = -1; + } + int sheetShapes = 0; + int sheetPictures = 0; + try { + if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { + org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sheet; + XSSFDrawing drawing = xs.getDrawingPatriarch(); + if (drawing == null) { + for (POIXMLDocumentPart rel : xs.getRelations()) { + if (rel instanceof XSSFDrawing) { + drawing = (XSSFDrawing) rel; + break; + } + } + } + if (drawing != null) { + List shapes = drawing.getShapes(); + sheetShapes = shapes == null ? 0 : shapes.size(); + if (shapes != null) { + for (XSSFShape s : shapes) { + if (s instanceof XSSFPicture) sheetPictures++; + } + } + } + } else if (sheet instanceof org.apache.poi.hssf.usermodel.HSSFSheet) { + org.apache.poi.hssf.usermodel.HSSFSheet hs = (org.apache.poi.hssf.usermodel.HSSFSheet) sheet; + org.apache.poi.hssf.usermodel.HSSFPatriarch p = hs.getDrawingPatriarch(); + if (p != null) { + List children = p.getChildren(); + sheetShapes = children == null ? 0 : children.size(); + if (children != null) { + for (HSSFShape s : children) { + if (s instanceof HSSFPicture) sheetPictures++; + } + } + } + } + } catch (Exception ignore) { + } + int lastRowNum = sheet.getLastRowNum(); + int dispRows = 0; + int matchedRows = 0; + java.util.Set dispIds = new java.util.LinkedHashSet<>(); + int dispIdInPictures = 0; + List> missingSample = new ArrayList<>(); + for (int r = 1; r <= lastRowNum; r++) { + String dispName = extractDispImgNameFromRow(sheet, r, colImages); + if (StringUtils.isBlank(dispName)) continue; + dispRows++; + dispIds.add(dispName); + boolean ok = false; + List rowBins = getPicturesForRow(picturesByRow, r); + if (rowBins != null && !rowBins.isEmpty()) ok = true; + if (!ok && picturesByName != null && !picturesByName.isEmpty()) { + List byName = picturesByName.get(dispName); + if (byName == null) byName = picturesByName.get(dispName.toLowerCase()); + if (byName == null) byName = picturesByName.get(normalizeDispId(dispName)); + if (byName != null && !byName.isEmpty()) ok = true; + } + if (ok) { + matchedRows++; + } else if (missingSample.size() < 80) { + Map it = new LinkedHashMap<>(); + it.put("rowIndex", r); + it.put("dispName", dispName); + if (colProductName >= 0) { + Row row = sheet.getRow(r); + String pn = row == null ? "" : StringUtils.trim(cellString(row.getCell(colProductName), formatter, evaluator)); + it.put("productName", pn); + } else { + it.put("productName", ""); + } + missingSample.add(it); + } + } + if (picturesByName != null && !picturesByName.isEmpty() && !dispIds.isEmpty()) { + for (String id : dispIds) { + if (StringUtils.isBlank(id)) continue; + if (picturesByName.containsKey(id) || picturesByName.containsKey(id.toLowerCase()) || picturesByName.containsKey(normalizeDispId(id))) { + dispIdInPictures++; + } + } + } + out.put("dispimgRows", dispRows); + out.put("matchedRows", matchedRows); + out.put("missingRows", Math.max(0, dispRows - matchedRows)); + out.put("missingRowsSample", missingSample); + out.put("dispimgUnique", dispIds.size()); + out.put("dispimgIdInPictures", dispIdInPictures); + out.put("workbookPictures", workbookPictures); + out.put("sheetShapes", sheetShapes); + out.put("sheetPictures", sheetPictures); + if (dispRows > 0 && matchedRows == 0 && workbookPictures == 0) { + out.put("suggestion", "当前文件图片未内嵌(xls常见),请另存为xlsx或改为图片URL/上传后填ossId"); + } else if (dispRows > 0 && matchedRows == 0 && workbookPictures > 0) { + out.put("suggestion", "文件内确实存在图片,但未能按行/ID匹配到DISPIMG;请根据“图片锚点示例”继续定位映射关系"); + } + if (picturesByName != null && !picturesByName.isEmpty()) { + List sample = new ArrayList<>(); + for (String k : picturesByName.keySet()) { + if (StringUtils.isBlank(k)) continue; + if (!k.toUpperCase().startsWith("ID_")) continue; + sample.add(k); + if (sample.size() >= 12) break; + } + out.put("picturesIdSample", sample); + } + return out; + } + + private static String extractDispImgNameFromRow(Sheet sheet, int rowIdx, int colIdx) { + if (sheet == null || rowIdx < 0 || colIdx < 0) return null; + Cell c = getCellInMergedRegion(sheet, rowIdx, colIdx); + String s = getDispImgSourceText(c); + if (StringUtils.isBlank(s)) return null; + Matcher m = DISPIMG_PATTERN.matcher(s); + if (!m.find()) return null; + return normalizeDispId(m.group(1)); + } + + private static int sumPictures(Map> picturesByRow) { + int sum = 0; + if (picturesByRow == null || picturesByRow.isEmpty()) return 0; + for (List list : picturesByRow.values()) { + if (list != null) sum += list.size(); + } + return sum; + } + + private static int countWithImages(List list) { + if (list == null || list.isEmpty()) return 0; + int n = 0; + for (MatProductImportBo bo : list) { + String v = bo == null ? null : bo.getProductImages(); + if (StringUtils.isNotBlank(v)) n++; + } + return n; + } + + private static Object detectXssfCellImagesPart(Workbook workbook) { + if (!(workbook instanceof XSSFWorkbook)) return null; + try { + XSSFWorkbook xssf = (XSSFWorkbook) workbook; + OPCPackage pkg = xssf.getPackage(); + List parts = pkg.getPartsByName(Pattern.compile("/xl/cellimages.*\\.xml")); + return parts == null ? 0 : parts.size(); + } catch (Exception e) { + return "err:" + e.getClass().getSimpleName(); + } + } + + private static void mergePicturesByRow(Map> base, Map> extra) { + if (base == null || extra == null || extra.isEmpty()) return; + for (Map.Entry> e : extra.entrySet()) { + Integer row = e.getKey(); + List bins = e.getValue(); + if (row == null || bins == null || bins.isEmpty()) continue; + base.computeIfAbsent(row, k -> new ArrayList<>()).addAll(bins); + } + } + + private static final Pattern DISPIMG_PATTERN = Pattern.compile("DISPIMG\\(\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); + + private static Map> extractCellImagesByRow(XSSFWorkbook workbook, Sheet sheet, int colFilter) throws Exception { + if (!(sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet)) return Collections.emptyMap(); + if (colFilter < 0) return Collections.emptyMap(); + + OPCPackage pkg = workbook.getPackage(); + List parts = pkg.getPartsByName(Pattern.compile("/xl/cellimages.*\\.xml")); + if (parts == null || parts.isEmpty()) return Collections.emptyMap(); + + Map nameToImage = new HashMap<>(); + for (PackagePart part : parts) { + if (part == null) continue; + Map one = parseCellImagesPart(pkg, part); + if (one.isEmpty()) continue; + for (Map.Entry e : one.entrySet()) { + String k = e.getKey(); + ImageBin v = e.getValue(); + if (StringUtils.isBlank(k) || v == null) continue; + nameToImage.putIfAbsent(k, v); + nameToImage.putIfAbsent(k.toLowerCase(), v); + String nk = normalizeDispId(k); + if (StringUtils.isNotBlank(nk)) nameToImage.putIfAbsent(nk, v); + } + } + if (nameToImage.isEmpty()) return Collections.emptyMap(); + + Map> byRow = new HashMap<>(); + int lastRowNum = sheet.getLastRowNum(); + for (int r = 1; r <= lastRowNum; r++) { + Cell cell = getCellInMergedRegion(sheet, r, colFilter); + String formulaOrText = getDispImgSourceText(cell); + if (StringUtils.isBlank(formulaOrText)) continue; + Matcher m = DISPIMG_PATTERN.matcher(formulaOrText); + if (!m.find()) continue; + String name = StringUtils.trim(m.group(1)); + if (StringUtils.isBlank(name)) continue; + String nk = normalizeDispId(name); + ImageBin bin = nameToImage.get(nk); + if (bin == null) bin = nameToImage.get(name); + if (bin == null) bin = nameToImage.get(name.toLowerCase()); + if (bin == null) continue; + byRow.computeIfAbsent(r, k -> new ArrayList<>()).add(bin); + } + + Map dbg = new LinkedHashMap<>(); + dbg.put("sheetName", sheet.getSheetName()); + dbg.put("mappedRows", byRow.size()); + dbg.put("mappedPictures", sumPictures(byRow)); + debugPost("import_cellimages_map", dbg); + + return byRow; + } + + private static Cell getCellInMergedRegion(Sheet sheet, int rowIdx, int colFilter) { + if (sheet == null || rowIdx < 0 || colFilter < 0) return null; + Row row = sheet.getRow(rowIdx); + if (row == null) return null; + Cell direct = row.getCell(colFilter); + if (direct != null && hasDispImgText(direct)) return direct; + int n = sheet.getNumMergedRegions(); + for (int i = 0; i < n; i++) { + org.apache.poi.ss.util.CellRangeAddress r = sheet.getMergedRegion(i); + if (r == null) continue; + if (rowIdx < r.getFirstRow() || rowIdx > r.getLastRow()) continue; + if (colFilter < r.getFirstColumn() || colFilter > r.getLastColumn()) continue; + Row baseRow = sheet.getRow(r.getFirstRow()); + Cell base = baseRow == null ? null : baseRow.getCell(r.getFirstColumn()); + if (base != null) return base; + } + return direct; + } + + private static List getPicturesForRow(Map> picturesByRow, int rowIdx) { + if (picturesByRow == null || picturesByRow.isEmpty() || rowIdx < 0) return Collections.emptyList(); + List bins = picturesByRow.get(rowIdx); + if (bins != null && !bins.isEmpty()) return bins; + bins = picturesByRow.get(rowIdx - 1); + if (bins != null && !bins.isEmpty()) return bins; + bins = picturesByRow.get(rowIdx + 1); + if (bins != null && !bins.isEmpty()) return bins; + return Collections.emptyList(); + } + + private static String normalizeDispId(String raw) { + String s = raw == null ? "" : StringUtils.trim(raw); + if (StringUtils.isBlank(s)) return null; + String cleaned = s.replaceAll("[^A-Za-z0-9_]", ""); + cleaned = StringUtils.trim(cleaned); + if (StringUtils.isBlank(cleaned)) return null; + return cleaned.toUpperCase(); + } + + private static String buildUploadCacheKey(String dispName, ImageBin bin, int idx, int total) { + String dn = normalizeDispId(dispName); + if (StringUtils.isNotBlank(dn)) { + if (total <= 1) return dn; + return dn + "#" + idx; + } + return md5Hex(bin == null ? null : bin.data); + } + + private static String md5Hex(byte[] data) { + if (data == null || data.length == 0) return ""; + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + return ""; + } + byte[] dig = md.digest(data); + char[] hex = new char[dig.length * 2]; + final char[] digits = "0123456789abcdef".toCharArray(); + for (int i = 0; i < dig.length; i++) { + int v = dig[i] & 0xff; + hex[i * 2] = digits[v >>> 4]; + hex[i * 2 + 1] = digits[v & 0x0f]; + } + return new String(hex); + } + + private static boolean hasDispImgText(Cell cell) { + String s = getDispImgSourceText(cell); + return StringUtils.isNotBlank(s) && s.toUpperCase().contains("DISPIMG"); + } + + private static String getDispImgSourceText(Cell cell) { + if (cell == null) return null; + try { + if (cell.getCellType() == CellType.FORMULA) { + return cell.getCellFormula(); + } + } catch (Exception ignore) { + } + try { + if (cell.getCellType() == CellType.STRING) { + return cell.getStringCellValue(); + } + } catch (Exception ignore) { + } + try { + return String.valueOf(cell); + } catch (Exception ignore) { + return null; + } + } + + private static Map parseCellImagesPart(OPCPackage pkg, PackagePart cellImagesPart) throws Exception { + Document doc = parseXml(cellImagesPart.getInputStream()); + PackagePart relsPart = pkg.getPart(PackagingURIHelper.getRelationshipPartName(cellImagesPart.getPartName())); + Document relsDoc = parseXml(relsPart.getInputStream()); + + Map ridToTarget = new HashMap<>(); + NodeList relNodes = relsDoc.getElementsByTagName("Relationship"); + for (int i = 0; i < relNodes.getLength(); i++) { + Node n = relNodes.item(i); + if (!(n instanceof Element)) continue; + Element e = (Element) n; + String id = e.getAttribute("Id"); + String target = e.getAttribute("Target"); + if (StringUtils.isBlank(id) || StringUtils.isBlank(target)) continue; + ridToTarget.put(id, target); + } + + Map nameToImage = new HashMap<>(); + NodeList picNodes = doc.getElementsByTagNameNS("*", "pic"); + for (int i = 0; i < picNodes.getLength(); i++) { + Node n = picNodes.item(i); + if (!(n instanceof Element)) continue; + Element pic = (Element) n; + Element cNvPr = firstDescendantByLocalName(pic, "cNvPr"); + if (cNvPr == null) continue; + String name = StringUtils.trim(cNvPr.getAttribute("name")); + String descr = StringUtils.trim(cNvPr.getAttribute("descr")); + + Element blip = firstDescendantByLocalName(pic, "blip"); + if (blip == null) continue; + + String rid = blip.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed"); + if (StringUtils.isBlank(rid)) rid = blip.getAttribute("r:embed"); + if (StringUtils.isBlank(rid)) rid = blip.getAttribute("embed"); + if (StringUtils.isBlank(rid)) continue; + + String target = ridToTarget.get(rid); + if (StringUtils.isBlank(target)) continue; + + ImageBin bin = loadImageBin(pkg, cellImagesPart, target); + if (bin == null) continue; + if (StringUtils.isNotBlank(name)) { + nameToImage.putIfAbsent(name, bin); + nameToImage.putIfAbsent(name.toLowerCase(), bin); + String nk = normalizeDispId(name); + if (StringUtils.isNotBlank(nk)) nameToImage.putIfAbsent(nk, bin); + } + if (StringUtils.isNotBlank(descr)) { + nameToImage.putIfAbsent(descr, bin); + nameToImage.putIfAbsent(descr.toLowerCase(), bin); + String dk = normalizeDispId(descr); + if (StringUtils.isNotBlank(dk)) nameToImage.putIfAbsent(dk, bin); + } + } + + Map dbg = new LinkedHashMap<>(); + dbg.put("cellImages", nameToImage.size()); + debugPost("import_cellimages_parsed", dbg); + + return nameToImage; + } + + private static ImageBin loadImageBin(OPCPackage pkg, PackagePart basePart, String target) { + if (pkg == null || basePart == null || StringUtils.isBlank(target)) return null; + List candidates = buildTargetPathCandidates(basePart, target); + for (String path : candidates) { + if (StringUtils.isBlank(path)) continue; + if (!path.startsWith("/")) path = "/" + path; + PackagePart imgPart; + try { + imgPart = pkg.getPart(PackagingURIHelper.createPartName(path)); + } catch (Exception ex) { + continue; + } + if (imgPart == null) continue; + byte[] data; + try { + data = readBytes(imgPart.getInputStream()); + } catch (Exception e) { + continue; + } + if (data.length == 0) continue; + String ext = ""; + int dot = path.lastIndexOf('.'); + if (dot >= 0 && dot < path.length() - 1) ext = path.substring(dot + 1); + String ct = imgPart.getContentType(); + return new ImageBin(data, ext, ct); + } + return null; + } + + private static List buildTargetPathCandidates(PackagePart basePart, String target) { + if (basePart == null || StringUtils.isBlank(target)) return Collections.emptyList(); + String t = StringUtils.trim(target); + List list = new ArrayList<>(); + try { + URI base = basePart.getPartName().getURI(); + URI resolved = base.resolve(t); + String path = resolved == null ? null : resolved.getPath(); + if (StringUtils.isNotBlank(path)) list.add(path); + } catch (Exception ignore) { + } + if (!t.startsWith("/")) { + list.add("/xl/" + t); + } else { + list.add(t); + } + String noDotDot = t; + while (noDotDot.startsWith("../")) { + noDotDot = noDotDot.substring(3); + } + if (!Objects.equals(noDotDot, t)) { + list.add("/xl/" + noDotDot); + list.add("/xl/" + t.replace("../", "")); + } + for (String p : new ArrayList<>(list)) { + if (StringUtils.isBlank(p)) continue; + if (p.startsWith("/media/")) { + list.add("/xl" + p); + } + if (p.startsWith("/xl/../")) { + list.add(p.replace("/xl/../", "/xl/")); + } + } + List uniq = new ArrayList<>(); + for (String p : list) { + String v = StringUtils.trim(p); + if (StringUtils.isBlank(v)) continue; + if (!uniq.contains(v)) uniq.add(v); + } + return uniq; + } + + private static Document parseXml(InputStream in) throws Exception { + DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + f.setNamespaceAware(true); + f.setExpandEntityReferences(false); + try { + f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (Exception ignore) { + } + try { + f.setFeature("http://xml.org/sax/features/external-general-entities", false); + f.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (Exception ignore) { + } + return f.newDocumentBuilder().parse(in); + } + + private static byte[] readBytes(InputStream in) throws IOException { + if (in == null) return new byte[0]; + try (InputStream input = in; ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buf = new byte[8192]; + int len; + while ((len = input.read(buf)) > 0) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } + } + + private static Element firstChildElementByLocalName(Element parent, String local) { + NodeList children = parent.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node n = children.item(i); + if (n instanceof Element) { + String ln = n.getLocalName(); + if (local.equals(ln)) return (Element) n; + } + } + return null; + } + + private static Element firstDescendantByLocalName(Element parent, String local) { + NodeList list = parent.getElementsByTagNameNS("*", local); + for (int i = 0; i < list.getLength(); i++) { + Node n = list.item(i); + if (n instanceof Element) return (Element) n; + } + return null; + } + + private static Element ascendToLocalName(Element start, String local) { + Node cur = start; + while (cur != null) { + if (cur instanceof Element) { + String ln = ((Element) cur).getLocalName(); + if (local.equals(ln)) return (Element) cur; + } + cur = cur.getParentNode(); + } + return null; + } + + private static boolean colMatchesFilterConsideringMerged(Sheet sheet, int rowIdx, int colIdx, int colFilter) { + if (colIdx == colFilter) return true; + if (sheet == null) return false; + int n = sheet.getNumMergedRegions(); + for (int i = 0; i < n; i++) { + org.apache.poi.ss.util.CellRangeAddress r = sheet.getMergedRegion(i); + if (r == null) continue; + if (rowIdx < r.getFirstRow() || rowIdx > r.getLastRow()) continue; + if (colIdx < r.getFirstColumn() || colIdx > r.getLastColumn()) continue; + return colFilter >= r.getFirstColumn() && colFilter <= r.getLastColumn(); + } + return false; + } + + private static int parseRowIndex0Based(String cellRef) { + int i = 0; + while (i < cellRef.length() && !Character.isDigit(cellRef.charAt(i))) i++; + if (i >= cellRef.length()) return -1; + int row = Integer.parseInt(cellRef.substring(i)); + return row - 1; + } + + private static int parseColIndex0Based(String cellRef) { + int i = 0; + int col = 0; + while (i < cellRef.length() && Character.isLetter(cellRef.charAt(i))) { + char ch = Character.toUpperCase(cellRef.charAt(i)); + col = col * 26 + (ch - 'A' + 1); + i++; + } + return col - 1; + } + + private static void debugPost(String event, Map payload) { + String url = debugServerUrl(); + if (StringUtils.isBlank(url)) return; + Map body = new LinkedHashMap<>(); + body.put("ts", System.currentTimeMillis()); + body.put("event", event); + body.put("payload", payload); + byte[] bytes = toJson(body).getBytes(StandardCharsets.UTF_8); + HttpURLConnection conn = null; + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setConnectTimeout(1200); + conn.setReadTimeout(2000); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + conn.getOutputStream().write(bytes); + int code = conn.getResponseCode(); + if (code >= 400) { + try (InputStream err = conn.getErrorStream()) { + drain(err); + } + } else { + try (InputStream ok = conn.getInputStream()) { + drain(ok); + } + } + } catch (Exception ignore) { + } finally { + if (conn != null) conn.disconnect(); + } + } + + private static void drain(InputStream in) throws IOException { + if (in == null) return; + byte[] buf = new byte[512]; + while (in.read(buf) >= 0) { + } + } + + private static String debugServerUrl() { + String env = System.getenv("DEBUG_SERVER_URL"); + if (StringUtils.isNotBlank(env)) return env; + try { + java.nio.file.Path p = Paths.get(".dbg", "product-import-image.env"); + if (!Files.exists(p)) return null; + List lines = Files.readAllLines(p, StandardCharsets.UTF_8); + for (String line : lines) { + String s = StringUtils.trim(line); + if (StringUtils.isBlank(s)) continue; + if (s.startsWith("DEBUG_SERVER_URL=")) { + return StringUtils.trim(s.substring("DEBUG_SERVER_URL=".length())); + } + } + } catch (IOException ignore) { + } + return null; + } + + private static String toJson(Object obj) { + if (obj == null) return "null"; + if (obj instanceof String) return "\"" + escapeJson((String) obj) + "\""; + if (obj instanceof Number || obj instanceof Boolean) return String.valueOf(obj); + if (obj instanceof Map) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean first = true; + for (Object e : ((Map) obj).entrySet()) { + Map.Entry me = (Map.Entry) e; + Object k = me.getKey(); + if (k == null) continue; + if (!first) sb.append(","); + first = false; + sb.append(toJson(String.valueOf(k))).append(":").append(toJson(me.getValue())); + } + sb.append("}"); + return sb.toString(); + } + if (obj instanceof Iterable) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean first = true; + for (Object it : (Iterable) obj) { + if (!first) sb.append(","); + first = false; + sb.append(toJson(it)); + } + sb.append("]"); + return sb.toString(); + } + return toJson(String.valueOf(obj)); + } + + private static String escapeJson(String s) { + if (s == null) return ""; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': + sb.append("\\\\"); + break; + case '"': + sb.append("\\\""); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + if (c < 0x20) { + sb.append(String.format("\\u%04x", (int) c)); + } else { + sb.append(c); + } + } + } + return sb.toString(); + } + + private static Map readHeaderIndex(Row headerRow, DataFormatter formatter, FormulaEvaluator evaluator) { + Map map = new LinkedHashMap<>(); + short last = headerRow.getLastCellNum(); + for (int c = 0; c < last; c++) { + Cell cell = headerRow.getCell(c); + String name = cellString(cell, formatter, evaluator); + name = StringUtils.trim(name); + if (StringUtils.isBlank(name)) continue; + if (!map.containsKey(name)) { + map.put(name, c); + } + } + return map; + } + + private static int getCol(Map headerIndex, String key, int def) { + Integer v = headerIndex.get(key); + return v == null ? def : v; + } + + private static String cellString(Cell cell, DataFormatter formatter, FormulaEvaluator evaluator) { + if (cell == null) return ""; + try { + return formatter.formatCellValue(cell, evaluator); + } catch (Exception e) { + try { + return formatter.formatCellValue(cell); + } catch (Exception ignore) { + return ""; + } + } + } + + private static BigDecimal cellDecimal(Cell cell, DataFormatter formatter, FormulaEvaluator evaluator) { + String s = StringUtils.trim(cellString(cell, formatter, evaluator)); + if (StringUtils.isBlank(s)) return null; + try { + return new BigDecimal(s); + } catch (Exception e) { + return null; + } + } + + private static boolean containsDispImgFormula(Cell cell) { + if (cell == null) return false; + if (cell.getCellType() != CellType.FORMULA) return false; + String f = cell.getCellFormula(); + if (f == null) return false; + return f.toUpperCase().contains("DISPIMG"); + } + + private static String truncate(String val, int maxLen) { + String s = val == null ? "" : StringUtils.trim(val); + if (StringUtils.isBlank(s)) return s; + return s.length() <= maxLen ? s : s.substring(0, maxLen); + } + + private static Map> extractPicturesByRow(Sheet sheet, int colFilter) { + Map> map = new HashMap<>(); + if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { + org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sheet; + XSSFDrawing drawing = xs.getDrawingPatriarch(); + if (drawing == null) { + for (POIXMLDocumentPart rel : xs.getRelations()) { + if (rel instanceof XSSFDrawing) { + drawing = (XSSFDrawing) rel; + break; + } + } + } + if (drawing != null) { + for (XSSFShape shape : drawing.getShapes()) { + if (!(shape instanceof XSSFPicture)) continue; + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + if (anchor == null) continue; + int row = anchor.getRow1(); + int col = anchor.getCol1(); + if (colFilter >= 0 && !colMatchesFilterConsideringMerged(sheet, row, col, colFilter)) continue; + PictureData pd = pic.getPictureData(); + if (pd == null) continue; + ImageBin bin = new ImageBin(pd.getData(), pd.suggestFileExtension(), pd.getMimeType()); + map.computeIfAbsent(row, k -> new ArrayList<>()).add(bin); + } + } + } else if (sheet.getWorkbook() instanceof HSSFWorkbook) { + org.apache.poi.hssf.usermodel.HSSFSheet hs = (org.apache.poi.hssf.usermodel.HSSFSheet) sheet; + org.apache.poi.hssf.usermodel.HSSFPatriarch patriarch = hs.getDrawingPatriarch(); + if (patriarch != null) { + for (HSSFShape shape : patriarch.getChildren()) { + if (!(shape instanceof HSSFPicture)) continue; + HSSFPicture pic = (HSSFPicture) shape; + if (!(pic.getAnchor() instanceof HSSFClientAnchor)) continue; + HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); + int row = anchor.getRow1(); + int col = anchor.getCol1(); + if (colFilter >= 0 && !colMatchesFilterConsideringMerged(sheet, row, col, colFilter)) continue; + PictureData pd = pic.getPictureData(); + if (pd == null) continue; + ImageBin bin = new ImageBin(pd.getData(), pd.suggestFileExtension(), guessImageContentType(pd.suggestFileExtension())); + map.computeIfAbsent(row, k -> new ArrayList<>()).add(bin); + } + } + } + if (colFilter >= 0 && map.isEmpty()) { + return extractPicturesByRow(sheet, -1); + } + return map; + } + + private static Map> extractPicturesByName(org.apache.poi.xssf.usermodel.XSSFSheet sheet) { + if (sheet == null) return Collections.emptyMap(); + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + if (drawing == null) { + for (POIXMLDocumentPart rel : sheet.getRelations()) { + if (rel instanceof XSSFDrawing) { + drawing = (XSSFDrawing) rel; + break; + } + } + } + if (drawing == null) return Collections.emptyMap(); + Map> map = new HashMap<>(); + for (XSSFShape shape : drawing.getShapes()) { + if (!(shape instanceof XSSFPicture)) continue; + XSSFPicture pic = (XSSFPicture) shape; + PictureData pd = pic.getPictureData(); + if (pd == null) continue; + ImageBin bin = new ImageBin(pd.getData(), pd.suggestFileExtension(), pd.getMimeType()); + try { + String name = null; + String descr = null; + org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture ct = pic.getCTPicture(); + if (ct != null && ct.getNvPicPr() != null && ct.getNvPicPr().getCNvPr() != null) { + name = StringUtils.trim(ct.getNvPicPr().getCNvPr().getName()); + descr = StringUtils.trim(ct.getNvPicPr().getCNvPr().getDescr()); + } + if (StringUtils.isNotBlank(name)) { + map.computeIfAbsent(name, k -> new ArrayList<>()).add(bin); + map.computeIfAbsent(name.toLowerCase(), k -> new ArrayList<>()).add(bin); + String nk = normalizeDispId(name); + if (StringUtils.isNotBlank(nk)) map.computeIfAbsent(nk, k -> new ArrayList<>()).add(bin); + } + if (StringUtils.isNotBlank(descr)) { + map.computeIfAbsent(descr, k -> new ArrayList<>()).add(bin); + map.computeIfAbsent(descr.toLowerCase(), k -> new ArrayList<>()).add(bin); + String dk = normalizeDispId(descr); + if (StringUtils.isNotBlank(dk)) map.computeIfAbsent(dk, k -> new ArrayList<>()).add(bin); + } + } catch (Exception ignore) { + } + } + return map; + } + + private static Map> extractPicturesByName(org.apache.poi.hssf.usermodel.HSSFSheet sheet) { + if (sheet == null) return Collections.emptyMap(); + org.apache.poi.hssf.usermodel.HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + if (patriarch == null) return Collections.emptyMap(); + Map> map = new HashMap<>(); + for (HSSFShape shape : patriarch.getChildren()) { + if (!(shape instanceof HSSFPicture)) continue; + HSSFPicture pic = (HSSFPicture) shape; + PictureData pd = pic.getPictureData(); + if (pd == null) continue; + ImageBin bin = new ImageBin(pd.getData(), pd.suggestFileExtension(), guessImageContentType(pd.suggestFileExtension())); + String name = null; + try { + name = StringUtils.trim(shape.getShapeName()); + } catch (Exception ignore) { + } + if (StringUtils.isNotBlank(name)) { + map.computeIfAbsent(name, k -> new ArrayList<>()).add(bin); + map.computeIfAbsent(name.toLowerCase(), k -> new ArrayList<>()).add(bin); + String nk = normalizeDispId(name); + if (StringUtils.isNotBlank(nk)) map.computeIfAbsent(nk, k -> new ArrayList<>()).add(bin); + } + } + return map; + } + + private static List> buildPictureAnchorSample(Workbook workbook, Sheet sheet, int limit) { + int lim = Math.max(0, limit); + if (sheet == null || lim == 0) return Collections.emptyList(); + List> list = new ArrayList<>(); + try { + if (sheet instanceof org.apache.poi.xssf.usermodel.XSSFSheet) { + org.apache.poi.xssf.usermodel.XSSFSheet xs = (org.apache.poi.xssf.usermodel.XSSFSheet) sheet; + XSSFDrawing drawing = xs.getDrawingPatriarch(); + if (drawing == null) { + for (POIXMLDocumentPart rel : xs.getRelations()) { + if (rel instanceof XSSFDrawing) { + drawing = (XSSFDrawing) rel; + break; + } + } + } + if (drawing != null) { + for (XSSFShape shape : drawing.getShapes()) { + if (!(shape instanceof XSSFPicture)) continue; + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor a = pic.getPreferredSize(); + Map it = new LinkedHashMap<>(); + it.put("type", "xssf"); + it.put("row1", a == null ? null : a.getRow1()); + it.put("col1", a == null ? null : a.getCol1()); + it.put("row2", a == null ? null : a.getRow2()); + it.put("col2", a == null ? null : a.getCol2()); + try { + org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTPicture ct = pic.getCTPicture(); + if (ct != null && ct.getNvPicPr() != null && ct.getNvPicPr().getCNvPr() != null) { + it.put("name", ct.getNvPicPr().getCNvPr().getName()); + it.put("descr", ct.getNvPicPr().getCNvPr().getDescr()); + } + } catch (Exception ignore) { + } + list.add(it); + if (list.size() >= lim) break; + } + } + } else if (sheet instanceof org.apache.poi.hssf.usermodel.HSSFSheet) { + org.apache.poi.hssf.usermodel.HSSFSheet hs = (org.apache.poi.hssf.usermodel.HSSFSheet) sheet; + org.apache.poi.hssf.usermodel.HSSFPatriarch p = hs.getDrawingPatriarch(); + if (p != null) { + for (HSSFShape shape : p.getChildren()) { + if (!(shape instanceof HSSFPicture)) continue; + HSSFPicture pic = (HSSFPicture) shape; + HSSFClientAnchor a = null; + try { + if (pic.getAnchor() instanceof HSSFClientAnchor) a = (HSSFClientAnchor) pic.getAnchor(); + } catch (Exception ignore) { + } + Map it = new LinkedHashMap<>(); + it.put("type", "hssf"); + it.put("row1", a == null ? null : a.getRow1()); + it.put("col1", a == null ? null : a.getCol1()); + it.put("row2", a == null ? null : a.getRow2()); + it.put("col2", a == null ? null : a.getCol2()); + try { + it.put("shapeName", shape.getShapeName()); + } catch (Exception ignore) { + } + try { + it.put("picIndex", pic.getPictureIndex()); + } catch (Exception ignore) { + } + list.add(it); + if (list.size() >= lim) break; + } + } + } + } catch (Exception ignore) { + } + return list; + } + + private static String guessImageContentType(String ext) { + String e = ext == null ? "" : ext.toLowerCase(); + if (Objects.equals(e, "png")) return "image/png"; + if (Objects.equals(e, "jpg") || Objects.equals(e, "jpeg")) return "image/jpeg"; + if (Objects.equals(e, "gif")) return "image/gif"; + if (Objects.equals(e, "bmp")) return "image/bmp"; + if (Objects.equals(e, "webp")) return "image/webp"; + return "application/octet-stream"; + } + + private static class ImageBin { + private final byte[] data; + private final String ext; + private final String contentType; + + private ImageBin(byte[] data, String ext, String contentType) { + this.data = data == null ? new byte[0] : data; + this.ext = ext; + this.contentType = contentType; + } + } + + private static class ByteArrayMultipartFile implements MultipartFile { + private final byte[] bytes; + private final String fileName; + private final String contentType; + + private ByteArrayMultipartFile(byte[] bytes, String fileName, String contentType) { + this.bytes = bytes == null ? new byte[0] : bytes; + this.fileName = fileName == null ? "file" : fileName; + this.contentType = contentType; + } + + @Override + public String getName() { + return "file"; + } + + @Override + public String getOriginalFilename() { + return fileName; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return bytes.length == 0; + } + + @Override + public long getSize() { + return bytes.length; + } + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(bytes); + } + + @Override + public void transferTo(java.io.File dest) throws java.io.IOException, java.lang.IllegalStateException { + try (java.io.InputStream in = getInputStream(); java.io.OutputStream out = new java.io.FileOutputStream(dest)) { + byte[] buf = new byte[8192]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } } } diff --git a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatMaterialOutBo.java b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatMaterialOutBo.java index 7a309c0..6f118bb 100644 --- a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatMaterialOutBo.java +++ b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatMaterialOutBo.java @@ -67,6 +67,11 @@ public class MatMaterialOutBo extends BaseEntity { */ private String remark; + /** + * 厂家(配料厂家) + */ + private String factory; + /** * 开始时间 */ diff --git a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatProductImportBo.java b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatProductImportBo.java index 8fa5d42..5df7155 100644 --- a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatProductImportBo.java +++ b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatProductImportBo.java @@ -27,4 +27,7 @@ public class MatProductImportBo { @ExcelProperty("备注") private String remark; + + @ExcelProperty("产品图片") + private String productImages; } diff --git a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatPurchaseInDetailBo.java b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatPurchaseInDetailBo.java index 2d89ed2..23e1255 100644 --- a/gear-mat/src/main/java/com/gear/mat/domain/bo/MatPurchaseInDetailBo.java +++ b/gear-mat/src/main/java/com/gear/mat/domain/bo/MatPurchaseInDetailBo.java @@ -72,6 +72,11 @@ public class MatPurchaseInDetailBo extends BaseEntity { */ private String remark; + /** + * 厂家(配料厂家) + */ + private String factory; + /** * 开始时间 diff --git a/gear-mat/src/main/java/com/gear/mat/service/impl/MatMaterialOutServiceImpl.java b/gear-mat/src/main/java/com/gear/mat/service/impl/MatMaterialOutServiceImpl.java index a9070f6..2e0c599 100644 --- a/gear-mat/src/main/java/com/gear/mat/service/impl/MatMaterialOutServiceImpl.java +++ b/gear-mat/src/main/java/com/gear/mat/service/impl/MatMaterialOutServiceImpl.java @@ -71,6 +71,7 @@ public class MatMaterialOutServiceImpl implements IMatMaterialOutService { lqw.eq(StringUtils.isNotBlank(bo.getOperator()), "mmo.operator", bo.getOperator()); lqw.ge(bo.getBeginTime() != null, "mmo.out_time", bo.getBeginTime()); lqw.le(bo.getEndTime() != null, "mmo.out_time", bo.getEndTime()); + lqw.eq(StringUtils.isNotBlank(bo.getFactory()), "mm.factory", bo.getFactory()); // 逻辑删除 lqw.eq("mmo.del_flag", 0); // 排序 diff --git a/gear-mat/src/main/java/com/gear/mat/service/impl/MatProductServiceImpl.java b/gear-mat/src/main/java/com/gear/mat/service/impl/MatProductServiceImpl.java index ed59903..11f0c93 100644 --- a/gear-mat/src/main/java/com/gear/mat/service/impl/MatProductServiceImpl.java +++ b/gear-mat/src/main/java/com/gear/mat/service/impl/MatProductServiceImpl.java @@ -255,6 +255,10 @@ public class MatProductServiceImpl implements IMatProductService { data.setModel(model); data.setUnitPrice(row.getUnitPrice()); data.setRemark(row.getRemark()); + String images = StringUtils.trim(row.getProductImages()); + if (StringUtils.isNotBlank(images)) { + data.setProductImages(images); + } data.setProductType(normalizeProductType(data.getProductType())); data.setCurrentStock(normalizeCurrentStock(data.getCurrentStock())); if (exist == null) { diff --git a/gear-mat/src/main/java/com/gear/mat/service/impl/MatPurchaseInDetailServiceImpl.java b/gear-mat/src/main/java/com/gear/mat/service/impl/MatPurchaseInDetailServiceImpl.java index 1d4eb97..c3d29d9 100644 --- a/gear-mat/src/main/java/com/gear/mat/service/impl/MatPurchaseInDetailServiceImpl.java +++ b/gear-mat/src/main/java/com/gear/mat/service/impl/MatPurchaseInDetailServiceImpl.java @@ -85,6 +85,7 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi qw.ge(bo.getBeginTime() != null, "mpid.in_time", bo.getBeginTime()); qw.le(bo.getEndTime() != null, "mpid.in_time", bo.getEndTime()); qw.eq(StringUtils.isNotBlank(bo.getOperator()), "mpid.operator", bo.getOperator()); + qw.eq(StringUtils.isNotBlank(bo.getFactory()), "mm.factory", bo.getFactory()); // 逻辑删除 qw.eq("mpid.del_flag", 0); // 排序 diff --git a/gear-oa/src/main/java/com/gear/oa/controller/GearShippingOrderDetailController.java b/gear-oa/src/main/java/com/gear/oa/controller/GearShippingOrderDetailController.java new file mode 100644 index 0000000..11d8a10 --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/controller/GearShippingOrderDetailController.java @@ -0,0 +1,60 @@ +package com.gear.oa.controller; + +import com.gear.common.annotation.Log; +import com.gear.common.annotation.RepeatSubmit; +import com.gear.common.core.controller.BaseController; +import com.gear.common.core.domain.PageQuery; +import com.gear.common.core.domain.R; +import com.gear.common.core.page.TableDataInfo; +import com.gear.common.core.validate.AddGroup; +import com.gear.common.core.validate.EditGroup; +import com.gear.common.enums.BusinessType; +import com.gear.oa.domain.bo.GearShippingOrderDetailBo; +import com.gear.oa.domain.vo.GearShippingOrderDetailVo; +import com.gear.oa.service.IGearShippingOrderDetailService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/oa/shippingOrderDetail") +public class GearShippingOrderDetailController extends BaseController { + + private final IGearShippingOrderDetailService iGearShippingOrderDetailService; + + @GetMapping("/list") + public TableDataInfo list(GearShippingOrderDetailBo bo, PageQuery pageQuery) { + return iGearShippingOrderDetailService.queryPageList(bo, pageQuery); + } + + @GetMapping("/{detailId}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long detailId) { + return R.ok(iGearShippingOrderDetailService.queryById(detailId)); + } + + @Log(title = "发货单据明细", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody GearShippingOrderDetailBo bo) { + return toAjax(iGearShippingOrderDetailService.insertByBo(bo)); + } + + @Log(title = "发货单据明细", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody GearShippingOrderDetailBo bo) { + return toAjax(iGearShippingOrderDetailService.updateByBo(bo)); + } + + @Log(title = "发货单据明细", businessType = BusinessType.DELETE) + @DeleteMapping("/{detailIds}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] detailIds) { + return toAjax(iGearShippingOrderDetailService.deleteWithValidByIds(Arrays.asList(detailIds), true)); + } +} diff --git a/gear-oa/src/main/java/com/gear/oa/controller/GearStockIoOrderController.java b/gear-oa/src/main/java/com/gear/oa/controller/GearStockIoOrderController.java index 4347b4f..9a22201 100644 --- a/gear-oa/src/main/java/com/gear/oa/controller/GearStockIoOrderController.java +++ b/gear-oa/src/main/java/com/gear/oa/controller/GearStockIoOrderController.java @@ -9,6 +9,8 @@ import com.gear.common.core.page.TableDataInfo; import com.gear.common.core.validate.AddGroup; import com.gear.common.core.validate.EditGroup; import com.gear.common.enums.BusinessType; +import com.gear.common.exception.ServiceException; +import com.gear.common.utils.StringUtils; import com.gear.common.utils.poi.ExcelUtil; import com.gear.oa.domain.bo.GearStockIoOrderBo; import com.gear.oa.domain.bo.GearStockIoOrderWithDetailBo; @@ -54,23 +56,39 @@ public class GearStockIoOrderController extends BaseController { } @GetMapping("/materialFlow") - public R materialFlow(@NotNull(message = "物料ID不能为空") @RequestParam Long itemId, + public R materialFlow(@RequestParam(required = false) Long itemId, + @RequestParam(required = false) String factory, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) { - return R.ok(stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime)); + if (itemId != null) { + return R.ok(stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime)); + } + if (StringUtils.isBlank(factory)) { + return R.fail("请选择物料或填写厂家"); + } + return R.ok(stockIoOrderService.queryMaterialFlowByFactory(factory, startTime, endTime)); } @Log(title = "物料出入库统计", businessType = BusinessType.EXPORT) @PostMapping("/materialFlow/export") - public void exportMaterialFlow(@NotNull(message = "物料ID不能为空") @RequestParam Long itemId, + public void exportMaterialFlow(@RequestParam(required = false) Long itemId, + @RequestParam(required = false) String factory, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, HttpServletResponse response) { - IGearStockIoOrderService.MaterialFlowResp resp = stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime); + IGearStockIoOrderService.MaterialFlowResp resp; + if (itemId != null) { + resp = stockIoOrderService.queryMaterialFlow(itemId, startTime, endTime); + } else if (StringUtils.isNotBlank(factory)) { + resp = stockIoOrderService.queryMaterialFlowByFactory(factory, startTime, endTime); + } else { + throw new ServiceException("请选择物料或填写厂家"); + } List exportRows = new ArrayList<>(); MaterialFlowExportRow summary = new MaterialFlowExportRow(); summary.setItemId(resp.getItemId()); + summary.setFactory(factory); summary.setStartTime(resp.getStartTime()); summary.setEndTime(resp.getEndTime()); summary.setAction("汇总"); @@ -85,6 +103,7 @@ public class GearStockIoOrderController extends BaseController { for (IGearStockIoOrderService.MaterialFlowRow r : resp.getRows()) { MaterialFlowExportRow row = new MaterialFlowExportRow(); row.setItemId(resp.getItemId()); + row.setFactory(factory); row.setTime(r.getTime()); row.setAction(r.getAction()); row.setOrderCode(r.getOrderCode()); @@ -105,6 +124,8 @@ public class GearStockIoOrderController extends BaseController { public static class MaterialFlowExportRow { @ExcelProperty(value = "物料ID") private Long itemId; + @ExcelProperty(value = "厂家") + private String factory; @ExcelProperty(value = "开始时间") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @@ -144,6 +165,14 @@ public class GearStockIoOrderController extends BaseController { this.itemId = itemId; } + public String getFactory() { + return factory; + } + + public void setFactory(String factory) { + this.factory = factory; + } + public Date getStartTime() { return startTime; } diff --git a/gear-oa/src/main/java/com/gear/oa/domain/GearOrderDetail.java b/gear-oa/src/main/java/com/gear/oa/domain/GearOrderDetail.java index 724232e..c20eb31 100644 --- a/gear-oa/src/main/java/com/gear/oa/domain/GearOrderDetail.java +++ b/gear-oa/src/main/java/com/gear/oa/domain/GearOrderDetail.java @@ -62,4 +62,8 @@ public class GearOrderDetail extends BaseEntity { */ private BigDecimal noTaxPrice; + private String spec; + + private String model; + } diff --git a/gear-oa/src/main/java/com/gear/oa/domain/GearShippingOrderDetail.java b/gear-oa/src/main/java/com/gear/oa/domain/GearShippingOrderDetail.java new file mode 100644 index 0000000..3c7ffb7 --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/domain/GearShippingOrderDetail.java @@ -0,0 +1,44 @@ +package com.gear.oa.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.gear.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("gear_shipping_order_detail") +public class GearShippingOrderDetail extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "detail_id") + private Long detailId; + + private Long shippingId; + + private Long productId; + + private String productCode; + + private String productName; + + private String spec; + + private String model; + + private BigDecimal quantity; + + private String unit; + + private String remark; + + private Integer sort; + + @TableLogic(value = "0", delval = "2") + private String delFlag; +} diff --git a/gear-oa/src/main/java/com/gear/oa/domain/bo/GearOrderDetailBo.java b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearOrderDetailBo.java index d5dad08..f7d14d7 100644 --- a/gear-oa/src/main/java/com/gear/oa/domain/bo/GearOrderDetailBo.java +++ b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearOrderDetailBo.java @@ -63,5 +63,9 @@ public class GearOrderDetailBo extends BaseEntity { */ private BigDecimal noTaxPrice; + private String spec; + + private String model; + } diff --git a/gear-oa/src/main/java/com/gear/oa/domain/bo/GearReceivableBo.java b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearReceivableBo.java index 4594d4d..ff41dd0 100644 --- a/gear-oa/src/main/java/com/gear/oa/domain/bo/GearReceivableBo.java +++ b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearReceivableBo.java @@ -40,6 +40,8 @@ public class GearReceivableBo extends BaseEntity { */ private Long orderId; + private Long salesmanId; + /** * 到期日 */ diff --git a/gear-oa/src/main/java/com/gear/oa/domain/bo/GearShippingOrderDetailBo.java b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearShippingOrderDetailBo.java new file mode 100644 index 0000000..92dfe0a --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/domain/bo/GearShippingOrderDetailBo.java @@ -0,0 +1,40 @@ +package com.gear.oa.domain.bo; + +import com.gear.common.core.domain.BaseEntity; +import com.gear.common.core.validate.AddGroup; +import com.gear.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GearShippingOrderDetailBo extends BaseEntity { + + private Long detailId; + + @NotNull(message = "发货单据ID不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long shippingId; + + @NotNull(message = "产品ID不能为空", groups = {AddGroup.class, EditGroup.class}) + private Long productId; + + private String productCode; + + private String productName; + + private String spec; + + private String model; + + @NotNull(message = "数量不能为空", groups = {AddGroup.class, EditGroup.class}) + private BigDecimal quantity; + + private String unit; + + private String remark; + + private Integer sort; +} diff --git a/gear-oa/src/main/java/com/gear/oa/domain/vo/GearShippingOrderDetailVo.java b/gear-oa/src/main/java/com/gear/oa/domain/vo/GearShippingOrderDetailVo.java new file mode 100644 index 0000000..c66580c --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/domain/vo/GearShippingOrderDetailVo.java @@ -0,0 +1,38 @@ +package com.gear.oa.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.gear.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@Data +@EqualsAndHashCode(callSuper = true) +@ExcelIgnoreUnannotated +public class GearShippingOrderDetailVo extends BaseEntity { + + private static final long serialVersionUID = 1L; + + private Long detailId; + + private Long shippingId; + + private Long productId; + + private String productCode; + + private String productName; + + private String spec; + + private String model; + + private BigDecimal quantity; + + private String unit; + + private String remark; + + private Integer sort; +} diff --git a/gear-oa/src/main/java/com/gear/oa/mapper/GearShippingOrderDetailMapper.java b/gear-oa/src/main/java/com/gear/oa/mapper/GearShippingOrderDetailMapper.java new file mode 100644 index 0000000..b823266 --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/mapper/GearShippingOrderDetailMapper.java @@ -0,0 +1,8 @@ +package com.gear.oa.mapper; + +import com.gear.common.core.mapper.BaseMapperPlus; +import com.gear.oa.domain.GearShippingOrderDetail; +import com.gear.oa.domain.vo.GearShippingOrderDetailVo; + +public interface GearShippingOrderDetailMapper extends BaseMapperPlus { +} diff --git a/gear-oa/src/main/java/com/gear/oa/mapper/MatMaterialSimpleMapper.java b/gear-oa/src/main/java/com/gear/oa/mapper/MatMaterialSimpleMapper.java index 89368b8..334e1fb 100644 --- a/gear-oa/src/main/java/com/gear/oa/mapper/MatMaterialSimpleMapper.java +++ b/gear-oa/src/main/java/com/gear/oa/mapper/MatMaterialSimpleMapper.java @@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.math.BigDecimal; +import java.util.List; import java.util.Map; public interface MatMaterialSimpleMapper { @@ -13,7 +14,9 @@ public interface MatMaterialSimpleMapper { "from mat_material where material_id = #{materialId} and del_flag = 0 limit 1") Map selectSnapshot(@Param("materialId") Long materialId); + @Select("select material_id from mat_material where del_flag = 0 and factory like concat('%', #{factory}, '%')") + List selectIdsByFactory(@Param("factory") String factory); + @Update("update mat_material set current_stock = ifnull(current_stock, 0) + #{delta} where material_id = #{materialId} and del_flag = 0") int updateStockDelta(@Param("materialId") Long materialId, @Param("delta") BigDecimal delta); } - diff --git a/gear-oa/src/main/java/com/gear/oa/service/IGearShippingOrderDetailService.java b/gear-oa/src/main/java/com/gear/oa/service/IGearShippingOrderDetailService.java new file mode 100644 index 0000000..56674b7 --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/service/IGearShippingOrderDetailService.java @@ -0,0 +1,24 @@ +package com.gear.oa.service; + +import com.gear.common.core.domain.PageQuery; +import com.gear.common.core.page.TableDataInfo; +import com.gear.oa.domain.bo.GearShippingOrderDetailBo; +import com.gear.oa.domain.vo.GearShippingOrderDetailVo; + +import java.util.Collection; +import java.util.List; + +public interface IGearShippingOrderDetailService { + + GearShippingOrderDetailVo queryById(Long detailId); + + TableDataInfo queryPageList(GearShippingOrderDetailBo bo, PageQuery pageQuery); + + List queryList(GearShippingOrderDetailBo bo); + + Boolean insertByBo(GearShippingOrderDetailBo bo); + + Boolean updateByBo(GearShippingOrderDetailBo bo); + + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/gear-oa/src/main/java/com/gear/oa/service/IGearStockIoOrderService.java b/gear-oa/src/main/java/com/gear/oa/service/IGearStockIoOrderService.java index ae52026..1c96329 100644 --- a/gear-oa/src/main/java/com/gear/oa/service/IGearStockIoOrderService.java +++ b/gear-oa/src/main/java/com/gear/oa/service/IGearStockIoOrderService.java @@ -35,6 +35,8 @@ public interface IGearStockIoOrderService { MaterialFlowResp queryMaterialFlow(Long itemId, Date startTime, Date endTime); + MaterialFlowResp queryMaterialFlowByFactory(String factory, Date startTime, Date endTime); + @lombok.Data class MaterialFlowResp { private Long itemId; diff --git a/gear-oa/src/main/java/com/gear/oa/service/impl/GearOrderServiceImpl.java b/gear-oa/src/main/java/com/gear/oa/service/impl/GearOrderServiceImpl.java index 9e13c93..512f9be 100644 --- a/gear-oa/src/main/java/com/gear/oa/service/impl/GearOrderServiceImpl.java +++ b/gear-oa/src/main/java/com/gear/oa/service/impl/GearOrderServiceImpl.java @@ -13,9 +13,7 @@ import org.springframework.stereotype.Service; import com.gear.oa.domain.bo.GearOrderBo; import com.gear.oa.domain.vo.GearOrderVo; import com.gear.oa.domain.GearOrder; -import com.gear.oa.domain.GearShippingOrder; import com.gear.oa.mapper.GearOrderMapper; -import com.gear.oa.mapper.GearShippingOrderMapper; import com.gear.oa.service.IGearOrderService; import java.util.List; @@ -33,51 +31,13 @@ import java.util.Collection; public class GearOrderServiceImpl implements IGearOrderService { private final GearOrderMapper baseMapper; - private final GearShippingOrderMapper shippingOrderMapper; /** * 查询订单主 */ @Override public GearOrderVo queryById(Long orderId){ - GearOrderVo vo = baseMapper.selectVoById(orderId); - if (vo == null) { - return null; - } - Integer derived = deriveOrderStatusByShipping(orderId, vo.getOrderStatus()); - if (derived != null && vo.getOrderStatus() != null && !derived.equals(vo.getOrderStatus())) { - baseMapper.update(null, Wrappers.lambdaUpdate() - .eq(GearOrder::getOrderId, orderId) - .ne(GearOrder::getOrderStatus, 3) - .in(GearOrder::getOrderStatus, 0, 1) - .set(GearOrder::getOrderStatus, derived)); - vo.setOrderStatus(derived); - } - return vo; - } - - private Integer deriveOrderStatusByShipping(Long orderId, Integer currentStatus) { - if (orderId == null) return currentStatus; - if (currentStatus != null && currentStatus == 3) return 3; - - QueryWrapper qw = new QueryWrapper<>(); - qw.select("MAX(CAST(status AS SIGNED))"); - qw.eq("order_id", orderId); - qw.eq("del_flag", "0"); - List objs = shippingOrderMapper.selectObjs(qw); - Integer maxStatus = null; - if (objs != null && !objs.isEmpty() && objs.get(0) != null) { - try { - maxStatus = Integer.parseInt(String.valueOf(objs.get(0))); - } catch (Exception ignored) { - maxStatus = null; - } - } - - int st = currentStatus == null ? 0 : currentStatus; - if (maxStatus != null && maxStatus >= 3 && (st == 0 || st == 1)) return 2; - if (maxStatus != null && maxStatus >= 2 && st == 0) return 1; - return currentStatus; + return baseMapper.selectVoById(orderId); } /** @@ -98,8 +58,6 @@ public class GearOrderServiceImpl implements IGearOrderService { lqw.eq(bo.getSalesmanId() != null, "o.salesman_id", bo.getSalesmanId()); lqw.like(StringUtils.isNotBlank(bo.getSalesManager()), "s.name", bo.getSalesManager()); lqw.eq(bo.getOrderStatus() != null, "o.order_status", bo.getOrderStatus()); - lqw.apply(bo.getOrderStatus() != null && bo.getOrderStatus() == 0, - "NOT EXISTS (SELECT 1 FROM gear_shipping_order so WHERE so.order_id = o.order_id AND so.del_flag = '0' AND CAST(so.status AS SIGNED) >= 2)"); lqw.eq(bo.getTradeType() != null, "o.trade_type", bo.getTradeType()); lqw.eq(bo.getTaxAmount() != null, "o.tax_amount", bo.getTaxAmount()); lqw.eq(bo.getNoTaxAmount() != null, "o.no_tax_amount", bo.getNoTaxAmount()); diff --git a/gear-oa/src/main/java/com/gear/oa/service/impl/GearReceivableServiceImpl.java b/gear-oa/src/main/java/com/gear/oa/service/impl/GearReceivableServiceImpl.java index 2a28606..ef71f3b 100644 --- a/gear-oa/src/main/java/com/gear/oa/service/impl/GearReceivableServiceImpl.java +++ b/gear-oa/src/main/java/com/gear/oa/service/impl/GearReceivableServiceImpl.java @@ -9,8 +9,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.gear.oa.domain.GearJournal; +import com.gear.oa.domain.GearOrder; import com.gear.oa.mapper.GearCustomerMapper; import com.gear.oa.mapper.GearJournalMapper; +import com.gear.oa.mapper.GearOrderMapper; import com.gear.oa.service.IGearJournalService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -24,9 +26,11 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.Date; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Collection; +import java.util.stream.Collectors; /** * 应收款管理(宽松版)Service业务层处理 @@ -39,6 +43,7 @@ import java.util.Collection; public class GearReceivableServiceImpl implements IGearReceivableService { private final GearReceivableMapper baseMapper; + private final GearOrderMapper orderMapper; @Resource private GearJournalMapper journalMapper; @Resource @@ -67,7 +72,16 @@ public class GearReceivableServiceImpl implements IGearReceivableService { QueryWrapper lqw = Wrappers.query(); lqw.eq("r.del_flag", 0); lqw.eq(bo.getCustomerId() != null, "r.customer_id", bo.getCustomerId()); - lqw.eq(bo.getOrderId() != null, "r.order_id", bo.getOrderId()); + if (bo.getOrderId() != null) { + lqw.eq("r.order_id", bo.getOrderId()); + } else if (bo.getSalesmanId() != null) { + List orderIds = queryOrderIdsBySalesman(bo.getSalesmanId()); + if (orderIds.isEmpty()) { + lqw.eq("r.receivable_id", -1L); + } else { + lqw.in("r.order_id", orderIds); + } + } lqw.eq(bo.getDueDate() != null, "r.due_date", bo.getDueDate()); lqw.eq(bo.getAmount() != null, "r.amount", bo.getAmount()); lqw.eq(bo.getPaidAmount() != null, "r.paid_amount", bo.getPaidAmount()); @@ -77,6 +91,19 @@ public class GearReceivableServiceImpl implements IGearReceivableService { lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, "r.create_time", bo.getStartTime(), bo.getEndTime()); return lqw; } + + private List queryOrderIdsBySalesman(Long salesmanId) { + if (salesmanId == null) return Collections.emptyList(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.select(GearOrder::getOrderId); + lqw.eq(GearOrder::getSalesmanId, salesmanId); + lqw.eq(GearOrder::getDelFlag, 0); + List list = orderMapper.selectList(lqw); + if (list == null || list.isEmpty()) { + return Collections.emptyList(); + } + return list.stream().map(GearOrder::getOrderId).filter(id -> id != null).distinct().collect(Collectors.toList()); + } /** * 查询应收款管理(宽松版)列表 */ diff --git a/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderDetailServiceImpl.java b/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderDetailServiceImpl.java new file mode 100644 index 0000000..cbf01ad --- /dev/null +++ b/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderDetailServiceImpl.java @@ -0,0 +1,94 @@ +package com.gear.oa.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.gear.common.core.domain.PageQuery; +import com.gear.common.core.page.TableDataInfo; +import com.gear.common.utils.StringUtils; +import com.gear.oa.domain.GearShippingOrderDetail; +import com.gear.oa.domain.bo.GearShippingOrderDetailBo; +import com.gear.oa.domain.vo.GearShippingOrderDetailVo; +import com.gear.oa.mapper.GearShippingOrderDetailMapper; +import com.gear.oa.service.IGearShippingOrderDetailService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; + +@RequiredArgsConstructor +@Service +public class GearShippingOrderDetailServiceImpl implements IGearShippingOrderDetailService { + + private final GearShippingOrderDetailMapper baseMapper; + + @Override + public GearShippingOrderDetailVo queryById(Long detailId) { + return baseMapper.selectVoById(detailId); + } + + @Override + public TableDataInfo queryPageList(GearShippingOrderDetailBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + @Override + public List queryList(GearShippingOrderDetailBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(GearShippingOrderDetailBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getDetailId() != null, GearShippingOrderDetail::getDetailId, bo.getDetailId()); + lqw.eq(bo.getShippingId() != null, GearShippingOrderDetail::getShippingId, bo.getShippingId()); + lqw.eq(bo.getProductId() != null, GearShippingOrderDetail::getProductId, bo.getProductId()); + lqw.like(StringUtils.isNotBlank(bo.getProductName()), GearShippingOrderDetail::getProductName, bo.getProductName()); + lqw.orderByAsc(GearShippingOrderDetail::getSort); + lqw.orderByAsc(GearShippingOrderDetail::getCreateTime); + return lqw; + } + + @Override + public Boolean insertByBo(GearShippingOrderDetailBo bo) { + GearShippingOrderDetail add = BeanUtil.toBean(bo, GearShippingOrderDetail.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setDetailId(add.getDetailId()); + } + return flag; + } + + @Override + public Boolean updateByBo(GearShippingOrderDetailBo bo) { + GearShippingOrderDetail update = BeanUtil.toBean(bo, GearShippingOrderDetail.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + private void validEntityBeforeSave(GearShippingOrderDetail entity) { + boolean isInsert = entity.getDetailId() == null; + if (entity.getDetailId() == null) { + entity.setDetailId(IdUtil.getSnowflakeNextId()); + } + if (isInsert) { + if (StringUtils.isBlank(entity.getDelFlag())) { + entity.setDelFlag("0"); + } + if (entity.getSort() == null) { + entity.setSort(0); + } + } + } + + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderServiceImpl.java b/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderServiceImpl.java index f6b6cae..9cb90b4 100644 --- a/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderServiceImpl.java +++ b/gear-oa/src/main/java/com/gear/oa/service/impl/GearShippingOrderServiceImpl.java @@ -112,7 +112,6 @@ public class GearShippingOrderServiceImpl implements IGearShippingOrderService { boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setShippingId(add.getShippingId()); - syncOrderStatusIfShipped(add); } return flag; } @@ -122,43 +121,9 @@ public class GearShippingOrderServiceImpl implements IGearShippingOrderService { GearShippingOrder update = BeanUtil.toBean(bo, GearShippingOrder.class); validEntityBeforeSave(update); boolean ok = baseMapper.updateById(update) > 0; - if (ok) { - syncOrderStatusIfShipped(update); - } return ok; } - private void syncOrderStatusIfShipped(GearShippingOrder entity) { - if (entity == null || entity.getOrderId() == null) return; - Integer statusNum = null; - try { - if (StringUtils.isNotBlank(entity.getStatus())) { - statusNum = Integer.parseInt(entity.getStatus()); - } - } catch (Exception ignored) { - statusNum = null; - } - if (statusNum == null) return; - // 发货单据状态与订单状态联动: - // - 发货单据 >=2(已发货):若订单仍为预订单(0),推进到进行中(1) - // - 发货单据 >=3(已完成):若订单为预订单(0)/进行中(1),推进到已完成(2) - // - 订单若已取消(3),不做推进 - if (statusNum >= 3) { - orderMapper.update(null, Wrappers.lambdaUpdate() - .eq(GearOrder::getOrderId, entity.getOrderId()) - .ne(GearOrder::getOrderStatus, 3) - .in(GearOrder::getOrderStatus, 0, 1) - .set(GearOrder::getOrderStatus, 2)); - return; - } - if (statusNum >= 2) { - orderMapper.update(null, Wrappers.lambdaUpdate() - .eq(GearOrder::getOrderId, entity.getOrderId()) - .eq(GearOrder::getOrderStatus, 0) - .set(GearOrder::getOrderStatus, 1)); - } - } - private void validEntityBeforeSave(GearShippingOrder entity) { boolean isInsert = entity.getShippingId() == null; // 发货单据ID:项目内大部分业务表使用雪花ID,这里保持一致 diff --git a/gear-oa/src/main/java/com/gear/oa/service/impl/GearStockIoOrderServiceImpl.java b/gear-oa/src/main/java/com/gear/oa/service/impl/GearStockIoOrderServiceImpl.java index 1e13555..5e3076f 100644 --- a/gear-oa/src/main/java/com/gear/oa/service/impl/GearStockIoOrderServiceImpl.java +++ b/gear-oa/src/main/java/com/gear/oa/service/impl/GearStockIoOrderServiceImpl.java @@ -362,6 +362,32 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService { if (itemId == null) { throw new ServiceException("物料ID不能为空"); } + return queryMaterialFlowByItemIds(java.util.Collections.singletonList(itemId), itemId, startTime, endTime); + } + + @Override + public MaterialFlowResp queryMaterialFlowByFactory(String factory, Date startTime, Date endTime) { + if (StringUtils.isBlank(factory)) { + throw new ServiceException("厂家不能为空"); + } + List ids = matMaterialMapper.selectIdsByFactory(factory); + if (ids == null || ids.isEmpty()) { + MaterialFlowResp resp = new MaterialFlowResp(); + resp.setItemId(null); + resp.setStartTime(startTime); + resp.setEndTime(endTime); + resp.setConfirmInQty(BigDecimal.ZERO); + resp.setOutQty(BigDecimal.ZERO); + resp.setRevokeInQty(BigDecimal.ZERO); + resp.setRevokeOutQty(BigDecimal.ZERO); + resp.setNetQty(BigDecimal.ZERO); + resp.setRows(new ArrayList<>()); + return resp; + } + return queryMaterialFlowByItemIds(ids, null, startTime, endTime); + } + + private MaterialFlowResp queryMaterialFlowByItemIds(List itemIds, Long singleItemId, Date startTime, Date endTime) { List confirmOrders = baseMapper.selectList(Wrappers.lambdaQuery() .eq(GearStockIoOrder::getIoType, "I") .eq(GearStockIoOrder::getConfirmInFlag, "1") @@ -400,7 +426,7 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService { List details = detailMapper.selectList(Wrappers.lambdaQuery() .in(GearStockIoOrderDetail::getOrderId, orderIds) .eq(GearStockIoOrderDetail::getItemType, "material") - .eq(GearStockIoOrderDetail::getItemId, itemId) + .in(GearStockIoOrderDetail::getItemId, itemIds) .eq(GearStockIoOrderDetail::getDelFlag, "0")); for (GearStockIoOrderDetail d : details) { if (d == null || d.getOrderId() == null) continue; @@ -468,7 +494,7 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService { BigDecimal net = confirmInQty.subtract(outQty).add(revokeOutQty).subtract(revokeInQty); MaterialFlowResp resp = new MaterialFlowResp(); - resp.setItemId(itemId); + resp.setItemId(singleItemId); resp.setStartTime(startTime); resp.setEndTime(endTime); resp.setConfirmInQty(confirmInQty); diff --git a/gear-oa/src/main/resources/mapper/oa/GearOrderDetailMapper.xml b/gear-oa/src/main/resources/mapper/oa/GearOrderDetailMapper.xml index 6caf6d6..a869328 100644 --- a/gear-oa/src/main/resources/mapper/oa/GearOrderDetailMapper.xml +++ b/gear-oa/src/main/resources/mapper/oa/GearOrderDetailMapper.xml @@ -18,6 +18,8 @@ + +