1
647556386
2025-11-30 8639f19c82f6e263654db44286256bb8d028d2c2
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/MaterialUnitService.cs
@@ -1,24 +1,415 @@
using System;
using Microsoft.AspNetCore.JsonPatch.Internal;
using Microsoft.Extensions.Logging;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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
{
    public class MaterialUnitService : ServiceBase<Dt_MaterialUnit, IRepository<Dt_MaterialUnit>>, IMaterialUnitService
    {
        private readonly ILogger<MaterialUnitService> _logger;
        public IRepository<Dt_MaterialUnit> Repository => BaseDal;
        public MaterialUnitService(IRepository<Dt_MaterialUnit> BaseDal) : base(BaseDal)
        public IRepository<Dt_MaterielInfo> _materielInfoRepository;
        public MaterialUnitService(IRepository<Dt_MaterialUnit> BaseDal, ILogger<MaterialUnitService> logger, IRepository<Dt_MaterielInfo> materielInfoRepository) : base(BaseDal)
        {
            _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<MaterialWithUnitConversionResult> ConvertAsync(string materialCode, decimal quantity, string fromUnit, string toUnit)
        {
            if (string.IsNullOrEmpty(materialCode))
                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 materialData = await GetMaterialWithUnitsAsync(materialCode);
            var ratio = GetConversionRatioFromData(materialData, fromUnit, toUnit);
            if (ratio == null)
            {
                _logger.LogWarning($"未找到物料 {materialCode} ä»Ž {fromUnit} åˆ° {toUnit} çš„单位转换关系");
                throw new InvalidOperationException($"未找到物料 {materialCode} ä»Ž {fromUnit} åˆ° {toUnit} çš„单位转换关系");
            }
            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<MaterialWithUnitConversionResult> ConvertPurchaseToStockAsync(string materialCode, decimal quantity)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            // å¦‚果采购单位和库存单位相同,直接返回
            if (materialData.PurchaseUnit.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, materialData.StockUnit, false);
            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<MaterialWithUnitConversionResult> ConvertIssueToStockAsync(string materialCode, decimal quantity)
        {
            var materialData = await GetMaterialWithUnitsAsync(materialCode);
            // å¦‚果领料单位和库存单位相同,直接返回
            if (materialData.IssueUnit.Equals(materialData.StockUnit, StringComparison.OrdinalIgnoreCase))
                return new MaterialWithUnitConversionResult(quantity, materialData.StockUnit, false);
            return   ConvertAsync(materialData, quantity, materialData.IssueUnit, materialData.StockUnit);
        }
        /// <summary>
        /// èŽ·å–å•ä½è½¬æ¢æ¯”çŽ‡
        /// </summary>
        public async Task<decimal?> GetConversionRatioAsync(string materialCode, string fromUnit, string toUnit)
        {
            // å¦‚果单位相同,比率为1
            if (fromUnit.Equals(toUnit, StringComparison.OrdinalIgnoreCase))
                return 1m;
            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 directConversion.Ratio;
            }
            // æŸ¥æ‰¾åå‘转换关系(取倒数)
            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 stockUnit = materialData.StockUnit;
            // æŸ¥æ‰¾ 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)
            {
                ratioFromToStock = fromToStock.Ratio;
            }
            else if (stockToFrom != null)
            {
                ratioFromToStock = 1 / stockToFrom.Ratio;
            }
            else if (fromUnit.Equals(stockUnit, StringComparison.OrdinalIgnoreCase))
            {
                ratioFromToStock = 1; // å¦‚果源单位就是库存单位,比率为1
            }
            // è®¡ç®— 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 (ratioFromToStock != null && ratioStockToTo != null)
            {
                return ratioFromToStock * ratioStockToTo;
            }
            return null;
        }
        /// <summary>
        /// æ‰¹é‡è½¬æ¢ï¼ˆä¼˜åŒ–性能,一次查询处理多个物料)
        /// </summary>
        public async Task<Dictionary<string, MaterialWithUnitConversionResult>> BatchConvertAsync(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 (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);
                    }
                }
            }
            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;
        }
    }
}