package com.ruoyi.asset.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.constant.ReceiveOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsReceiveOrder;
import com.ruoyi.asset.domain.AmsReceiveOrderItem;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsReceiveOrderMapper;
import com.ruoyi.asset.service.IAmsReceiveOrderService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysCodeRuleService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 资产领用管理服务实现层
 * 
 * 核心职责是处理资产从“在库（IN_STOCK）”状态到“在用（IN_USE）”状态的生命周期流转。
 * 业务设计亮点：
 * 1. 【防死锁机制】：在批量对资产记录加锁前，采用按资产ID升序排列的策略，防范多单据并发竞争资产资源导致死锁。
 * 2. 【脏快照防范】：在提交和最终确认环节，强制核对物理仓位历史快照与实时仓位，防范在草稿存续期内仓位被其他调拨流程改变。
 * 3. 【状态最终一致】：在 SQL 层面限制操作前置状态，形成强力安全防护线。
 *
 * @author Yangk
 */
@Service
public class AmsReceiveOrderServiceImpl implements IAmsReceiveOrderService
{
    /** 领用单编号编码规则标识 */
    private static final String RECEIVE_ORDER_RULE = "RECEIVE_ORDER";

    /** 逻辑删除/启用通用常量值 */
    private static final String DEL_FLAG_NORMAL = "0";

    @Autowired
    private AmsReceiveOrderMapper amsReceiveOrderMapper;

    @Autowired
    private AmsAssetMapper amsAssetMapper;

    @Autowired
    private ISysCodeRuleService sysCodeRuleService;

    @Autowired
    private ISysDeptService sysDeptService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private IAssetStatusTransitionService assetStatusTransitionService;

    @Override
    public AmsReceiveOrder selectAmsReceiveOrderByOrderId(Long orderId)
    {
        return amsReceiveOrderMapper.selectAmsReceiveOrderByOrderId(orderId);
    }

    @Override
    public List<AmsReceiveOrder> selectAmsReceiveOrderList(AmsReceiveOrder order)
    {
        return amsReceiveOrderMapper.selectAmsReceiveOrderList(order);
    }

    /**
     * 过滤并加载当前可用于领用的资产。
     * 业务意图：排除已被其他草稿或待确认领用单关联的在库资产，提供细粒度的前端可用检索。
     */
    @Override
    public List<AmsAsset> selectAvailableReceiveAssetList(AmsAsset asset, Long currentOrderId)
    {
        return amsReceiveOrderMapper.selectAvailableReceiveAssetList(asset, currentOrderId,
                AssetStatus.IN_STOCK, ReceiveOrderStatus.DRAFT, ReceiveOrderStatus.PENDING_CONFIRM);
    }

    /**
     * 新增领用申请草稿
     * 
     * 业务逻辑：
     * 1. 验证申请人与申请部门的强对应关系，防止从前端提交脏数据。
     * 2. 通过 SysCodeRule 规则动态获取最新的流水单号。
     * 3. 锁定明细并生成资产快照、接收人快照，级联持久化明细项。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertAmsReceiveOrder(AmsReceiveOrder order, Long applicantId, String applicantName, Long applyDeptId)
    {
        // 1. 基础报文非空校验
        validateOrderRequest(order);
        validateOperator(applicantId, applicantName, "申请人");
        
        // 2. 校验申请人与申请部门的真实性与归属关系，杜绝非正常用户或越权部门申请
        SysUser applicant = requireNormalUser(applicantId, "申请人");
        SysDept applyDept = requireNormalDept(applyDeptId, "申请部门");
        if (!Objects.equals(applicant.getDeptId(), applyDept.getDeptId()))
        {
            throw new ServiceException("申请人不属于当前申请部门");
        }

        // 3. 填充主表核心状态及主单号
        order.setReceiveNo(sysCodeRuleService.nextCode(RECEIVE_ORDER_RULE));
        order.setApplicantId(applicantId);
        order.setApplicantName(StringUtils.trim(applicantName));
        order.setApplyDeptId(applyDept.getDeptId());
        order.setApplyDeptName(applyDept.getDeptName());
        
        // 新建草稿时，确认信息与驳回原因必须置空，防止脏数据注入
        order.setConfirmUserId(null);
        order.setConfirmUserName(null);
        order.setConfirmTime(null);
        order.setRejectReason(null);
        order.setOrderStatus(ReceiveOrderStatus.DRAFT);
        order.setDelFlag(DEL_FLAG_NORMAL);
        order.setCreateTime(DateUtils.getNowDate());
        
        // 4. 按序锁定资产并回填相关资产在库时的位置快照及领用目标归属
        fillOrderSnapshots(order, null);

        // 5. 写入数据库并级联保存子表
        if (amsReceiveOrderMapper.insertAmsReceiveOrder(order) != 1 || StringUtils.isNull(order.getOrderId()))
        {
            throw new ServiceException("领用单保存失败");
        }
        insertReceiveOrderItems(order);
        return 1;
    }

    /**
     * 更新资产领用草稿
     * 
     * 业务逻辑：
     * 1. 采用 FOR UPDATE 排他性锁定主表，核验状态是否为 DRAFT。
     * 2. 重置明细快照并重新进行排他性资产占用核验。
     * 3. 先清空旧明细（物理删除），再插入新明细，最后更新主表非受控字段。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateAmsReceiveOrder(AmsReceiveOrder order)
    {
        if (StringUtils.isNull(order) || StringUtils.isNull(order.getOrderId()))
        {
            throw new ServiceException("领用单ID不能为空");
        }
        // 1. 获取主表悲观排他锁，并强制校验状态。非草稿状态严禁调用修改接口
        AmsReceiveOrder current = requireOrderForUpdate(order.getOrderId(), ReceiveOrderStatus.DRAFT,
                "仅草稿领用单允许修改");
        validateOrderRequest(order);

        // 2. 强行还原受控字段，防止前端绕过系统校验恶意修改单号、状态或申请归属
        order.setReceiveNo(current.getReceiveNo());
        order.setApplicantId(current.getApplicantId());
        order.setApplicantName(current.getApplicantName());
        order.setApplyDeptId(current.getApplyDeptId());
        order.setApplyDeptName(current.getApplyDeptName());
        order.setConfirmUserId(null);
        order.setConfirmUserName(null);
        order.setConfirmTime(null);
        order.setRejectReason(null);
        order.setOrderStatus(ReceiveOrderStatus.DRAFT);
        order.setUpdateTime(DateUtils.getNowDate());
        
        // 3. 校验并重新填充资产快照与领用接收人姓名
        fillOrderSnapshots(order, order.getOrderId());

        // 4. “先物理删后增”策略刷新明细，保证明细数据洁净度
        amsReceiveOrderMapper.deleteAmsReceiveOrderItemByOrderId(order.getOrderId());
        insertReceiveOrderItems(order);
        
        // 5. 乐观更新主表，更新失败说明状态已被其他并发线程篡改
        if (amsReceiveOrderMapper.updateAmsReceiveOrder(order) != 1)
        {
            throw new ServiceException("领用单状态已变化，保存失败");
        }
        return 1;
    }

    /**
     * 提交资产领用单
     * 
     * 业务逻辑：
     * 1. 悲观锁锁定主表并确认状态为 DRAFT。
     * 2. 循环锁定所选资产。若在库资产的当前仓位与草稿快照不匹配（说明在草稿存续期内资产发生了移动），强制驳回要求重新编辑。
     * 3. 状态扭转为 PENDING_CONFIRM (待确认)，资产实际归属暂时不发生变化。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int submitReceive(Long orderId, String operateLoginName)
    {
        // 1. 锁定领用主表记录
        AmsReceiveOrder order = requireOrderForUpdate(orderId, ReceiveOrderStatus.DRAFT,
                "仅草稿领用单允许提交");
        validateLoginName(operateLoginName);
        
        // 2. 核心校验：锁定资产、核验跨单据占用以及核对仓位一致性（防止基于脏快照流转）
        validateOrderReadyForFlow(order);

        // 3. 更新主表状态为待确认
        order.setOrderStatus(ReceiveOrderStatus.PENDING_CONFIRM);
        order.setUpdateBy(StringUtils.trim(operateLoginName));
        order.setUpdateTime(DateUtils.getNowDate());
        if (amsReceiveOrderMapper.submitAmsReceiveOrder(order) != 1)
        {
            throw new ServiceException("领用单状态已变化，提交失败");
        }
        return 1;
    }

    /**
     * 确认资产领用
     * 
     * 业务逻辑：
     * 1. 获取待确认状态的领用主表悲观锁。
     * 2. 对明细中涉及的所有资产通过排他锁锁定，重核占用和仓位一致性快照。
     * 3. 逐条调用公共状态扭转服务 `assetStatusTransitionService` 执行状态流转，生成履历明细。
     * 4. 扭转主表为 COMPLETED 状态，记录确认人快照及确认时间。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int confirmReceive(Long orderId, Long operateUserId, String operateUserName, String operateLoginName)
    {
        // 1. 悲观锁锁定主单并校验状态
        AmsReceiveOrder order = requireOrderForUpdate(orderId, ReceiveOrderStatus.PENDING_CONFIRM,
                "仅待确认领用单允许确认");
        validateOperator(operateUserId, operateUserName, "确认人");
        validateLoginName(operateLoginName);
        
        // 2. 二次确认校验：重新锁定明细资产，确保仓位和可用状态无异常
        validateOrderReadyForFlow(order);

        // 3. 逐条执行资产状态更新及履历创建
        for (AmsReceiveOrderItem item : sortedItems(order))
        {
            // 构造转移上下文对象，用以在履历中精确追踪原始单号与明细关系
            AssetTransitionContext context = new AssetTransitionContext();
            context.setSourceOrderId(order.getOrderId());
            context.setSourceOrderNo(order.getReceiveNo());
            context.setSourceItemId(item.getItemId());
            context.setOperateUserId(operateUserId);
            context.setOperateUserName(StringUtils.trim(operateUserName));
            context.setOperateLoginName(StringUtils.trim(operateLoginName));
            context.setChangeSummary("确认资产领用");
            context.setRemark(order.getRemark());
            
            // 领用确认后实物离开仓库，公共流转服务会清空当前仓位、更新使用归属并生成履历。
            assetStatusTransitionService.confirmReceive(item.getAssetId(), item.getReceiveDeptId(),
                    item.getReceiveUserId(), context);
        }

        // 4. 扭转单据为已完成状态
        Date now = DateUtils.getNowDate();
        order.setOrderStatus(ReceiveOrderStatus.COMPLETED);
        order.setConfirmUserId(operateUserId);
        order.setConfirmUserName(StringUtils.trim(operateUserName));
        order.setConfirmTime(now);
        order.setRejectReason(null); // 确认通过，清空历史驳回信息
        order.setUpdateBy(StringUtils.trim(operateLoginName));
        order.setUpdateTime(now);
        if (amsReceiveOrderMapper.confirmAmsReceiveOrder(order) != 1)
        {
            throw new ServiceException("领用单状态已变化，确认领用失败");
        }
        return 1;
    }

    /**
     * 驳回资产领用申请
     * 
     * 业务设计：
     * 驳回操作仅流转单据状态（扭转为 REJECTED 并记录原因），并不对底层的在库资产发生实质修改。
     * 因此，驳回时不执行任何资产快照或状态比对。即使在审批期间资产发生了其他流转，此处也无条件允许直接驳回，缩短冲突链路。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int rejectReceive(Long orderId, String rejectReason, String operateLoginName)
    {
        // 1. 锁定领用主单，确认状态为 PENDING_CONFIRM
        AmsReceiveOrder order = requireOrderForUpdate(orderId, ReceiveOrderStatus.PENDING_CONFIRM,
                "仅待确认领用单允许驳回");
        validateLoginName(operateLoginName);
        if (StringUtils.isEmpty(StringUtils.trim(rejectReason)))
        {
            throw new ServiceException("驳回原因不能为空");
        }
        validateLength(StringUtils.trim(rejectReason), 500, "驳回原因");

        // 2. 状态扭转为已驳回
        order.setOrderStatus(ReceiveOrderStatus.REJECTED);
        order.setRejectReason(StringUtils.trim(rejectReason));
        order.setUpdateBy(StringUtils.trim(operateLoginName));
        order.setUpdateTime(DateUtils.getNowDate());
        if (amsReceiveOrderMapper.rejectAmsReceiveOrder(order) != 1)
        {
            throw new ServiceException("领用单状态已变化，驳回失败");
        }
        return 1;
    }

    /**
     * 批量逻辑删除领用单草稿。
     * 限制：逐一悲观加锁，且强制校验前置状态必须为 DRAFT。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteAmsReceiveOrderByOrderIds(String orderIds)
    {
        // 对要操作的ID升序排列，避免批量删除时产生死锁
        Long[] sortedIds = Arrays.stream(Convert.toStrArray(orderIds))
                .map(Long::valueOf).sorted().toArray(Long[]::new);
        int rows = 0;
        for (Long orderId : sortedIds)
        {
            AmsReceiveOrder order = amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(orderId);
            if (StringUtils.isNull(order))
            {
                continue;
            }
            requireDraft(order); // 强校验是否为草稿
            amsReceiveOrderMapper.deleteAmsReceiveOrderItemByOrderId(orderId);
            rows += amsReceiveOrderMapper.deleteAmsReceiveOrderByOrderId(orderId);
        }
        return rows;
    }

    /**
     * 逻辑删除单个领用单。
     * 限制：强制校验状态为 DRAFT。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteAmsReceiveOrderByOrderId(Long orderId)
    {
        AmsReceiveOrder order = amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(orderId);
        if (StringUtils.isNull(order))
        {
            return 0;
        }
        requireDraft(order);
        amsReceiveOrderMapper.deleteAmsReceiveOrderItemByOrderId(orderId);
        return amsReceiveOrderMapper.deleteAmsReceiveOrderByOrderId(orderId);
    }

    private void validateOrderRequest(AmsReceiveOrder order)
    {
        if (StringUtils.isNull(order))
        {
            throw new ServiceException("领用单不能为空");
        }
        if (order.getAmsReceiveOrderItemList() == null || order.getAmsReceiveOrderItemList().isEmpty())
        {
            throw new ServiceException("领用单明细不能为空");
        }
        validateLength(order.getRemark(), 500, "备注");
    }

    /**
     * 资产快照与领用归属快照填充（在草稿保存、编辑修改时执行）
     */
    private void fillOrderSnapshots(AmsReceiveOrder order, Long currentOrderId)
    {
        Set<Long> assetIds = new HashSet<>();
        // 1. 获取升序排列后的明细项，以此顺序逐个加锁，防范并发死锁
        List<AmsReceiveOrderItem> items = sortedItems(order);
        order.setAmsReceiveOrderItemList(items);
        
        for (AmsReceiveOrderItem item : items)
        {
            // 2. 悲观锁定该资产，并校验其是否在库，防范脏读
            AmsAsset asset = lockReceivableAsset(item.getAssetId());
            if (!assetIds.add(asset.getAssetId()))
            {
                throw new ServiceException("同一领用单不能重复选择资产");
            }
            
            // 3. 校验此资产当前是否正处于其他活跃领用单的锁定列表中
            validateNotOccupied(asset, currentOrderId);
            
            // 4. 回填资产领用前的位置及类别明细历史快照
            fillAssetSnapshots(item, asset);
            
            // 5. 校验前端提交的“领用人”与“领用部门”归属的合法性，并进行姓名快照保存
            fillReceiveOwnershipSnapshots(item, asset);
            
            item.setReceiveNo(order.getReceiveNo());
            item.setDelFlag(DEL_FLAG_NORMAL);
            validateLength(item.getRemark(), 500, "明细备注");
        }
    }

    /**
     * 流程提交及确认前的二次快照与占用校验。
     * 业务意图：在单据流转至下一状态节点前，重新锁定所有资产，验证物理位置有无挪动，以及是否被其他单据中途占用，保证业务一致性。
     */
    private void validateOrderReadyForFlow(AmsReceiveOrder order)
    {
        List<AmsReceiveOrderItem> items = sortedItems(order);
        if (items.isEmpty())
        {
            throw new ServiceException("领用单明细不能为空");
        }
        Set<Long> assetIds = new HashSet<>();
        for (AmsReceiveOrderItem item : items)
        {
            // 重新获取该资产在当前数据库中的物理排他锁
            AmsAsset asset = lockReceivableAsset(item.getAssetId());
            if (!assetIds.add(asset.getAssetId()))
            {
                throw new ServiceException("同一领用单不能重复选择资产");
            }
            
            // 二次确认是否被其他单据非法占用
            validateNotOccupied(asset, order.getOrderId());
            
            // 二次确认仓位信息是否与草稿保存时的“领用前物理位置快照”保持一致
            validateBeforeSnapshot(item, asset);
            
            // 刷新使用归属的最新人员名称与部门快照
            fillReceiveOwnershipSnapshots(item, asset);
        }
    }

    /**
     * 锁定资产并验证其可领用性
     * 
     * 业务防坑点：
     * 1. 采用 `selectAmsAssetByAssetIdForUpdate` 对单条资产记录加悲观排他锁，防止并发流程同时处理它。
     * 2. 强校验状态必须是 IN_STOCK 且在物理仓位信息上完整无缺。
     * 3. 强校验在库资产不能有任何已存在的使用归属（UseDeptId / UseUserId 必须为空），防止脏数据。
     */
    private AmsAsset lockReceivableAsset(Long assetId)
    {
        if (StringUtils.isNull(assetId))
        {
            throw new ServiceException("领用资产不能为空");
        }
        // 数据库层面加排他锁
        AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(assetId);
        if (StringUtils.isNull(asset))
        {
            throw new ServiceException("领用资产不存在或已删除");
        }
        // 可用状态检查
        if (!StringUtils.equals(AssetStatus.IN_STOCK, asset.getAssetStatus()))
        {
            throw new ServiceException(StringUtils.format("资产【{}】当前状态不允许领用", asset.getAssetCode()));
        }
        // 完整仓位检查
        if (StringUtils.isNull(asset.getWarehouseId()) || StringUtils.isNull(asset.getLocationId()))
        {
            throw new ServiceException(StringUtils.format("资产【{}】缺少当前仓库或位置", asset.getAssetCode()));
        }
        // 脏归属数据校验：处于在库状态的资产不允许有任何现存的使用部门或使用人
        if (StringUtils.isNotNull(asset.getUseDeptId()) || StringUtils.isNotNull(asset.getUseUserId())
                || StringUtils.isNotEmpty(asset.getUseDeptName()) || StringUtils.isNotEmpty(asset.getUseUserName()))
        {
            throw new ServiceException(StringUtils.format("在库资产【{}】仍存在使用归属，请先修正", asset.getAssetCode()));
        }
        return asset;
    }

    /**
     * 核对资产有无被除当前领用单以外的其他活跃领用单（DRAFT / PENDING_CONFIRM）占用
     */
    private void validateNotOccupied(AmsAsset asset, Long currentOrderId)
    {
        if (amsReceiveOrderMapper.countOtherActiveReceiveOrderByAssetId(asset.getAssetId(), currentOrderId,
                ReceiveOrderStatus.DRAFT, ReceiveOrderStatus.PENDING_CONFIRM) > 0)
        {
            throw new ServiceException(StringUtils.format("资产【{}】已被其他有效领用单占用", asset.getAssetCode()));
        }
    }

    /**
     * 写入领用前的资产基本属性快照与物理位置快照
     */
    private void fillAssetSnapshots(AmsReceiveOrderItem item, AmsAsset asset)
    {
        item.setAssetCode(asset.getAssetCode());
        item.setAssetName(asset.getAssetName());
        item.setCategoryId(asset.getCategoryId());
        item.setCategoryCode(asset.getCategoryCode());
        item.setCategoryName(asset.getCategoryName());
        item.setSpecModel(asset.getSpecModel());
        item.setBrand(asset.getBrand());
        item.setAssetStatus(asset.getAssetStatus());
        
        // 存储领用发生前的一号仓库及库位快照，作为后续生成履历资产流转的“始发地”凭证
        item.setBeforeWarehouseId(asset.getWarehouseId());
        item.setBeforeWarehouseCode(asset.getWarehouseCode());
        item.setBeforeWarehouseName(asset.getWarehouseName());
        item.setBeforeLocationId(asset.getLocationId());
        item.setBeforeLocationCode(asset.getLocationCode());
        item.setBeforeLocationName(asset.getLocationName());
    }

    /**
     * 验证领用后的人员和部门对应合法性，并保存人员、部门名称历史快照
     */
    private void fillReceiveOwnershipSnapshots(AmsReceiveOrderItem item, AmsAsset asset)
    {
        SysDept dept = requireNormalDept(item.getReceiveDeptId(), "领用部门");
        SysUser user = requireNormalUser(item.getReceiveUserId(), "领用人");
        // 强制合规约束：所选领用人必须隶属于指定的领用部门，防止前台恶意提交不匹配的关联关系
        if (!Objects.equals(user.getDeptId(), dept.getDeptId()))
        {
            throw new ServiceException(StringUtils.format("资产【{}】的领用人不属于所选领用部门", asset.getAssetCode()));
        }
        item.setReceiveDeptName(dept.getDeptName());
        item.setReceiveUserName(user.getUserName());
    }

    /**
     * 核对草稿快照仓位与当前物理库位是否保持一致。
     * 业务意图：防范草稿暂存到最终审批流转期间，资产发生了二次物理移库或位置变更。若不匹配，则强制退回。
     */
    private void validateBeforeSnapshot(AmsReceiveOrderItem item, AmsAsset asset)
    {
        if (!Objects.equals(item.getBeforeWarehouseId(), asset.getWarehouseId())
                || !Objects.equals(item.getBeforeLocationId(), asset.getLocationId()))
        {
            throw new ServiceException(StringUtils.format("资产【{}】当前仓位已变化，请重新编辑并提交领用单",
                    asset.getAssetCode()));
        }
    }

    /**
     * 检索校验正常运行中的部门记录，并防止引用已注销或停用的组织
     */
    private SysDept requireNormalDept(Long deptId, String fieldName)
    {
        if (StringUtils.isNull(deptId))
        {
            throw new ServiceException(fieldName + "不能为空");
        }
        SysDept dept = sysDeptService.selectDeptById(deptId);
        if (StringUtils.isNull(dept)
                || !StringUtils.equals(UserConstants.DEPT_NORMAL, dept.getStatus())
                || !StringUtils.equals(DEL_FLAG_NORMAL, dept.getDelFlag()))
        {
            throw new ServiceException(fieldName + "不存在或已停用");
        }
        return dept;
    }

    /**
     * 检索校验在册正常运行的用户记录，并防止引用已停用或已注销的员工
     */
    private SysUser requireNormalUser(Long userId, String fieldName)
    {
        if (StringUtils.isNull(userId))
        {
            throw new ServiceException(fieldName + "不能为空");
        }
        SysUser user = sysUserService.selectUserById(userId);
        if (StringUtils.isNull(user)
                || !StringUtils.equals(UserConstants.NORMAL, user.getStatus())
                || !StringUtils.equals(DEL_FLAG_NORMAL, user.getDelFlag()))
        {
            throw new ServiceException(fieldName + "不存在或已停用");
        }
        return user;
    }

    /**
     * 悲观检索锁定主单，并核验单据状态符合要求
     */
    private AmsReceiveOrder requireOrderForUpdate(Long orderId, String requiredStatus, String message)
    {
        if (StringUtils.isNull(orderId))
        {
            throw new ServiceException("领用单ID不能为空");
        }
        AmsReceiveOrder order = amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(orderId);
        if (StringUtils.isNull(order))
        {
            throw new ServiceException("领用单不存在或已删除");
        }
        if (!StringUtils.equals(requiredStatus, order.getOrderStatus()))
        {
            throw new ServiceException(message);
        }
        return order;
    }

    /**
     * 单据删除校验：仅限 DRAFT 草稿状态单据可逻辑删除
     */
    private void requireDraft(AmsReceiveOrder order)
    {
        if (!StringUtils.equals(ReceiveOrderStatus.DRAFT, order.getOrderStatus()))
        {
            throw new ServiceException("仅草稿领用单允许删除");
        }
    }

    /**
     * 对领用明细项列表按资产 ID 升序排列。
     * 业务意图：极其重要！在需要批量对数据库中的资产行进行加锁控制时，必须保证各单据以一字不差的资产主键排序顺序加锁，以彻底规避并发事务下的死锁问题。
     */
    private List<AmsReceiveOrderItem> sortedItems(AmsReceiveOrder order)
    {
        if (order.getAmsReceiveOrderItemList() == null)
        {
            return new ArrayList<>();
        }
        List<AmsReceiveOrderItem> items = new ArrayList<>(order.getAmsReceiveOrderItemList());
        items.sort(Comparator.comparing(AmsReceiveOrderItem::getAssetId, Comparator.nullsFirst(Long::compareTo)));
        return items;
    }

    /**
     * 级联插入子表记录
     */
    private void insertReceiveOrderItems(AmsReceiveOrder order)
    {
        List<AmsReceiveOrderItem> items = new ArrayList<>();
        Date now = DateUtils.getNowDate();
        for (AmsReceiveOrderItem item : order.getAmsReceiveOrderItemList())
        {
            item.setItemId(null); // 重置主键，防止前端提交脏主键引发重复键异常
            item.setOrderId(order.getOrderId());
            item.setCreateBy(StringUtils.isNotEmpty(order.getCreateBy()) ? order.getCreateBy() : order.getUpdateBy());
            item.setCreateTime(now);
            item.setUpdateBy(null);
            item.setUpdateTime(null);
            items.add(item);
        }
        // 执行 MyBatis 批量拼装插入
        if (amsReceiveOrderMapper.batchAmsReceiveOrderItem(items) != items.size())
        {
            throw new ServiceException("领用单明细保存失败");
        }
    }

    private void validateOperator(Long userId, String userName, String fieldName)
    {
        if (StringUtils.isNull(userId) || StringUtils.isEmpty(StringUtils.trim(userName)))
        {
            throw new ServiceException(fieldName + "ID和名称不能为空");
        }
        validateLength(StringUtils.trim(userName), 100, fieldName + "名称");
    }

    private void validateLoginName(String loginName)
    {
        if (StringUtils.isEmpty(StringUtils.trim(loginName)))
        {
            throw new ServiceException("操作登录账号不能为空");
        }
        validateLength(StringUtils.trim(loginName), 64, "操作登录账号");
    }

    private void validateLength(String value, int maxLength, String fieldName)
    {
        if (StringUtils.isNotEmpty(value) && value.length() > maxLength)
        {
            throw new ServiceException(fieldName + "长度不能超过" + maxLength + "个字符");
        }
    }
}
