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.Data; import lombok.experimental.Accessors; import javax.validation.constraints.*; import java.io.Serializable; import java.math.BigDecimal; @Data @Accessors(chain = true) @ApiModel(value = "SyncOrdersItem", description = "单据明细参数") public class SyncOrdersItem implements Serializable { private static final long serialVersionUID = 1L; /** * 货主类型枚举 */ 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(value = "物料标识", example = "2001") private Long matnrId; @NotBlank(message = "物料编码不能为空") @Size(max = 50, message = "物料编码长度不能超过50个字符") @ApiModelProperty(value = "物料编码", required = true, example = "MAT001") private String matnr; @ApiModelProperty(value = "物料名称", example = "原材料A") private String maktx; @ApiModelProperty(value = "客单号", example = "CUST20240101001") private String platOrderCode; @NotBlank(message = "平台标识(行号)不能为空") @ApiModelProperty(value = "平台标识(行号)", required = true, example = "10") private String platItemId; @ApiModelProperty(value = "工单号", example = "WO20240101001") private String platWorkCode; @ApiModelProperty(value = "项目号", example = "PROJ001") private String projectCode; @ApiModelProperty(value = "字段索引", example = "ext_001") private String fieldsIndex; @ApiModelProperty(value = "规格", example = "10 * 20 * 30") private String spec; @ApiModelProperty(value = "型号", example = "MODEL-A") private String model; @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(value = "库存单位", example = "个") private String unit; @NotBlank(message = "库存批次不能为空") @Size(max = 50, message = "库存批次长度不能超过50个字符") @ApiModelProperty(value = "库存批次", required = true, example = "BATCH20240101") private String batch; @DecimalMin(value = "0.0", message = "已收数量不能为负数") @ApiModelProperty(value = "已收数量", example = "50.0") private Double qty; @ApiModelProperty(value = "条形码", example = "6936983800013") private String barcode; @ApiModelProperty(value = "现金票号", example = "CASH001") private String crushNo; @NotBlank(message = "计划跟踪号不能为空") @Size(max = 50, message = "计划跟踪号长度不能超过50个字符") @ApiModelProperty(value = "计划跟踪号", required = true, example = "PLAN20240101") private String planNo; @NotBlank(message = "行内码不能为空") @Size(max = 50, message = "行内码长度不能超过50个字符") @ApiModelProperty(value = "行内码,唯一标识", required = true, example = "LINE001") private String lineId; @ApiModelProperty(value = "物料编码", example = "MAT001") private String matNr; @ApiModelProperty(value = "物料名称", example = "原材料A") private String makTx; @ApiModelProperty(value = "规格", example = "10 * 20 * 30") private String specs; @ApiModelProperty(value = "基本单位", example = "PCS") private String baseUnitId; @ApiModelProperty(value = "托盘码,半成品/成品入库ERP需要传,非则ERP不需要传", example = "PALLET001") private String palletId; @ApiModelProperty(value = "计价单位", example = "BOX") private String priceUnitId; @ApiModelProperty(value = "建议目标仓库", example = "WH001") private String targetWareHouseId; @ApiModelProperty(value = "调出仓", example = "WH002") private String sourceWareHouseId; @ApiModelProperty(value = "入库类型", example = "purchase") private String inStockType; @NotBlank(message = "货主类型不能为空") @Pattern(regexp = "^(supplier|customer|company|third_party)$", message = "货主类型必须是supplier、customer、company或third_party") @ApiModelProperty(value = "货主类型", required = true, example = "company") private String ownerTypeId; @NotBlank(message = "货主不能为空") @Size(max = 50, message = "货主长度不能超过50个字符") @ApiModelProperty(value = "货主", required = true, example = "OWNER001") private String ownerId; @NotBlank(message = "货主名称不能为空") @Size(max = 100, message = "货主名称长度不能超过100个字符") @ApiModelProperty(value = "货主名称", required = true, example = "XX有限公司") private String ownerName; @ApiModelProperty(value = "保管者类型", example = "warehouse") private String keeperTypeId; @ApiModelProperty(value = "保管者", example = "KEEPER001") private String keeperId; @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); } }