kyy
2025-07-26 787d745b9c721dbfdf61c80942916b1db3bb4211
采购仓退接口优化
已修改1个文件
245 ■■■■■ 文件已修改
MES.Service/service/BasicData/MesCgthSqManager.cs 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
MES.Service/service/BasicData/MesCgthSqManager.cs
@@ -3,19 +3,23 @@
using MES.Service.Modes;
using MES.Service.util;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MES.Service.service;
public class MesCgthSqManager : Repository<MesCgthSq>
{
    // 明细表管理器(用于处理明细数据)
    //private readonly MesCgthSqDetailManager _detailManager = new();
    /// <summary>
    /// 批量保存采购退货单(主表+明细)
    /// </summary>
    public bool SaveList(List<ErpCgth> erpCgthList)
    {
        // 逐条处理,全部成功才返回true
        if (erpCgthList == null || !erpCgthList.Any())
            throw new ArgumentNullException(nameof(erpCgthList), "待保存的退货单列表不能为空");
        // 逐条处理,全部成功才返回true(事务内批量处理更优,此处保持原有逻辑)
        var result = erpCgthList.Select(Save).ToList();
        return result.All(b => b);
    }
@@ -25,166 +29,211 @@
    /// </summary>
    public bool Save(ErpCgth erpCgth)
    {
        // 从ERP数据中提取主表和明细表DTO
        if (erpCgth == null)
            throw new ArgumentNullException(nameof(erpCgth), "退货单数据不能为空");
        if (erpCgth.ErpCgtha == null)
            throw new ArgumentNullException(nameof(erpCgth.ErpCgtha), "退货单主表数据不能为空");
        // 提取主表和明细表DTO
        var erpMain = erpCgth.ErpCgtha;
        var erpDetails = erpCgth.ErpCgthB;
        var erpDetails = erpCgth.ErpCgthB ?? new List<ErpCgthB>(); // 避免明细为null
        // 映射主表和明细表实体
        // 步骤1:映射主表基础数据(暂不处理Id)
        var mesMain = MapErpCgthaToMesCgthSq(erpMain);
        var mesDetails = MapErpCgthBToMesCgthSqDetail(erpDetails, mesMain.Id); // 关联主表ID
        // 使用事务处理主从表联动操作
        // 步骤2:提前确定主表Id(核心修正:在映射明细表前获取/生成Id)
        Guid mainId = GetOrCreateMainId(mesMain);
        // 步骤3:用确定的mainId映射明细表(确保Mid=mainId)
        var mesDetails = MapErpCgthBToMesCgthSqDetail(erpDetails, mainId);
        // 步骤4:事务内处理主从表保存
        return UseTransaction(db =>
        {
            // 根据操作类型(TYPE)执行不同逻辑(假设TYPE=1新增,2更新,3删除)
            switch (erpMain.TYPE)
            {
                case "1": // 新增
                case "2": // 更新(统一走新增或更新逻辑)
                case "4":
                    return SaveOrUpdateData(db, mesMain, mesDetails) ? 1 : 0;
                case "2": // 更新
                case "4": // 其他新增/更新类型
                    return SaveOrUpdateData(db, mesMain, mesDetails, mainId) ? 1 : 0;
                case "3": // 删除
                    return DeleteData(db, mesMain, mesDetails) ? 1 : 0;
                    return DeleteData(db, mesMain, mesDetails, mainId) ? 1 : 0;
                default:
                    throw new NotImplementedException($"操作类型[{erpMain.TYPE}]未实现");
                    throw new NotImplementedException($"未实现的操作类型:{erpMain.TYPE}");
            }
        }) > 0;
    }
    /// <summary>
    /// 提前获取或生成主表Id(确保在映射明细表前确定)
    /// </summary>
    private Guid GetOrCreateMainId(MesCgthSq mesMain)
    {
        // 优先通过单据号查询已有主表(更新场景)
        var existingMain = Context.Queryable<MesCgthSq>()
            .Where(m => m.BillNo == mesMain.BillNo)
            .First();
        if (existingMain != null)
        {
            // 已存在:返回数据库中的Id
            return existingMain.Id;
        }
        else
        {
            // 不存在:生成新Id(新增场景)
            return Guid.NewGuid();
        }
    }
    /// <summary>
    /// 新增或更新数据(主表+明细)
    /// </summary>
    private bool SaveOrUpdateData(SqlSugarScope db, MesCgthSq mesMain, List<MesCgthSqDetail> mesDetails)
    private bool SaveOrUpdateData(SqlSugarScope db, MesCgthSq mesMain, List<MesCgthSqDetail> mesDetails, Guid mainId)
    {
        // 1. 处理主表:若已存在则更新,不存在则新增
        bool isMainExist = db.Queryable<MesCgthSq>().Where(m => m.BillNo == mesMain.BillNo).Any();
        // 绑定主表Id(使用提前确定的mainId)
        mesMain.Id = mainId;
        // 处理主表:新增或更新
        bool isExist = db.Queryable<MesCgthSq>().Where(m => m.BillNo == mesMain.BillNo).Any();
        int mainResult;
        if (isMainExist)
        if (isExist)
        {
            // 更新主表(忽略空字段)
            // 更新:按Id匹配(避免单据号重复导致错误)
            mainResult = db.Updateable(mesMain)
                .IgnoreColumns(m => m.Id) // 不更新主键
                .Where(m => m.BillNo == mesMain.BillNo)
                .IgnoreColumns(m => new { m.CreateDate }) // 不更新创建时间
                .Where(m => m.Id == mainId)
                .ExecuteCommand();
        }
        else
        {
            // 新增主表(生成主键)
            mesMain.Id = Guid.NewGuid();
            // 新增:使用提前生成的Id
            mainResult = db.Insertable(mesMain).ExecuteCommand();
        }
        // 2. 处理明细表:先删除旧明细,再插入新明细(确保数据同步)
        // (通过主表ERPID关联旧明细)
        int oldDetailCount = db.Deleteable<MesCgthSqDetail>()
            .Where(d => d.Mid == mesMain.Id)
        // 处理明细表:先删旧数据,再插新数据(确保数据同步)
        // 1. 删除当前主表关联的旧明细
        int deleteOldDetailResult = db.Deleteable<MesCgthSqDetail>()
            .Where(d => d.Mid == mainId)
            .ExecuteCommand();
        // 插入新明细(关联主表ID)
        int detailResult = mesDetails.Count > 0
        // 2. 插入新明细(Mid已=mainId)
        int insertDetailResult = mesDetails.Count > 0
            ? db.Insertable(mesDetails).ExecuteCommand()
            : 1; // 无明细时默认成功
            : 1; // 无明细时视为成功
        // 3. 校验结果
        if (mainResult > 0 && detailResult > 0)
        {
            return true;
        }
        throw new NotImplementedException("主表或明细保存失败");
        // 校验结果(主表必须成功,明细删除/插入至少有一个成功)
        return mainResult > 0 && (deleteOldDetailResult >= 0 && insertDetailResult > 0);
    }
    /// <summary>
    /// 删除数据(主表+明细)
    /// </summary>
    private bool DeleteData(SqlSugarScope db, MesCgthSq mesMain, List<MesCgthSqDetail> mesDetails)
    private bool DeleteData(SqlSugarScope db, MesCgthSq mesMain, List<MesCgthSqDetail> mesDetails, Guid mainId)
    {
        // 处理ERPID为空的情况,转换为统一的空字符串进行比较
        string mainErpId = mesMain.ErpId?.ToString() ?? string.Empty;
        // 1. 删除明细(通过主表ERPID关联)
        // 1. 删除明细(按主表Id删除)
        int detailResult = db.Deleteable<MesCgthSqDetail>()
            .Where(d => (d.Eid.ToString() ?? string.Empty) == mainErpId)
            .Where(d => d.Mid == mainId)
            .ExecuteCommand();
        // 2. 删除主表(通过ERPID关联)
        // 2. 删除主表(按Id删除)
        int mainResult = db.Deleteable<MesCgthSq>()
            .Where(m => (m.ErpId.ToString() ?? string.Empty) == mainErpId)
            .Where(m => m.Id == mainId)
            .ExecuteCommand();
        return mainResult > 0 && detailResult >= 0; // 允许明细原本不存在(>=0)
        // 允许明细原本不存在(detailResult=0),但主表必须删除成功
        return mainResult > 0;
    }
    /// <summary>
    /// ErpCgtha 映射到 MesCgthSq(仅映射ErpCgtha中存在的字段)
    /// 主表映射(仅处理基础字段,Id由GetOrCreateMainId确定)
    /// </summary>
    private MesCgthSq MapErpCgthaToMesCgthSq(ErpCgtha erpMain)
    {
        return new MesCgthSq
        {
            // 主表核心字段(仅从ErpCgtha取值)
            // Id暂不赋值(由GetOrCreateMainId后续确定)
            ErpId = erpMain.ERPID, // ERP主表ID
            BillNo = erpMain.billNo, // 单据编号
            Type = erpMain.TYPE, // 操作类型
            FDate = erpMain.FDate, // 退料日期
            FDocumentStatus = erpMain.FDocumentStatus, // 单据状态
            FSupplierId = erpMain.FSupplierID, // 供应商ID
            FBillTypeId = erpMain.FBillTypeID, // 单据类型
            FBusinessType = erpMain.FBusinessType, // 业务类型
            ReturnType = erpMain.FMRTYPE, // 退料类型(对应ErpCgtha的退料类型)
            ReturnMethod = erpMain.FMRMODE, // 退料方式(对应ErpCgtha的退料方式)
            CreateBy = erpMain.FCreatorId, // 创建人
            FPurchaseOrgId = erpMain.FPurchaseOrgId, // 采购组织
            ThOrgId = erpMain.FStockOrgId, // 退料组织
            FRequireOrgId = erpMain.FRequireOrgId, // 需求组织
            FMRDeptId = erpMain.FMRDeptId, // 退料部门
            FStockerId = erpMain.FSTOCKERID, // 仓管员
            FPurchaserId = erpMain.FPURCHASERID, // 采购员
            FMRReason = erpMain.FMRREASON, // 退料原因
            FPurchaseDeptId = erpMain.FPURCHASEDEPTID, // 采购部门
            FPurchaserGroupId = erpMain.FPURCHASERGROUPID, // 采购组
            FACCTYPE = erpMain.FACCTYPE, // 验收方式
            FCreateDate = erpMain.FCreateDate, // 创建日期
            FWPVTINTEGERL6W = erpMain.F_WPVT_INTEGER_L6W, // 扫码标识
            // DepotId = string.IsNullOrEmpty(erpMain.FSTOCKID)
            //     ? null
            //     : Convert.ToInt32(erpMain.FSTOCKID), // 仓库ID(转换为int)
            SuppId = string.IsNullOrEmpty(erpMain.FSupplierID)
                ? null
                : Convert.ToInt32(erpMain.FSupplierID), // 供应商ID(转换为int)
            // 系统自动赋值字段
            BillNo = erpMain.billNo ?? throw new ArgumentNullException(nameof(erpMain.billNo), "单据编号不能为空"),
            Type = erpMain.TYPE,
            FDate = erpMain.FDate,
            FDocumentStatus = erpMain.FDocumentStatus,
            FSupplierId = erpMain.FSupplierID,
            FBillTypeId = erpMain.FBillTypeID,
            FBusinessType = erpMain.FBusinessType,
            ReturnType = erpMain.FMRTYPE,
            ReturnMethod = erpMain.FMRMODE,
            CreateBy = erpMain.FCreatorId,
            FPurchaseOrgId = erpMain.FPurchaseOrgId,
            ThOrgId = erpMain.FStockOrgId,
            FRequireOrgId = erpMain.FRequireOrgId,
            FMRDeptId = erpMain.FMRDeptId,
            FStockerId = erpMain.FSTOCKERID,
            FPurchaserId = erpMain.FPURCHASERID,
            FMRReason = erpMain.FMRREASON,
            FPurchaseDeptId = erpMain.FPURCHASEDEPTID,
            FPurchaserGroupId = erpMain.FPURCHASERGROUPID,
            FACCTYPE = erpMain.FACCTYPE,
            FCreateDate = erpMain.FCreateDate,
            FWPVTINTEGERL6W = erpMain.F_WPVT_INTEGER_L6W,
            // 供应商ID(安全转换)
            SuppId = !string.IsNullOrEmpty(erpMain.FSupplierID)
                     && int.TryParse(erpMain.FSupplierID, out int suppId)
                ? suppId
                : null,
            // 系统字段(创建时间仅新增时赋值,更新时不覆盖)
            CreateDate = DateTime.Now,
            LastUpdateTime = DateTime.Now
        };
    }
    /// <summary>
    /// ErpCgthB 映射到 MesCgthSqDetail(仅映射ErpCgthB中存在的字段)
    /// 明细表映射(使用提前确定的mainId作为Mid)
    /// </summary>
    private List<MesCgthSqDetail> MapErpCgthBToMesCgthSqDetail(List<ErpCgthB> erpDetails, Guid mainId)
    {
        return erpDetails.Select(erpDetail => new MesCgthSqDetail
        {
            // 关联主表ID
            Mid= mainId, // 主表ID(MesCgthSq的Id)
            // 明细核心字段(仅从ErpCgthB取值)
            ErpId = string.IsNullOrEmpty(erpDetail.ERPID) ? null : Convert.ToInt32(erpDetail.ERPID), // ERP明细ID
            Eid = string.IsNullOrEmpty(erpDetail.EID) ? null : Convert.ToInt32(erpDetail.EID), // 外部系统ID
            FsrcBillNo = erpDetail.FSRCBillNo, // 源单单号
            FsrcBillTypeId = erpDetail.FSRCBillTypeId, // 源单类型
            ItemId = string.IsNullOrEmpty(erpDetail.FMATERIALID)
                ? null
                : Convert.ToInt32(erpDetail.FMATERIALID), // 物料ID
            FUnitId = erpDetail.FUnitID, // 库存单位
            DepotId = string.IsNullOrEmpty(erpDetail.FSTOCKID) ? null : Convert.ToInt64(erpDetail.FSTOCKID), // 仓库ID
            FstockLocId = erpDetail.FSTOCKLOCID, // 仓位
            FLot = erpDetail.FLot, // 批号
            SqNum = erpDetail.FRMREALQTY, // 实退数量
            Remark = erpDetail.FNOTE, // 备注
            FMtoNo = erpDetail.FMtoNo, // 计划跟踪号
            // 系统自动赋值字段
            Mid = mainId, // 直接使用提前确定的mainId(确保非全零)
            // ERP明细ID(安全转换)
            ErpId = !string.IsNullOrEmpty(erpDetail.ERPID)
                    && int.TryParse(erpDetail.ERPID, out int erpId)
                ? erpId
                : null,
            // 外部系统ID(安全转换)
            Eid = !string.IsNullOrEmpty(erpDetail.EID)
                  && int.TryParse(erpDetail.EID, out int eid)
                ? eid
                : null,
            FsrcBillNo = erpDetail.FSRCBillNo,
            FsrcBillTypeId = erpDetail.FSRCBillTypeId,
            // 物料ID(安全转换)
            ItemId = !string.IsNullOrEmpty(erpDetail.FMATERIALID)
                     && int.TryParse(erpDetail.FMATERIALID, out int itemId)
                ? itemId
                : null,
            FUnitId = erpDetail.FUnitID,
            // 仓库ID(安全转换)
            DepotId = !string.IsNullOrEmpty(erpDetail.FSTOCKID)
                      && long.TryParse(erpDetail.FSTOCKID, out long depotId)
                ? depotId
                : null,
            FstockLocId = erpDetail.FSTOCKLOCID,
            FLot = erpDetail.FLot,
            SqNum = erpDetail.FRMREALQTY, // 假设FRMREALQTY是数值类型(如decimal)
            Remark = erpDetail.FNOTE,
            FMtoNo = erpDetail.FMtoNo,
            IsFinish = false // 初始未完成
        }).ToList();
    }
   }
}