cl
2 天以前 b47369fbc73269f0661ba169c6387e04fb037e87
src/main/java/com/zy/asrs/controller/SaleOrderController.java
@@ -7,7 +7,9 @@
import com.baomidou.mybatisplus.plugins.Page;
import com.core.common.DateUtils;
import com.zy.asrs.entity.SaleOrder;
import com.zy.asrs.entity.WaitPakin;
import com.zy.asrs.service.SaleOrderService;
import com.zy.asrs.service.WaitPakinService;
import com.core.annotations.ManagerAuth;
import com.core.common.BaseRes;
import com.core.common.Cools;
@@ -16,7 +18,10 @@
import com.zy.common.web.BaseController;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -31,8 +36,12 @@
@RestController
public class SaleOrderController extends BaseController {
    private static final Logger logger = LoggerFactory.getLogger(SaleOrderController.class);
    @Autowired
    private SaleOrderService saleOrderService;
    @Autowired
    private WaitPakinService waitPakinService;
    @RequestMapping(value = "/saleOrder/{id}/auth")
    @ManagerAuth
@@ -234,6 +243,7 @@
        }
        int successCount = 0;
        int updateCount = 0;
        int skipCount = 0;
        for (Map<String, Object> data : dataList) {
            try {
                // 检查是否已存在(根据订单号和物料编码)
@@ -242,11 +252,39 @@
                
                SaleOrder saleOrder;
                boolean isUpdate = false;
                boolean hasQtyChange = false;
                if (existOrder != null) {
                    // 已存在,更新数据
                    saleOrder = existOrder;
                    isUpdate = true;
                    updateCount++;
                    // 已存在,检查数量字段是否有变化
                    Double newOrderQty = parseDoubleSafely(data.get("orderQty"));
                    Double newProductQty = parseDoubleSafely(data.get("productQty"));
                    Double newInQty = parseDoubleSafely(data.get("inQty"));
                    Double newOutQty = parseDoubleSafely(data.get("outQty"));
                    Double newOutAmount = parseDoubleSafely(data.get("outAmount"));
                    Double newOutPrice = parseDoubleSafely(data.get("outPrice"));
                    Double newIcsbeqty = parseDoubleSafely(data.get("icsbeqty"));
                    Double newSeoseqty = parseDoubleSafely(data.get("seoseqty"));
                    // 比较数量字段是否有变化
                    hasQtyChange = !isDoubleEqual(existOrder.getOrderQty(), newOrderQty)
                            || !isDoubleEqual(existOrder.getProductQty(), newProductQty)
                            || !isDoubleEqual(existOrder.getInQty(), newInQty)
                            || !isDoubleEqual(existOrder.getOutQty(), newOutQty)
                            || !isDoubleEqual(existOrder.getOutAmount(), newOutAmount)
                            || !isDoubleEqual(existOrder.getOutPrice(), newOutPrice)
                            || !isDoubleEqual(existOrder.getIcsbeqty(), newIcsbeqty)
                            || !isDoubleEqual(existOrder.getSeoseqty(), newSeoseqty);
                    // 只有当数量有变化时才更新
                    if (hasQtyChange) {
                        saleOrder = existOrder;
                        isUpdate = true;
                        updateCount++;
                    } else {
                        // 数量没有变化,跳过更新
                        skipCount++;
                        continue;
                    }
                } else {
                    // 不存在,创建新记录
                    saleOrder = new SaleOrder();
@@ -285,6 +323,8 @@
                // 保存或更新
                if (isUpdate) {
                    saleOrderService.updateById(saleOrder);
                    // 销售订单更新后,顺带更新 xtyasrs 库 cust_wait_pakin 的数量(mobile/bill/query 的 count 取自 anfme),仅更新、不插入,失败不影响主流程
                    updateWaitPakinQuantity(data);
                } else {
                    saleOrderService.insert(saleOrder);
                }
@@ -293,7 +333,90 @@
                e.printStackTrace();
            }
        }
        return R.ok("成功保存 " + successCount + " 条新数据,更新 " + updateCount + " 条已存在数据");
        String message = "成功保存 " + successCount + " 条新数据,更新 " + updateCount + " 条已存在数据";
        if (skipCount > 0) {
            message += ",跳过 " + skipCount + " 条数量未变化的数据";
        }
        return R.ok(message);
    }
    /**
     * 比较两个Double值是否相等(处理null值和0值)
     * 重要:null和0被认为是不同的值,需要更新
     * @param d1 第一个Double值
     * @param d2 第二个Double值
     * @return true-相等,false-不相等
     */
    private boolean isDoubleEqual(Double d1, Double d2) {
        // 两个都是null,认为相等
        if (d1 == null && d2 == null) {
            return true;
        }
        // 一个为null,另一个不为null,认为不相等(需要更新)
        // 注意:null和0是不同的,null和0.0会返回false,触发更新
        if (d1 == null || d2 == null) {
            return false;
        }
        // 两个都不为null,使用误差范围比较,避免浮点数精度问题
        // 注意:0值会被正确比较,例如:0.0和0.0会返回true,100.0和0.0会返回false
        return Math.abs(d1 - d2) < 0.0001;
    }
    /** 死锁重试次数 */
    private static final int CUST_WAIT_PAKIN_DEADLOCK_RETRIES = 2;
    /** 死锁重试间隔(毫秒) */
    private static final int CUST_WAIT_PAKIN_RETRY_DELAY_MS = 80;
    /**
     * 销售订单数量更新后,顺带更新 cust_wait_pakin 的 anfme/nqty(mobile/bill/query 的 count 取自 anfme)。
     * 仅按 matnr+mnemonic 更新,不插入;无匹配或异常不影响主流程。
     * 发生死锁时自动重试,减少并发更新导致的死锁牺牲。
     */
    private void updateWaitPakinQuantity(Map<String, Object> data) {
        String invCode = data.get("invCode") != null ? String.valueOf(data.get("invCode")).trim() : null;
        String orderCode = data.get("orderCode") != null ? String.valueOf(data.get("orderCode")).trim() : null;
        if (Cools.isEmpty(invCode) || Cools.isEmpty(orderCode)) {
            logger.debug("cust_wait_pakin 跳过更新:订单号或物料编码为空,orderCode={},invCode={}", orderCode, invCode);
            return;
        }
        Double productQty = parseDoubleSafely(data.get("productQty"));
        if (productQty == null) {
            productQty = parseDoubleSafely(data.get("orderQty"));
        }
        logger.info("cust_wait_pakin 开始更新:订单号={},物料编码={},数量={}", orderCode, invCode, productQty);
        String invName = data.get("invName") != null ? String.valueOf(data.get("invName")) : null;
        WaitPakin updateEntity = new WaitPakin();
        updateEntity.setMaktx(invName);
        updateEntity.setAnfme(productQty);
        updateEntity.setNqty(productQty);
        updateEntity.setModiTime(new Date());
        Wrapper<WaitPakin> wrapper = new EntityWrapper<WaitPakin>().eq("matnr", invCode).eq("mnemonic", orderCode);
        for (int attempt = 0; attempt <= CUST_WAIT_PAKIN_DEADLOCK_RETRIES; attempt++) {
            try {
                boolean updated = waitPakinService.update(updateEntity, wrapper);
                if (updated) {
                    logger.info("cust_wait_pakin 更新成功:订单号={},物料编码={},数量={}", orderCode, invCode, productQty);
                } else {
                    logger.warn("cust_wait_pakin 更新未影响行(可能无匹配记录):订单号={},物料编码={}", orderCode, invCode);
                }
                return;
            } catch (Exception e) {
                if (e instanceof DeadlockLoserDataAccessException || (e.getCause() != null && e.getCause().getClass().getSimpleName().contains("Deadlock"))) {
                    if (attempt < CUST_WAIT_PAKIN_DEADLOCK_RETRIES) {
                        try {
                            Thread.sleep(CUST_WAIT_PAKIN_RETRY_DELAY_MS);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                        }
                        logger.warn("cust_wait_pakin 更新死锁,第{}次重试:订单号={},物料编码={}", attempt + 1, orderCode, invCode);
                        continue;
                    }
                }
                logger.error("cust_wait_pakin 更新异常:orderCode={},invCode={},error={}", data.get("orderCode"), data.get("invCode"), e.getMessage(), e);
                return;
            }
        }
    }
    @RequestMapping(value = "/saleOrder/update/auth")
@@ -496,11 +619,12 @@
            // 表头
            String[] headers = { "生产单号", "业务员", "图号", "物料名称", "规格", "订单数量", "交货日期",
                    "任务单数量", "成品数量", "出货通知数", "实出数量", "对账数量", "对账单价", "对账金额",
                    "任务单数量", "成品数量", "出货通知数", /* "实出数量", */ "对账数量", "对账单价", "对账金额",
                    "包材版费", "退税资料", "开票", "内陆费", "收款", "应收款余额",
                    "对账数量残余", "对账金额残余", "任务单残余", "实出数量残余", "收款残余" };
            // 实出数量字段已隐藏,isCalc数组需要相应调整(移除一个false)
            boolean[] isCalc = { false, false, false, false, false, false, false,
                    false, false, false, false, false, false, false,
                    false, false, false, /* false, */ false, false, false,
                    false, false, false, false, false, false,
                    true, true, true, true, true };
@@ -528,7 +652,7 @@
                createNumCell(row, col++, order.getProductQty(), dataStyle);
                createNumCell(row, col++, order.getInQty(), dataStyle);
                createNumCell(row, col++, order.getIcsbeqty(), dataStyle);
                createNumCell(row, col++, order.getSeoseqty(), dataStyle);
                // createNumCell(row, col++, order.getSeoseqty(), dataStyle);
                createNumCell(row, col++, order.getOutQty(), dataStyle);
                createNumCell(row, col++, order.getOutPrice(), dataStyle);
                createNumCell(row, col++, order.getOutAmount(), dataStyle);