rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/SyncOrderController.java
@@ -3,6 +3,7 @@ import com.vincent.rsf.framework.common.R; import com.vincent.rsf.framework.exception.CoolException; import com.vincent.rsf.server.api.controller.erp.params.*; import com.vincent.rsf.server.api.entity.validator.SyncOrderValidator; import com.vincent.rsf.server.api.service.ReceiveMsgService; import com.vincent.rsf.server.common.annotation.OperationLog; import com.vincent.rsf.server.common.utils.ExcelUtil; @@ -164,8 +165,17 @@ if (Objects.isNull(orders) || orders.isEmpty()) { return R.error("参数不能为空!!"); } // return receiveMsgService.syncOrderUpdate(orders); try { // 业务验证 new SyncOrderValidator().validateBatchOrders(orders); // 处理业务 return receiveMsgService.syncCheckOrder(orders, getLoginUserId()); } catch (IllegalArgumentException e) { return R.error(e.getMessage()); } catch (Exception e) { return R.error(e.getMessage()); } } rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncOrderParams.java
@@ -1,14 +1,16 @@ package com.vincent.rsf.server.api.controller.erp.params; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.vincent.rsf.framework.common.Cools; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.experimental.Accessors; import org.springframework.format.annotation.DateTimeFormat; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.Valid; import javax.validation.constraints.*; import java.io.Serializable; import java.util.Date; import java.util.List; @@ -20,127 +22,274 @@ private static final long serialVersionUID = 1L; /* * 业务类型,待ERP补充,以下为示例: * 入库:收料通知单(PUR_ReceiveBill)、 * 采购入库单(STK_InStock)、 * 退料申请单(PUR_MRAPP)、 * 采购退料单(PUR_MRB)、 * 退货通知单(SAL_RETURNNOTICE)、 * 销售退货单(SAL_RETURNSTOCK)、 * 生产退料单(PRD_ReturnMtrl)、 * 生产入库单(PRD_INSTOCK)/ * 生产汇报单(PRD_MORPT)、 * 其他入库单(STK_MISCELLANEOUS) * 出库:发货通知单(SAL_DELIVERYNOTICE)、 * 销售出库单(SAL_OUTSTOCK)、 * 出库申请单(STK_OutStockApply)、 * 生产领料单(PRD_PickMtrl)、 * 生产补料单(PRD_FeedMtrl)、 * 其他出库单(STK_MisDelivery)调拨: * 调拨申请单(STK_TRANSFERAPPLY)、 * 直接调拨单(STK_TransferDirect) * */ /** * 业务类型枚举 */ public interface BusinessType { // 入库单据 String PUR_RECEIVE_BILL = "PUR_ReceiveBill"; // 收料通知单 String STK_IN_STOCK = "STK_InStock"; // 采购入库单 String PUR_MRAPP = "PUR_MRAPP"; // 退料申请单 String PUR_MRB = "PUR_MRB"; // 采购退料单 String SAL_RETURNNOTICE = "SAL_RETURNNOTICE"; // 退货通知单 String SAL_RETURNSTOCK = "SAL_RETURNSTOCK"; // 销售退货单 String PRD_RETURN_MTRL = "PRD_ReturnMtrl"; // 生产退料单 //接驳 String PRD_INSTOCK = "PRD_INSTOCK"; // 生产入库单 //接驳 String PRD_MORPT = "PRD_MORPT"; // 生产汇报单 String STK_MISCELLANEOUS = "STK_MISCELLANEOUS"; // 其他入库单 // 出库单据 String SAL_DELIVERYNOTICE = "SAL_DELIVERYNOTICE"; // 发货通知单 String SAL_OUTSTOCK = "SAL_OUTSTOCK"; // 销售出库单 String STK_OUTSTOCK_APPLY = "STK_OutStockApply"; // 出库申请单 String PRD_PICK_MTRL = "PRD_PickMtrl"; // 生产领料单 //接驳 String PRD_FEED_MTRL = "PRD_FeedMtrl"; // 生产补料单 //接驳 String STK_MIS_DELIVERY = "STK_MisDelivery"; // 其他出库单 // 调拨单据 String STK_TRANSFER_APPLY = "STK_TRANSFERAPPLY"; // 调拨申请单 String STK_TRANSFER_DIRECT = "STK_TransferDirect"; // 直接调拨单 } @NotNull(message = "业务类型不能为null") @NotEmpty(message = "业务类型不能为空") @ApiModelProperty("业务类型") /** * 单据类型枚举 */ public interface OrderType { String OUT_STOCK = "1"; // 出库单 String IN_STOCK = "2"; // 入库单 String TRANSFER = "3"; // 调拨单 } @NotBlank(message = "业务类型不能为空") @Pattern(regexp = "^(PUR_ReceiveBill|STK_InStock|PUR_MRAPP|PUR_MRB|SAL_RETURNNOTICE|SAL_RETURNSTOCK|" + "PRD_ReturnMtrl|PRD_INSTOCK|PRD_MORPT|STK_MISCELLANEOUS|SAL_DELIVERYNOTICE|SAL_OUTSTOCK|" + "STK_OutStockApply|PRD_PickMtrl|PRD_FeedMtrl|STK_MisDelivery|STK_TRANSFERAPPLY|STK_TransferDirect)$", message = "业务类型格式不正确") @ApiModelProperty(value = "业务类型", required = true, example = "STK_InStock") private String wkType; /* * 订单类型,1 出库单;2 入库单;3 调拨单; * */ @NotNull(message = "单据类型不能为null") @NotEmpty(message = "单据类型不能为空") @ApiModelProperty("单据类型") @NotBlank(message = "单据类型不能为空") @Pattern(regexp = "^[123]$", message = "单据类型只能是1(出库单)、2(入库单)或3(调拨单)") @ApiModelProperty(value = "单据类型: 1-出库单, 2-入库单, 3-调拨单", required = true, example = "2") private String type; @NotNull(message = "单号不能为null") @NotEmpty(message = "单号不能为空") @ApiModelProperty("单号") @NotBlank(message = "单号不能为空") @Size(max = 50, message = "单号长度不能超过50个字符") @ApiModelProperty(value = "单号", required = true, example = "PO202401010001") private String orderNo; @NotNull(message = "单据内码不能为null") @NotEmpty(message = "单据内码不能为空") @ApiModelProperty("单据内码,唯一标识") @NotNull(message = "单据内码不能为空") @Positive(message = "单据内码必须是正整数") @ApiModelProperty(value = "单据内码,唯一标识", required = true, example = "100001") private Long orderInternalCode; @NotNull(message = "订单ID不能为null") @NotEmpty(message = "订单ID不能为空") @ApiModelProperty("订单ID") // @NotNull(message = "订单ID不能为空") // @Positive(message = "订单ID必须是正整数") // @ApiModelProperty(value = "订单ID", required = true, example = "200001") private Long orderId; @NotNull(message = "数量不能为null") @NotEmpty(message = "数量不能为空") @ApiModelProperty("数量") // @NotNull(message = "数量不能为空") // @DecimalMin(value = "0.0", inclusive = false, message = "数量必须大于0") // @ApiModelProperty(value = "数量", required = true, example = "100.5") private Double anfme; @ApiModelProperty("客户编码") @ApiModelProperty(value = "客户编码", example = "CUST001") private String customerId; @ApiModelProperty("客户名称") @ApiModelProperty(value = "客户名称", example = "XX科技有限公司") private String customerName; @ApiModelProperty("供应商编码") @ApiModelProperty(value = "供应商编码", example = "SUP001") private String supplierId; @ApiModelProperty("供应商名称") @ApiModelProperty(value = "供应商名称", example = "XX供应商") private String supplierName; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @ApiModelProperty(value = "到达时间", example = "2024-01-01 10:00:00") private Date arrTime; @ApiModelProperty("创建日期,时间戳,精确到秒") @NotNull(message = "创建日期不能为空") @Positive(message = "创建日期必须是正整数") @ApiModelProperty(value = "创建日期,时间戳,精确到秒", required = true, example = "1704067200") private Long createTime; @ApiModelProperty("业务日期,对账使用,时间戳,精确到秒") @NotNull(message = "业务日期不能为空") @Positive(message = "业务日期必须是正整数") @ApiModelProperty(value = "业务日期,对账使用,时间戳,精确到秒", required = true, example = "1704067200") private Long businessTime; @ApiModelProperty("单据明细信息") @Valid @NotNull(message = "单据明细信息不能为空") @Size(min = 1, message = "至少需要一个明细项") @ApiModelProperty(value = "单据明细信息", required = true) private List<SyncOrdersItem> orderItems; @ApiModelProperty("收料/发货组织") @ApiModelProperty(value = "收料/发货组织", example = "ORG001") private String stockOrgId; @ApiModelProperty("收料/发货组织名称") @ApiModelProperty(value = "收料/发货组织名称", example = "发货部") private String stockOrgName; @ApiModelProperty("采购组织") @ApiModelProperty(value = "采购组织", example = "PUR001") private String purchaseOrgId; @ApiModelProperty("采购组织名称") @ApiModelProperty(value = "采购组织名称", example = "采购部") private String purchaseOrgName; @ApiModelProperty("采购员") @ApiModelProperty(value = "采购员", example = "USER001") private String purchaseUserId; @ApiModelProperty("采购员名称") @ApiModelProperty(value = "采购员名称", example = "张三") private String purchaseUserName; @ApiModelProperty("生产组织") @ApiModelProperty(value = "生产组织", example = "PRD001") private String prdOrgId; @ApiModelProperty("生产组织名称") @ApiModelProperty(value = "生产组织名称", example = "生产部") private String prdOrgName; @ApiModelProperty("销售组织") @ApiModelProperty(value = "销售组织", example = "SALE001") private String saleOrgId; @ApiModelProperty("销售组织名称") @ApiModelProperty(value = "销售组织名称", example = "销售部") private String saleOrgName; @ApiModelProperty("销售员") @ApiModelProperty(value = "销售员", example = "USER002") private String saleUserId; @ApiModelProperty("销售员名称") @ApiModelProperty(value = "销售员名称", example = "李四") private String saleUserName; @ApiModelProperty("库存方向") @ApiModelProperty(value = "库存方向", example = "IN") private String stockDirect; @ApiModelProperty("出入库接驳站点,出库时将物料出库后运输至该站点,入库时从该站点将物料运回库中") @ApiModelProperty(value = "出入库接驳站点,出库时将物料出库后运输至该站点,入库时从该站点将物料运回库中", example = "STATION001") private String stationId; /** * 获取实际到达时间 * 优先使用 arrTime,如果为空则从 createTime 转换 */ @JsonIgnore public Date getActualArrTime() { if (this.arrTime != null) { return this.arrTime; } if (this.createTime != null) { return new Date(this.createTime * 1000L); } return null; } /** * 获取实际业务时间 */ @JsonIgnore public Date getActualBusinessTime() { if (this.businessTime != null) { return new Date(this.businessTime * 1000L); } return null; } /** * 获取实际创建时间 */ @JsonIgnore public Date getActualCreateTime() { if (this.createTime != null) { return new Date(this.createTime * 1000L); } return null; } /** * 是否入库单 */ @JsonIgnore public boolean isInStockOrder() { return OrderType.IN_STOCK.equals(this.type); } /** * 是否出库单 */ @JsonIgnore public boolean isOutStockOrder() { return OrderType.OUT_STOCK.equals(this.type); } /** * 是否调拨单 */ @JsonIgnore public boolean isTransferOrder() { return OrderType.TRANSFER.equals(this.type); } /** * 业务验证 */ public void validateBusiness() { // if (isOutStockOrder() && Cools.isEmpty(customerId)) { // throw new IllegalArgumentException("出库单必须指定客户"); // } // // if (isInStockOrder() && Cools.isEmpty(supplierId)) { // throw new IllegalArgumentException("入库单必须指定供应商"); // } // if (Cools.isEmpty(stationId) && !isTransferOrder()) { // throw new IllegalArgumentException("必须指定接驳站点"); // } // 验证明细总数与主单数量是否一致 // if (orderItems != null && anfme != null) { // double itemsTotal = orderItems.stream() // .mapToDouble(item -> item.getAnfme() != null ? item.getAnfme() : 0.0) // .sum(); // // if (Math.abs(itemsTotal - anfme) > 0.001) { // throw new IllegalArgumentException(String.format( // "主单数量(%.3f)与明细总数(%.3f)不一致", anfme, itemsTotal)); // } // } } /** * 计算明细总数量 */ @JsonIgnore public Double calculateItemsTotal() { if (orderItems == null || orderItems.isEmpty()) { return 0.0; } return orderItems.stream() .mapToDouble(item -> item.getAnfme() != null ? item.getAnfme() : 0.0) .sum(); } /** * 获取单据摘要 */ @JsonIgnore public String getSummary() { return String.format("单据[%s] 类型[%s] 业务类型[%s] 单据内码[%s]", orderNo, type, wkType, orderInternalCode); } /** * 是否有效单据 */ @JsonIgnore public boolean isValid() { return !Cools.isEmpty(orderNo) && orderInternalCode != null && !Cools.isEmpty(orderInternalCode) && type != null && !Cools.isEmpty(type) && wkType != null && !Cools.isEmpty(wkType) && createTime != null && !Cools.isEmpty(createTime) && businessTime != null && !Cools.isEmpty(businessTime) && orderItems != null && !orderItems.isEmpty(); } } rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncOrdersItem.java
@@ -1,129 +1,327 @@ package com.vincent.rsf.server.api.controller.erp.params; import com.fasterxml.jackson.annotation.JsonIgnore; import com.vincent.rsf.framework.common.Cools; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.experimental.Accessors; import lombok.Data; import lombok.experimental.Accessors; import javax.validation.constraints.*; import java.io.Serializable; import java.math.BigDecimal; @Data @Accessors(chain = true) @ApiModel(value = "SyncOrdersParams", description = "同步盘点参数") @ApiModel(value = "SyncOrdersItem", description = "单据明细参数") public class SyncOrdersItem implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("盘点单明细ID") /** * 货主类型枚举 */ public interface OwnerType { String SUPPLIER = "supplier"; // 供应商 String CUSTOMER = "customer"; // 客户 String COMPANY = "company"; // 公司 String THIRD_PARTY = "third_party"; // 第三方 } @ApiModelProperty(value = "盘点单明细ID", example = "1001") private Long id; @ApiModelProperty("物料标识") @ApiModelProperty(value = "物料标识", example = "2001") private Long matnrId; @ApiModelProperty("物料编码") @NotBlank(message = "物料编码不能为空") @Size(max = 50, message = "物料编码长度不能超过50个字符") @ApiModelProperty(value = "物料编码", required = true, example = "MAT001") private String matnr; @ApiModelProperty("物料名称") @ApiModelProperty(value = "物料名称", example = "原材料A") private String maktx; @ApiModelProperty("客单号") @ApiModelProperty(value = "客单号", example = "CUST20240101001") private String platOrderCode; @ApiModelProperty("平台标识(行号)") @NotBlank(message = "平台标识(行号)不能为空") @ApiModelProperty(value = "平台标识(行号)", required = true, example = "10") private String platItemId; @ApiModelProperty("工单号") @ApiModelProperty(value = "工单号", example = "WO20240101001") private String platWorkCode; @ApiModelProperty("项目号") @ApiModelProperty(value = "项目号", example = "PROJ001") private String projectCode; @ApiModelProperty("字段索引") @ApiModelProperty(value = "字段索引", example = "ext_001") private String fieldsIndex; @ApiModelProperty("规格") @ApiModelProperty(value = "规格", example = "10 * 20 * 30") private String spec; @ApiModelProperty("型号") @ApiModelProperty(value = "型号", example = "MODEL-A") private String model; @ApiModelProperty("数量") @NotNull(message = "数量不能为空") @DecimalMin(value = "0.0", inclusive = false, message = "数量必须大于0") @Digits(integer = 10, fraction = 3, message = "数量整数位不超过10位,小数位不超过3位") @ApiModelProperty(value = "数量", required = true, example = "100.5") private Double anfme; @ApiModelProperty("库存单位") @ApiModelProperty(value = "库存单位", example = "个") private String unit; @ApiModelProperty("库存批次") @NotBlank(message = "库存批次不能为空") @Size(max = 50, message = "库存批次长度不能超过50个字符") @ApiModelProperty(value = "库存批次", required = true, example = "BATCH20240101") private String batch; @ApiModelProperty("已收数量") @DecimalMin(value = "0.0", message = "已收数量不能为负数") @ApiModelProperty(value = "已收数量", example = "50.0") private Double qty; @ApiModelProperty("条形码") @ApiModelProperty(value = "条形码", example = "6936983800013") private String barcode; @ApiModelProperty("现金票号") @ApiModelProperty(value = "现金票号", example = "CASH001") private String crushNo; @ApiModelProperty("计划跟踪号") @NotBlank(message = "计划跟踪号不能为空") @Size(max = 50, message = "计划跟踪号长度不能超过50个字符") @ApiModelProperty(value = "计划跟踪号", required = true, example = "PLAN20240101") private String planNo; @ApiModelProperty("行内码,唯一标识") @NotBlank(message = "行内码不能为空") @Size(max = 50, message = "行内码长度不能超过50个字符") @ApiModelProperty(value = "行内码,唯一标识", required = true, example = "LINE001") private String lineId; @ApiModelProperty("物料编码") @ApiModelProperty(value = "物料编码", example = "MAT001") private String matNr; @ApiModelProperty("物料名称") @ApiModelProperty(value = "物料名称", example = "原材料A") private String makTx; @ApiModelProperty("规格") @ApiModelProperty(value = "规格", example = "10 * 20 * 30") private String specs; @ApiModelProperty("基本单位") @ApiModelProperty(value = "基本单位", example = "PCS") private String baseUnitId; @ApiModelProperty("托盘码,半成品/成品入库ERP需要传,非则ERP不需要传") @ApiModelProperty(value = "托盘码,半成品/成品入库ERP需要传,非则ERP不需要传", example = "PALLET001") private String palletId; @ApiModelProperty("计价单位") @ApiModelProperty(value = "计价单位", example = "BOX") private String priceUnitId; @ApiModelProperty("建议目标仓库") @ApiModelProperty(value = "建议目标仓库", example = "WH001") private String targetWareHouseId; @ApiModelProperty("调出仓") @ApiModelProperty(value = "调出仓", example = "WH002") private String sourceWareHouseId; @ApiModelProperty("入库类型") @ApiModelProperty(value = "入库类型", example = "purchase") private String inStockType; @ApiModelProperty("货主类型") @NotBlank(message = "货主类型不能为空") @Pattern(regexp = "^(supplier|customer|company|third_party)$", message = "货主类型必须是supplier、customer、company或third_party") @ApiModelProperty(value = "货主类型", required = true, example = "company") private String ownerTypeId; @ApiModelProperty("货主") @NotBlank(message = "货主不能为空") @Size(max = 50, message = "货主长度不能超过50个字符") @ApiModelProperty(value = "货主", required = true, example = "OWNER001") private String ownerId; @ApiModelProperty("货主名称") @NotBlank(message = "货主名称不能为空") @Size(max = 100, message = "货主名称长度不能超过100个字符") @ApiModelProperty(value = "货主名称", required = true, example = "XX有限公司") private String ownerName; @ApiModelProperty("保管者类型") @ApiModelProperty(value = "保管者类型", example = "warehouse") private String keeperTypeId; @ApiModelProperty("保管者") @ApiModelProperty(value = "保管者", example = "KEEPER001") private String keeperId; @ApiModelProperty("保管者名称") @ApiModelProperty(value = "保管者名称", example = "仓库管理部") private String keeperName; /** * 获取实际的物料编码 * 优先使用 matnr,如果为空则使用 matNr */ @JsonIgnore public String getActualMatnr() { return !Cools.isEmpty(matnr) ? matnr : matNr; } /** * 获取实际的物料名称 * 优先使用 maktx,如果为空则使用 makTx */ @JsonIgnore public String getActualMaktx() { return !Cools.isEmpty(maktx) ? maktx : makTx; } /** * 获取实际的规格 * 优先使用 spec,如果为空则使用 specs */ @JsonIgnore public String getActualSpec() { return !Cools.isEmpty(spec) ? spec : specs; } /** * 设置物料编码(保持两个字段同步) */ public void setMatnr(String matnr) { this.matnr = matnr; this.matNr = matnr; } public void setMatNr(String matNr) { this.matNr = matNr; this.matnr = matNr; } /** * 设置物料名称(保持两个字段同步) */ public void setMaktx(String maktx) { this.maktx = maktx; this.makTx = maktx; } public void setMakTx(String makTx) { this.makTx = makTx; this.maktx = makTx; } /** * 设置规格(保持两个字段同步) */ public void setSpec(String spec) { this.spec = spec; this.specs = spec; } public void setSpecs(String specs) { this.specs = specs; this.spec = specs; } /** * 转换为BigDecimal(精度计算使用) */ @JsonIgnore public BigDecimal getAnfmeAsBigDecimal() { return anfme != null ? BigDecimal.valueOf(anfme) : BigDecimal.ZERO; } @JsonIgnore public BigDecimal getQtyAsBigDecimal() { return qty != null ? BigDecimal.valueOf(qty) : BigDecimal.ZERO; } /** * 获取剩余数量 */ @JsonIgnore public Double getRemainingQty() { if (anfme == null) { return 0.0; } double currentQty = qty != null ? qty : 0.0; double remaining = anfme - currentQty; return Math.max(0, remaining); } /** * 是否已完成 */ @JsonIgnore public boolean isCompleted() { if (anfme == null) { return false; } double currentQty = qty != null ? qty : 0.0; return currentQty >= anfme; } /** * 获取完成百分比 */ @JsonIgnore public int getCompletionPercentage() { if (anfme == null || anfme == 0) { return 0; } double currentQty = qty != null ? qty : 0.0; int percentage = (int) ((currentQty / anfme) * 100); return Math.min(Math.max(percentage, 0), 100); } /** * 业务验证 */ public void validateBusiness(String orderType) { if ("purchase".equals(inStockType) && Cools.isEmpty(palletId)) { throw new IllegalArgumentException("采购入库明细必须提供托盘码"); } if ("production".equals(inStockType) && Cools.isEmpty(palletId)) { throw new IllegalArgumentException("生产入库明细必须提供托盘码"); } if (!Cools.isEmpty(targetWareHouseId) && !Cools.isEmpty(sourceWareHouseId)) { if (targetWareHouseId.equals(sourceWareHouseId)) { throw new IllegalArgumentException("目标仓库和调出仓库不能相同"); } } if (qty != null && qty > anfme) { throw new IllegalArgumentException("已收数量不能大于计划数量"); } } /** * 是否需要托盘码 */ @JsonIgnore public boolean requiresPallet() { return "purchase".equals(inStockType) || "production".equals(inStockType) || "return".equals(inStockType); } /** * 是否调拨明细 */ @JsonIgnore public boolean isTransferItem() { return !Cools.isEmpty(sourceWareHouseId) && !Cools.isEmpty(targetWareHouseId); } /** * 获取物料唯一标识 */ @JsonIgnore public String getMaterialKey() { return String.format("%s_%s_%s", getActualMatnr(), batch, getActualSpec()); } /** * 获取明细摘要 */ @JsonIgnore public String getSummary() { return String.format("物料[%s] 批次[%s] 数量[%s/%s]", getActualMatnr(), batch, qty, anfme); } } rsf-server/src/main/java/com/vincent/rsf/server/api/entity/validator/SyncOrderValidator.java
New file @@ -0,0 +1,152 @@ package com.vincent.rsf.server.api.entity.validator; import com.vincent.rsf.framework.common.Cools; import com.vincent.rsf.server.api.controller.erp.params.SyncOrderParams; import com.vincent.rsf.server.api.controller.erp.params.SyncOrdersItem; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Component @Slf4j public class SyncOrderValidator { /** * 验证同步订单参数 */ public void validateSyncOrder(SyncOrderParams order) { if (order == null) { throw new IllegalArgumentException("订单参数不能为空"); } // 基本验证 if (!order.isValid()) { throw new IllegalArgumentException("订单参数不完整"); } // 业务验证 order.validateBusiness(); // 验证明细 if (order.getOrderItems() == null || order.getOrderItems().isEmpty()) { throw new IllegalArgumentException("订单明细不能为空"); } // 检查重复行号 Set<String> lineIds = new HashSet<>(); for (int i = 0; i < order.getOrderItems().size(); i++) { SyncOrdersItem item = order.getOrderItems().get(i); String prefix = "第" + (i + 1) + "个明细: "; if (item == null) { throw new IllegalArgumentException(prefix + "明细为空"); } // 验证行内码 if (Cools.isEmpty(item.getLineId())) { throw new IllegalArgumentException(prefix + "行内码不能为空"); } // 检查重复 if (!lineIds.add(item.getLineId())) { throw new IllegalArgumentException(prefix + "行内码重复: " + item.getLineId()); } // 业务验证 try { item.validateBusiness(order.getType()); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(prefix + e.getMessage()); } } // // 验证数量一致性 // double itemsTotal = order.calculateItemsTotal(); // if (Math.abs(itemsTotal - order.getAnfme()) > 0.001) { // log.warn("订单[{}] 主单数量({})与明细总数({})不一致", // order.getOrderNo(), order.getAnfme(), itemsTotal); // } } /** * 验证批次订单 */ public void validateBatchOrders(List<SyncOrderParams> orders) { if (orders == null || orders.isEmpty()) { throw new IllegalArgumentException("订单列表不能为空"); } if (orders.size() > 100) { throw new IllegalArgumentException("批量同步单次最多支持100个订单"); } // 检查重复订单号 Set<String> orderNos = new HashSet<>(); Set<Long> orderInternalCodes = new HashSet<>(); for (int i = 0; i < orders.size(); i++) { SyncOrderParams order = orders.get(i); String prefix = "第" + (i + 1) + "个订单: "; if (order == null) { throw new IllegalArgumentException(prefix + "订单为空"); } // 验证订单号 if (Cools.isEmpty(order.getOrderNo())) { throw new IllegalArgumentException(prefix + "订单号不能为空"); } if (!orderNos.add(order.getOrderNo())) { throw new IllegalArgumentException(prefix + "订单号重复: " + order.getOrderNo()); } // 验证单据内码 if (order.getOrderInternalCode() == null) { throw new IllegalArgumentException(prefix + "单据内码不能为空"); } if (!orderInternalCodes.add(order.getOrderInternalCode())) { throw new IllegalArgumentException(prefix + "单据内码重复: " + order.getOrderInternalCode()); } // 验证单个订单 try { validateSyncOrder(order); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(prefix + e.getMessage()); } } } /** * 过滤有效订单 */ public List<SyncOrderParams> filterValidOrders(List<SyncOrderParams> orders) { if (orders == null) { return Collections.emptyList(); } return orders.stream() .filter(order -> order != null && order.isValid()) .collect(Collectors.toList()); } /** * 计算总数量 */ // public double calculateTotalQuantity(List<SyncOrderParams> orders) { // if (orders == null || orders.isEmpty()) { // return 0.0; // } // // return orders.stream() // .mapToDouble(order -> order.getAnfme() != null ? order.getAnfme() : 0.0) // .sum(); // } } rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
@@ -14,6 +14,7 @@ import com.vincent.rsf.server.api.controller.erp.params.dto.CheckDiffDto; import com.vincent.rsf.server.api.controller.erp.params.dto.TransferInfoDto; import com.vincent.rsf.server.api.controller.erp.params.dto.WkOrderDto; import com.vincent.rsf.server.api.utils.TimeConverterUtils; import com.vincent.rsf.server.common.domain.BaseParam; import com.vincent.rsf.server.common.domain.PageParam; import com.vincent.rsf.server.manager.controller.dto.LocStockDto; @@ -448,13 +449,14 @@ .setWkType(one.getValue()) .setAnfme(syncOrder.getAnfme()) .setPoCode(syncOrder.getOrderNo()) .setWorkQty(0.0) .setQty(0.0) .setWorkQty(0.0)//执行数量 .setQty(0.0)//完成数量 .setPoId(syncOrder.getOrderInternalCode()) .setCode(ruleCode) .setArrTime(syncOrder.getArrTime()) .setId(null) .setCreateTime(new Date()) .setCreateTime(new TimeConverterUtils().timestampToDate(syncOrder.getCreateTime())) .setBusinessTime(new TimeConverterUtils().timestampToDate(syncOrder.getBusinessTime())) .setUpdateTime(new Date()) .setCreateBy(loginUserId) .setUpdateBy(loginUserId);