pan
2025-11-28 cf3050083e157819b94781d0445547ffc73e21f2
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/MaterialUnitService.cs
@@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.JsonPatch.Internal;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -6,9 +8,11 @@
using System.Threading.Tasks;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Basic;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
namespace WIDESEA_BasicService
{
@@ -23,10 +27,72 @@
            _logger = logger;
            _materielInfoRepository = materielInfoRepository;
        }
        /// <summary>
        /// èŽ·å–ç‰©æ–™åŠå…¶æ‰€æœ‰å•ä½è½¬æ¢å…³ç³»ï¼ˆä½¿ç”¨MaterialUnit左连接Material)
        /// </summary>
        private async Task<MaterialWithUnits> GetMaterialWithUnitsAsync(string materialCode)
        {
            var unitConversions = await Repository.Db.Queryable<Dt_MaterialUnit>()
                .LeftJoin<Dt_MaterielInfo>((u, m) => u.ItemNo == m.MaterielCode)
                .Where((u, m) => u.ItemNo == materialCode)
                .Select((u, m) => new
                {
                    MaterialCode = u.ItemNo,
                    FromUom = u.FromUom,
                    ToUom = u.ToUom,
                    Ratio = u.Ratio,
                    IssueUnit = m.usageUOM,
                    PurchaseUnit = m.purchaseUOM,
                    StockUnit = m.inventoryUOM
                })
                .ToListAsync();
            if (!unitConversions.Any())
            {
                // å¦‚果没有找到单位转换记录,尝试直接获取物料信息
                var material = await _materielInfoRepository.Db.Queryable<Dt_MaterielInfo>()
                    .Where(m => m.MaterielCode == materialCode)
                    .FirstAsync();
                if (material == null)
                {
                    throw new ArgumentException($"未找到物料编号: {materialCode}");
                }
                return new MaterialWithUnits
                {
                    MaterialCode = materialCode,
                    IssueUnit = material.usageUOM,
                    PurchaseUnit = material.purchaseUOM,
                    StockUnit = material.inventoryUOM
                };
            }
            // ä»Žç¬¬ä¸€æ¡è®°å½•中获取物料信息(所有记录都有相同的物料信息)
            var firstRecord = unitConversions.First();
            return new MaterialWithUnits
            {
                MaterialCode = materialCode,
                IssueUnit = firstRecord.IssueUnit,
                PurchaseUnit = firstRecord.PurchaseUnit,
                StockUnit = firstRecord.StockUnit,
                UnitConversions = unitConversions.Select(u => new UnitConversion
                {
                    FromUom = u.FromUom,
                    ToUom = u.ToUom,
                    Ratio = u.Ratio
                }).ToList()
            };
        }
        /// <summary>
        /// å•位转换
        /// </summary>
        public async Task<decimal> ConvertAsync(string materialCode, decimal quantity, string fromUnit, string toUnit)
        public async Task<MaterialWithUnitConversionResult> ConvertAsync(string materialCode, decimal quantity, string fromUnit, string toUnit)
        {
            if (string.IsNullOrEmpty(materialCode))
                throw new ArgumentException("物料编号不能为空");
@@ -34,11 +100,12 @@
            if (string.IsNullOrEmpty(fromUnit) || string.IsNullOrEmpty(toUnit))
                throw new ArgumentException("单位不能为空");
            // å¦‚果单位相同,直接返回
            // å¦‚果单位相同,直接返回,不进行转换
            if (fromUnit.Equals(toUnit, StringComparison.OrdinalIgnoreCase))
                return quantity;
                return new MaterialWithUnitConversionResult(quantity, toUnit, false);
            var ratio = await GetConversionRatioAsync(materialCode, fromUnit, toUnit);
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            var ratio = GetConversionRatioFromData(materialData, fromUnit, toUnit);
            if (ratio == null)
            {
@@ -46,141 +113,303 @@
                throw new InvalidOperationException($"未找到物料 {materialCode} ä»Ž {fromUnit} åˆ° {toUnit} çš„单位转换关系");
            }
            return quantity * ratio.Value;
            var convertedQuantity = quantity * ratio.Value;
            return new MaterialWithUnitConversionResult(convertedQuantity, toUnit, true);
        }
        private  MaterialWithUnitConversionResult ConvertAsync(MaterialWithUnits materialData, decimal quantity, string fromUnit, string toUnit)
        {
            if(materialData==null)
                throw new ArgumentException("物料不存在");
            if (string.IsNullOrEmpty(fromUnit) || string.IsNullOrEmpty(toUnit))
                throw new ArgumentException("单位不能为空");
            // å¦‚果单位相同,直接返回,不进行转换
            if (fromUnit.Equals(toUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, toUnit, false);
            var ratio = GetConversionRatioFromData(materialData, fromUnit, toUnit);
            if (ratio == null)
            {
                _logger.LogWarning($"未找到物料 {materialData.MaterialCode} ä»Ž {fromUnit} åˆ° {toUnit} çš„单位转换关系");
                throw new InvalidOperationException($"未找到物料 {materialData.MaterialCode} ä»Ž {fromUnit} åˆ° {toUnit} çš„单位转换关系");
            }
            var convertedQuantity = quantity * ratio.Value;
            return new MaterialWithUnitConversionResult(convertedQuantity, toUnit, true);
        }
        /// <summary>
        /// é‡‡è´­å•位转库存单位
        /// </summary>
        public async Task<decimal> ConvertPurchaseToStockAsync(string materialCode, decimal quantity)
        public async Task<MaterialWithUnitConversionResult> ConvertPurchaseToStockAsync(string materialCode, decimal quantity)
        {
            // èŽ·å–ç‰©æ–™ä¿¡æ¯
            var material = await _materielInfoRepository.Db.Queryable<Dt_MaterielInfo>()
                .Where(x => x.MaterielCode == materialCode)
                .FirstAsync();
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            if (material == null)
            {
                throw new ArgumentException($"未找到物料编号: {materialCode}");
            }
            // å¦‚果采购单位和库存单位相同,直接返回
            if (materialData.PurchaseUnit.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, materialData.StockUnit, false);
            return await ConvertAsync(materialCode, quantity, material.purchaseUOM, material.inventoryUOM);
            return ConvertAsync(materialData, quantity, materialData.PurchaseUnit, materialData.StockUnit);
        }
        public async Task<MaterialWithUnitConversionResult> ConvertFromToStockAsync(string materialCode,string  fromUom, decimal quantity)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            // å¦‚果领料单位和库存单位相同,直接返回
            if (fromUom.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, materialData.StockUnit, false);
            return ConvertAsync(materialData, quantity, fromUom, materialData.StockUnit);
        }
        /// <summary>
        /// é¢†æ–™å•位转库存单位
        /// </summary>
        public async Task<decimal> ConvertIssueToStockAsync(string materialCode, decimal quantity)
        public async Task<MaterialWithUnitConversionResult> ConvertIssueToStockAsync(string materialCode, decimal quantity)
        {
            // èŽ·å–ç‰©æ–™ä¿¡æ¯
            var material = await _materielInfoRepository.Db.Queryable<Dt_MaterielInfo>()
                .Where(x => x.MaterielCode == materialCode)
                .FirstAsync();
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            if (material == null)
            {
                throw new ArgumentException($"未找到物料编号: {materialCode}");
            }
            // å¦‚果领料单位和库存单位相同,直接返回
            if (materialData.IssueUnit.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, materialData.StockUnit, false);
            return await ConvertAsync(materialCode, quantity, material.usageUOM, material.inventoryUOM);
            return   ConvertAsync(materialData, quantity, materialData.IssueUnit, materialData.StockUnit);
        }
        /// <summary>
        /// èŽ·å–å•ä½è½¬æ¢æ¯”çŽ‡
        /// </summary>
        public async Task<decimal?> GetConversionRatioAsync(string materialCode, string fromUnit, string toUnit)
        {
            // å°è¯•直接查找转换关系
            var conversion = await Repository.Db.Queryable<Dt_MaterialUnit>()
                .Where(x => x.ItemNo == materialCode &&
                           x.FromUom == fromUnit &&
                           x.ToUom == toUnit)
                .FirstAsync();
            // å¦‚果单位相同,比率为1
            if (fromUnit.Equals(toUnit, StringComparison.OrdinalIgnoreCase))
                return 1m;
            if (conversion != null)
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            return GetConversionRatioFromData(materialData, fromUnit, toUnit);
        }
        /// <summary>
        /// ä»Žå†…存数据中获取转换比率
        /// </summary>
        private decimal? GetConversionRatioFromData(MaterialWithUnits materialData, string fromUnit, string toUnit)
        {
            // ç›´æŽ¥æŸ¥æ‰¾è½¬æ¢å…³ç³»
            var directConversion = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(fromUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(toUnit, StringComparison.OrdinalIgnoreCase));
            if (directConversion != null)
            {
                return conversion.Ratio;
                return directConversion.Ratio;
            }
            // å°è¯•查找反向转换关系(取倒数)
            var reverseConversion = await Repository.Db.Queryable<Dt_MaterialUnit>()
                .Where(x => x.ItemNo == materialCode &&
                           x.FromUom == toUnit &&
                           x.ToUom == fromUnit)
                .FirstAsync();
            // æŸ¥æ‰¾åå‘转换关系(取倒数)
            var reverseConversion = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(toUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(fromUnit, StringComparison.OrdinalIgnoreCase));
            if (reverseConversion != null)
            {
                return 1 / reverseConversion.Ratio;
            }
            // å°è¯•通过中间单位(库存单位)进行转换
            var material = await _materielInfoRepository.Db.Queryable<Dt_MaterielInfo>()
                .Where(x => x.MaterielCode == materialCode)
                .FirstAsync();
            // é€šè¿‡åº“存单位进行间接转换
            var stockUnit = materialData.StockUnit;
            if (material != null)
            // æŸ¥æ‰¾ fromUnit -> stockUnit çš„转换
            var fromToStock = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(fromUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(stockUnit, StringComparison.OrdinalIgnoreCase));
            var stockToFrom = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(stockUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(fromUnit, StringComparison.OrdinalIgnoreCase));
            // æŸ¥æ‰¾ stockUnit -> toUnit çš„转换
            var stockToTo = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(stockUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(toUnit, StringComparison.OrdinalIgnoreCase));
            var toToStock = materialData.UnitConversions
                .FirstOrDefault(x => x.FromUom.Equals(toUnit, StringComparison.OrdinalIgnoreCase) &&
                                    x.ToUom.Equals(stockUnit, StringComparison.OrdinalIgnoreCase));
            decimal? ratioFromToStock = null;
            decimal? ratioStockToTo = null;
            // è®¡ç®— fromUnit -> stockUnit çš„æ¯”率
            if (fromToStock != null)
            {
                var stockUnit = material.inventoryUOM;
                ratioFromToStock = fromToStock.Ratio;
            }
            else if (stockToFrom != null)
            {
                ratioFromToStock = 1 / stockToFrom.Ratio;
            }
            else if (fromUnit.Equals(stockUnit, StringComparison.OrdinalIgnoreCase))
            {
                ratioFromToStock = 1; // å¦‚果源单位就是库存单位,比率为1
            }
                // å¦‚果目标单位已经是库存单位,直接查找源单位到库存单位的转换
                if (toUnit.Equals(stockUnit, StringComparison.OrdinalIgnoreCase))
                {
                    var toStockRatio = await GetDirectRatioAsync(materialCode, fromUnit, stockUnit);
                    if (toStockRatio != null) return toStockRatio;
                }
            // è®¡ç®— stockUnit -> toUnit çš„æ¯”率
            if (stockToTo != null)
            {
                ratioStockToTo = stockToTo.Ratio;
            }
            else if (toToStock != null)
            {
                ratioStockToTo = 1 / toToStock.Ratio;
            }
            else if (toUnit.Equals(stockUnit, StringComparison.OrdinalIgnoreCase))
            {
                ratioStockToTo = 1; // å¦‚果目标单位就是库存单位,比率为1
            }
                // å¦‚果源单位是库存单位,直接查找库存单位到目标单位的转换
                if (fromUnit.Equals(stockUnit, StringComparison.OrdinalIgnoreCase))
                {
                    var fromStockRatio = await GetDirectRatioAsync(materialCode, stockUnit, toUnit);
                    if (fromStockRatio != null) return fromStockRatio;
                }
                // é€šè¿‡åº“存单位进行间接转换:fromUnit -> stockUnit -> toUnit
                var ratio1 = await GetDirectRatioAsync(materialCode, fromUnit, stockUnit);
                var ratio2 = await GetDirectRatioAsync(materialCode, stockUnit, toUnit);
                if (ratio1 != null && ratio2 != null)
                {
                    return ratio1 * ratio2;
                }
            // å¦‚果找到了两条路径,返回乘积
            if (ratioFromToStock != null && ratioStockToTo != null)
            {
                return ratioFromToStock * ratioStockToTo;
            }
            return null;
        }
        /// <summary>
        /// èŽ·å–ç›´æŽ¥è½¬æ¢æ¯”çŽ‡ï¼ˆåŒ…å«æ­£å‘å’Œåå‘æŸ¥æ‰¾ï¼‰
        /// æ‰¹é‡è½¬æ¢ï¼ˆä¼˜åŒ–性能,一次查询处理多个物料)
        /// </summary>
        private async Task<decimal?> GetDirectRatioAsync(string materialCode, string fromUnit, string toUnit)
        public async Task<Dictionary<string, MaterialWithUnitConversionResult>> BatchConvertAsync(List<BatchConversionRequest> requests)
        {
            // æ­£å‘查找
            var conversion = await Repository.Db.Queryable<Dt_MaterialUnit>()
                .Where(x => x.ItemNo == materialCode &&
                           x.FromUom == fromUnit &&
                           x.ToUom == toUnit)
                .FirstAsync();
            if (requests == null || !requests.Any())
                return new Dictionary<string, MaterialWithUnitConversionResult>();
            if (conversion != null)
            // æŒ‰ç‰©æ–™ç¼–号分组
            var materialGroups = requests.GroupBy(x => x.MaterialCode);
            var results = new Dictionary<string, MaterialWithUnitConversionResult>();
            foreach (var group in materialGroups)
            {
                return conversion.Ratio;
                var materialCode = group.Key;
                var materialData = await GetMaterialWithUnitsAsync(materialCode);
                foreach (var request in group)
                {
                    try
                    {
                        // å¦‚果单位相同,直接返回
                        if (request.FromUnit.Equals(request.ToUnit, StringComparison.OrdinalIgnoreCase))
                        {
                            results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, request.ToUnit, false);
                            continue;
                        }
                        var ratio = GetConversionRatioFromData(materialData, request.FromUnit, request.ToUnit);
                        if (ratio != null)
                        {
                            var convertedQuantity = request.Quantity * ratio.Value;
                            results[request.RequestId] = new MaterialWithUnitConversionResult(convertedQuantity, request.ToUnit, true);
                        }
                        else
                        {
                            _logger.LogWarning($"未找到物料 {materialCode} ä»Ž {request.FromUnit} åˆ° {request.ToUnit} çš„单位转换关系");
                            results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, request.ToUnit, false);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, $"批量转换物料 {materialCode} æ—¶å‘生错误");
                        results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, request.ToUnit, false);
                    }
                }
            }
            // åå‘查找
            var reverseConversion = await Repository.Db.Queryable<Dt_MaterialUnit>()
                .Where(x => x.ItemNo == materialCode &&
                           x.FromUom == toUnit &&
                           x.ToUom == fromUnit)
                .FirstAsync();
            if (reverseConversion != null)
            {
                return 1 / reverseConversion.Ratio;
            }
            return null;
            return results;
        }
        /// <summary>
        /// æ‰¹é‡é‡‡è´­å•位转库存单位
        /// </summary>
        public async Task<Dictionary<string, MaterialWithUnitConversionResult>> BatchConvertPurchaseToStockAsync(List<BatchConversionRequest> requests)
        {
            if (requests == null || !requests.Any())
                return new Dictionary<string, MaterialWithUnitConversionResult>();
            // æŒ‰ç‰©æ–™ç¼–号分组
            var materialGroups = requests.GroupBy(x => x.MaterialCode);
            var results = new Dictionary<string, MaterialWithUnitConversionResult>();
            foreach (var group in materialGroups)
            {
                var materialCode = group.Key;
                var materialData = await GetMaterialWithUnitsAsync(materialCode);
                foreach (var request in group)
                {
                    try
                    {
                        // å¦‚果采购单位和库存单位相同,直接返回
                        if (materialData.PurchaseUnit.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                        {
                            results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, materialData.StockUnit, false);
                            continue;
                        }
                        var ratio = GetConversionRatioFromData(materialData, materialData.PurchaseUnit, materialData.StockUnit);
                        if (ratio != null)
                        {
                            var convertedQuantity = request.Quantity * ratio.Value;
                            results[request.RequestId] = new MaterialWithUnitConversionResult(convertedQuantity, materialData.StockUnit, true);
                        }
                        else
                        {
                            _logger.LogWarning($"未找到物料 {materialCode} ä»Žé‡‡è´­å•位到库存单位的转换关系");
                            results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, materialData.StockUnit, false);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, $"批量转换物料 {materialCode} é‡‡è´­å•位到库存单位时发生错误");
                        results[request.RequestId] = new MaterialWithUnitConversionResult(request.Quantity, materialData.StockUnit, false);
                    }
                }
            }
            return results;
        }
        /// <summary>
        /// èŽ·å–ç‰©æ–™çš„åº“å­˜å•ä½
        /// </summary>
        public async Task<string> GetStockUnitAsync(string materialCode)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            return materialData.StockUnit;
        }
        /// <summary>
        /// èŽ·å–ç‰©æ–™çš„é‡‡è´­å•ä½
        /// </summary>
        public async Task<string> GetPurchaseUnitAsync(string materialCode)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            return materialData.PurchaseUnit;
        }
        /// <summary>
        /// èŽ·å–ç‰©æ–™çš„é¢†æ–™å•ä½
        /// </summary>
        public async Task<string> GetIssueUnitAsync(string materialCode)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            return materialData.IssueUnit;
        }
    }
}