1
1 天以前 a422cb5b73799050827251f835ebc53d5757a96b
rsf-server/src/main/java/com/vincent/rsf/server/api/controller/erp/params/SyncOrdersItem.java
@@ -1,66 +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("物料编码")
    private String matnrCode;
    @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(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);
    }
}