Просмотр исходного кода

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

yuanrf дней назад: 2
Родитель
Сommit
e7e76a9d11

+ 19 - 3
OASystem/OASystem.Api/Controllers/FinancialController.cs

@@ -55,6 +55,7 @@ namespace OASystem.API.Controllers
         private readonly PaymentRefundAndOtherMoneyRepository _paymentRefundAndOtherMoneyRep; //收款退还与其他款项 仓库
         private readonly DelegationInfoRepository _delegationInfoRep;             //团组信息 仓库
         private readonly ForeignReceivablesRepository _foreignReceivablesRepository;
+        private readonly CheckBoxsRepository _groupCheckBox; //团组成本选中项 仓库
 
         /// <summary>
         /// 初始化
@@ -73,7 +74,9 @@ namespace OASystem.API.Controllers
             HttpClient httpClient,
             DelegationInfoRepository delegationInfoRep,
             SetDataRepository setDataRep,
-            ForeignReceivablesRepository foreignReceivablesRepository)
+            ForeignReceivablesRepository foreignReceivablesRepository,
+            CheckBoxsRepository groupCheckBox
+            )
         {
             _mapper = mapper;
             _config = configuration;
@@ -89,6 +92,7 @@ namespace OASystem.API.Controllers
             _delegationInfoRep = delegationInfoRep;
             _setDataRep = setDataRep;
             _foreignReceivablesRepository = foreignReceivablesRepository;
+            _groupCheckBox = groupCheckBox;
         }
 
         #region 日付申请
@@ -2380,7 +2384,6 @@ namespace OASystem.API.Controllers
             if (list_rst.Any())
             {
                 //查询团组各模块费用信息
-
                 string sql = @"
                     Select
                       Id,
@@ -2571,6 +2574,13 @@ namespace OASystem.API.Controllers
                     .AddParameters(new { GroupIds = groupIds })
                     .ToList();
 
+                #region 2025-12-01 新增团组成本报价
+                string groupCostSql = @"select * from  Grp_GroupCostParameter where IsDel = 0 and CostType = 'A' And diid IN (@GroupIds)";
+                var groupCostDatas = _sqlSugar.SqlQueryable<GroupCostCalculate>(groupCostSql)
+                    .AddParameters(new { GroupIds = groupIds })
+                    .ToList();
+                #endregion
+
                 int rowNumber = 1;
                 foreach (var item_rst in list_rst)
                 {
@@ -2732,6 +2742,13 @@ namespace OASystem.API.Controllers
 
                     item_rst.feeItem = feeDatas;
                     #endregion
+
+                    #region 团组成本报价信息 2025-12-01 13:56:55
+
+                    var groupCostInfo = groupCostDatas.FirstOrDefault(f => f.DiId == diId);
+                    item_rst.groupCost = groupCostInfo?.GroupTotalPrice ?? 0.00m;
+                    item_rst.groupCostItem = groupCostInfo?.ChildFeeInfos ?? new List<ChildFeeInfo>();
+                    #endregion
                 }
 
                 result.total_fr = sumAll_fr.ToString("#0.00");
@@ -2752,7 +2769,6 @@ namespace OASystem.API.Controllers
                         Workbook = new Workbook(AppSettingsHelper.Get("ExcelBasePath") + "Template/应收款项模板 - 副本.xls")
                     };
 
-
                     int excNo = 1;
                     foreach (var item in list_rst)
                     {

+ 50 - 354
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -1178,7 +1178,7 @@ namespace OASystem.API.Controllers
         /// <returns></returns>
         [HttpGet]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
-        public async Task<IActionResult> GroupItemKeywordSearch(string keyword)
+        public async Task<IActionResult> GroupItemNativeKeywordSearch (string keyword)
         {
             var jw = JsonView(true, "暂无数据");
             if (string.IsNullOrEmpty(keyword)) return Ok(jw);
@@ -1215,387 +1215,83 @@ namespace OASystem.API.Controllers
             }
 
             return Ok(jw);
-
-            #region 智能搜索
-            //try
-            //{
-            //    // 验证请求参数
-            //    if (string.IsNullOrEmpty(keyword))
-            //    {
-            //        return Ok(JsonView(true, $"暂无数据!"));
-            //    }
-
-            //    var searchRequest = new DynamicSearchRequest
-            //    {
-            //        Keyword = keyword,
-            //        PageIndex = 1,
-            //        PageSize = 9999,
-            //        FieldWeights = new Dictionary<string, int>
-            //        {
-            //            { "TeamName", 10 },
-            //            { "ClientUnit", 8 },
-            //            { "ClientName", 6 }
-            //        },
-            //        Filters = new List<SearchFilter>()
-            //        {
-            //            new(){Field = "IsDel",Operator="eq",Value="1" }
-            //        },
-            //        OrderBy = "CreateTime",
-            //        ReturnFields = new List<string>() { "TeamName", "ClientUnit", "ClientName" }
-            //    };
-
-            //    // 验证字段配置
-            //    var validation = _groupSearchService.ValidateFieldConfig(
-            //        searchRequest.FieldWeights,
-            //        searchRequest.ReturnFields);
-
-            //    if (!validation.IsValid)
-            //    {
-            //        return BadRequest(new { message = validation.Message });
-            //    }
-
-            //    var result = await _groupSearchService.SearchAsync(searchRequest);
-
-            //    if (result.Success)
-            //    {
-            //        return Ok(JsonView(true, "搜索成功!", result.Items, result.Items.Count));
-            //    }
-
-            //    return Ok(JsonView(true, $"暂无数据!"));
-            //}
-            //catch (Exception ex)
-            //{
-            //    return Ok(JsonView(true, $"搜索服务暂时不可用!"));
-            //}
-
-
-            //var stopwatch = Stopwatch.StartNew();
-
-            //if (string.IsNullOrWhiteSpace(keyword))
-            //{
-            //    var result = await GetDefaultResultsAsync();
-            //    stopwatch.Stop();
-            //    return Ok(JsonView(true, $"操作成功!耗时:{stopwatch.ElapsedMilliseconds}ms", result, result.Count));
-            //}
-
-            //// 分析搜索模式
-            //var searchAnalysis = AnalyzeSearchPattern(keyword);
-
-            //if (!searchAnalysis.HasSearchContent)
-            //{
-            //    var result = await GetDefaultResultsAsync();
-            //    stopwatch.Stop();
-            //    return Ok(JsonView(true, $"操作成功!耗时:{stopwatch.ElapsedMilliseconds}ms", result, result.Count));
-            //}
-
-            //// 原生SQL查询
-            //var results = await SearchWithNativeSql(searchAnalysis);
-            //var results1 = CalculateUnifiedMatchScore(results, searchAnalysis);
-            //stopwatch.Stop();
-            //return Ok(JsonView(true, $"搜索成功!耗时:{stopwatch.ElapsedMilliseconds}ms", results1, results1.Count));
-            #endregion
-
         }
 
-        #region 搜索辅助方法
-
-        private List<GroupInfoWithScore> CalculateUnifiedMatchScore(List<GroupInfoWithScore> results, SearchAnalysis analysis)
-        {
-            return results.Select(info => new
-            {
-                Info = info,
-                Score = CalculateComprehensiveMatchScore(info, analysis)
-            })
-            .Where(x => x.Score > 0)
-            .OrderByDescending(x => x.Score)
-            //.ThenByDescending(x => x.Info.CreateTime)
-            .Select(x =>
-            {
-                x.Info.MatchScore = x.Score;
-                return x.Info;
-            })
-            .ToList();
-        }
-
-        private int CalculateComprehensiveMatchScore(GroupInfoWithScore info, SearchAnalysis analysis)
+        /// <summary>
+        ///  接团信息列表 关键字输入提示(智能版)
+        /// </summary>
+        /// <param name="keyword">关键字</param>
+        /// <returns></returns>
+        [HttpGet]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> GroupItemKeywordSearch(string keyword)
         {
-            int totalScore = 0;
-
-            var targetFields = new[]
-            {
-                new { Value = info.TeamName, Weight = 10 },
-                new { Value = info.ClientUnit, Weight = 8 },
-                new { Value = info.ClientName, Weight = 6 }
-            };
-
-            // 关键值匹配得分
-            foreach (var field in targetFields)
+            try
             {
-                foreach (var keyValue in analysis.KeyValues)
+                // 验证请求参数
+                if (string.IsNullOrEmpty(keyword))
                 {
-                    totalScore += CalculateFieldMatchScore(field.Value, keyValue, field.Weight);
+                    return Ok(JsonView(true, $"暂无数据!"));
                 }
-            }
 
-            // 符号分割关键字匹配得分
-            foreach (var field in targetFields)
-            {
-                foreach (var segment in analysis.SymbolSegments)
+                var searchRequest = new DynamicSearchRequest
                 {
-                    var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
-                    if (!string.IsNullOrEmpty(cleanSegment))
+                    Keyword = keyword,
+                    RequireAllSingleChars = true,
+                    PageIndex = 1,
+                    PageSize = 999999,
+                    FieldWeights = new Dictionary<string, int>
                     {
-                        totalScore += CalculateFieldMatchScore(field.Value, cleanSegment, field.Weight);
-                    }
-                }
-            }
-
-            // 单字匹配得分
-            foreach (var field in targetFields)
-            {
-                foreach (var singleChar in analysis.SingleChars)
-                {
-                    totalScore += CalculateSingleCharScore(field.Value, singleChar, (int)(field.Weight * 0.3));
-                }
-            }
-
-            return totalScore;
-        }
-
-        private int CalculateFieldMatchScore(string fieldValue, string keyword, int baseWeight)
-        {
-            if (string.IsNullOrEmpty(fieldValue) || string.IsNullOrEmpty(keyword))
-                return 0;
-
-            if (fieldValue.Contains(keyword))
-            {
-                int score = baseWeight;
-
-                if (fieldValue.Equals(keyword))
-                    score += 15;
-                else if (fieldValue.StartsWith(keyword))
-                    score += 10;
-                else if (fieldValue.EndsWith(keyword))
-                    score += 5;
-
-                return score;
-            }
-
-            return 0;
-        }
-
-        private int CalculateSingleCharScore(string fieldValue, char singleChar, int baseWeight)
-        {
-            if (string.IsNullOrEmpty(fieldValue))
-                return 0;
-
-            int count = fieldValue.Count(c => c == singleChar);
-            int score = count * baseWeight;
-
-            if (fieldValue.Length > 0 && fieldValue[0] == singleChar)
-            {
-                score += baseWeight * 2;
-            }
-
-            return score;
-        }
-
-        /// <summary>
-        /// 分析搜索模式 - 关键值查询改为单字查询
-        /// </summary>
-        private SearchAnalysis AnalyzeSearchPattern(string keyword)
-        {
-            var analysis = new SearchAnalysis { OriginalKeyword = keyword };
-
-            // 1. 检查是否包含特殊符号
-            if (ContainsSpecialSymbols(keyword))
-            {
-                analysis.HasSpecialSymbols = true;
-                analysis.SymbolSegments = SplitBySpecialSymbols(keyword);
-            }
+                        { "TeamName", 10 },
+                        //{ "ClientUnit", 8 },
+                        //{ "ClientName", 6 }
+                    },
+                    Filters = new List<SearchFilter>()
+                    {
+                        new(){Field = "IsDel",Operator="eq",Value="0" }
+                    },
+                    OrderBy = "TeamName",
+                    ReturnFields = new List<string>() { "TeamName", "ClientUnit", "ClientName" }
+                };
 
-            // 2. 将所有输入字符都作为单字处理
-            var cleanKeyword = Regex.Replace(keyword, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
+                // 验证字段配置
+                var validation = _groupSearchService.ValidateFieldConfig(
+                    searchRequest.FieldWeights,
+                    searchRequest.ReturnFields);
 
-            if (!string.IsNullOrEmpty(cleanKeyword))
-            {
-                // 将所有字符都作为单字处理
-                foreach (char c in cleanKeyword)
+                if (!validation.IsValid)
                 {
-                    analysis.SingleChars.Add(c);
+                    return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
                 }
 
-                analysis.IsSingleChar = true;
+                var result = await _groupSearchService.SearchAsync(searchRequest);
 
-                // 同时将长度>1的连续字符也作为符号分割段处理(为了兼容性)
-                if (cleanKeyword.Length > 1 && !analysis.HasSpecialSymbols)
+                if (result.Success)
                 {
-                    analysis.SymbolSegments.Add(cleanKeyword);
-                }
-            }
+                    var data = new List<dynamic>();
 
-            // 3. 如果包含符号,也从分割结果中提取单字
-            if (analysis.HasSpecialSymbols)
-            {
-                foreach (var segment in analysis.SymbolSegments)
-                {
-                    var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
-                    if (!string.IsNullOrEmpty(cleanSegment))
+                    foreach (var item in result.Items)
                     {
-                        // 将分割段中的每个字符都作为单字
-                        foreach (char c in cleanSegment)
+                        data.Add(new
                         {
-                            if (!analysis.SingleChars.Contains(c))
-                            {
-                                analysis.SingleChars.Add(c);
-                            }
-                        }
+                            item.Data.TeamName,
+                            item.Data.ClientUnit,
+                            item.Data.ClientName
+                        });
                     }
+                    
+                    return Ok(JsonView(true, result.Message, data, data.Count));
                 }
-            }
 
-            // 去重
-            analysis.SingleChars = analysis.SingleChars.Distinct().ToList();
-            analysis.SymbolSegments = analysis.SymbolSegments.Distinct().ToList();
-
-            return analysis;
-        }
-
-        private bool ContainsSpecialSymbols(string keyword)
-        {
-            var specialSymbols = new[] { ' ', ',', ',', ';', ';', '、', '/', '\\', '|', '-', '_' };
-            return keyword.Any(c => specialSymbols.Contains(c));
-        }
-
-        private List<string> SplitBySpecialSymbols(string keyword)
-        {
-            var separators = new[] { ' ', ',', ',', ';', ';', '、', '/', '\\', '|', '-', '_' };
-            return keyword.Split(separators, StringSplitOptions.RemoveEmptyEntries)
-                         .Select(s => s.Trim())
-                         .Where(s => !string.IsNullOrEmpty(s))
-                         .ToList();
-        }
-
-        /// <summary>
-        /// 使用原生SQL条件(最稳定)
-        /// </summary>
-        private async Task<List<GroupInfoWithScore>> SearchWithNativeSql(SearchAnalysis analysis)
-        {
-            var whereConditions = new List<string>();
-            var parameters = new List<SugarParameter>();
-
-            // 构建关键值条件
-            foreach (var keyValue in analysis.KeyValues)
-            {
-                whereConditions.Add($"(TeamName LIKE @keyValue{parameters.Count} OR ClientUnit LIKE @keyValue{parameters.Count} OR ClientName LIKE @keyValue{parameters.Count})");
-                parameters.Add(new SugarParameter($"@keyValue{parameters.Count}", $"%{keyValue}%"));
-            }
-
-            // 构建符号分割条件
-            foreach (var segment in analysis.SymbolSegments)
-            {
-                var cleanSegment = Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", "");
-                if (!string.IsNullOrEmpty(cleanSegment))
-                {
-                    whereConditions.Add($"(TeamName LIKE @segment{parameters.Count} OR ClientUnit LIKE @segment{parameters.Count} OR ClientName LIKE @segment{parameters.Count})");
-                    parameters.Add(new SugarParameter($"@segment{parameters.Count}", $"%{cleanSegment}%"));
-                }
+                return Ok(JsonView(true, result.Message));
             }
-
-            // 构建单字条件
-            foreach (var singleChar in analysis.SingleChars)
+            catch (Exception ex)
             {
-                var charStr = singleChar.ToString();
-                whereConditions.Add($"(TeamName LIKE @char{parameters.Count} OR ClientUnit LIKE @char{parameters.Count} OR ClientName LIKE @char{parameters.Count})");
-                parameters.Add(new SugarParameter($"@char{parameters.Count}", $"%{charStr}%"));
+                return Ok(JsonView(true, $"搜索服务暂时不可用!"));
             }
 
-            // 构建SQL
-            var whereClause = whereConditions.Any()
-                ? "AND (" + string.Join(" OR ", whereConditions) + ")"
-                : "";
-
-            var sql = $@"
-            SELECT * FROM Grp_DelegationInfo 
-            WHERE IsDel = 0 {whereClause}
-            ORDER BY CreateTime DESC";
-
-            var result = await _sqlSugar.Ado.SqlQueryAsync<Grp_DelegationInfo>(sql, parameters);
-
-            return result == null 
-                ? new List<GroupInfoWithScore>() 
-                : result.Select(x => new GroupInfoWithScore() { Id = x.Id, TeamName = x.TeamName, ClientUnit = x.ClientUnit, ClientName = x.ClientName }).ToList();
-        }
-
-        /// <summary>
-        /// 获取默认结果
-        /// </summary>
-        private async Task<List<GroupInfoWithScore>> GetDefaultResultsAsync()
-        {
-            return await _sqlSugar.Queryable<Grp_DelegationInfo>()
-                           .Where(x => x.IsDel == 1)
-                           .OrderByDescending(x => x.CreateTime)
-                           .Select(x => new GroupInfoWithScore() { Id = x.Id, TeamName = x.TeamName, ClientUnit = x.ClientUnit, ClientName = x.ClientName })
-                           .ToListAsync();
-        }
-
-        private class GroupInfoWithScore
-        {
-            public int Id { get; set; }
-            public string TeamName { get; set; }
-            public string ClientUnit { get; set; }
-            public string ClientName { get; set; }
-            public int MatchScore { get; set; }
-        }
-
-        /// <summary>
-        /// 搜索分析结果
-        /// </summary>
-        public class SearchAnalysis
-        {
-            /// <summary>
-            /// 原始关键词
-            /// </summary>
-            public string OriginalKeyword { get; set; }
-
-            /// <summary>
-            /// 是否有关键值
-            /// </summary>
-            public bool HasKeyValue { get; set; }
-
-            /// <summary>
-            /// 关键值列表
-            /// </summary>
-            public List<string> KeyValues { get; set; } = new List<string>();
-
-            /// <summary>
-            /// 是否有特殊符号
-            /// </summary>
-            public bool HasSpecialSymbols { get; set; }
-
-            /// <summary>
-            /// 符号分割的段
-            /// </summary>
-            public List<string> SymbolSegments { get; set; } = new List<string>();
-
-            /// <summary>
-            /// 是否是单字
-            /// </summary>
-            public bool IsSingleChar { get; set; }
-
-            /// <summary>
-            /// 单字列表
-            /// </summary>
-            public List<char> SingleChars { get; set; } = new List<char>();
-
-            /// <summary>
-            /// 是否有搜索内容
-            /// </summary>
-            public bool HasSearchContent => HasKeyValue || HasSpecialSymbols || IsSingleChar;
         }
 
-        #endregion
-
         /// <summary>
         ///  接团信息列表 Page
         /// </summary>

+ 5 - 0
OASystem/OASystem.Api/OAMethodLib/GenericSearch/DynamicSearchRequest.cs

@@ -45,6 +45,11 @@
         /// 页大小
         /// </summary>
         public int PageSize { get; set; } = 20;
+
+        /// <summary>
+        /// 是否要求所有单字必须完全出现(新增参数)
+        /// </summary>
+        public bool RequireAllSingleChars { get; set; } = false;
     }
 
     /// <summary>

+ 158 - 36
OASystem/OASystem.Api/OAMethodLib/GenericSearch/DynamicSearchService.cs

@@ -1,5 +1,6 @@
 using SqlSugar;
 using System.Diagnostics;
+using System.DirectoryServices;
 using System.Linq;
 using System.Linq.Expressions;
 
@@ -28,12 +29,11 @@ namespace OASystem.API.OAMethodLib.GenericSearch
         /// <returns>包含搜索结果和匹配度信息的结果对象</returns>
         public async Task<SearchResult<T>> SearchAsync(DynamicSearchRequest request)
         {
+            var resultView = new SearchResult<T>() { Success = false, Message = "异常错误" };
+
             var stopwatch = Stopwatch.StartNew();
             var searchId = Guid.NewGuid().ToString("N")[..8];
 
-            _logger.LogInformation("【{SearchId}】开始动态搜索: 实体{Entity}, 关键词'{Keyword}'",
-                searchId, typeof(T).Name, request.Keyword);
-
             try
             {
                 List<T> data;
@@ -59,11 +59,9 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
                 stopwatch.Stop();
 
-                _logger.LogInformation("【{SearchId}】动态搜索完成: 找到 {Count} 条记录, 耗时 {TotalTime}ms",
-                    searchId, scoredItems.Count, stopwatch.ElapsedMilliseconds);
-
                 return new SearchResult<T>
                 {
+                    Message = $"搜索成功!耗时:{stopwatch.ElapsedMilliseconds}ms",
                     Items = scoredItems,
                     TotalCount = totalCount,
                     Keyword = request.Keyword,
@@ -78,8 +76,8 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             catch (Exception ex)
             {
                 stopwatch.Stop();
-                _logger.LogError(ex, "【{SearchId}】动态搜索失败: {ErrorMessage}", searchId, ex.Message);
-                throw;
+                resultView.Message = string.Format("【{SearchId}】动态搜索失败: {ErrorMessage}", searchId, ex.Message);
+                return resultView;
             }
         }
 
@@ -328,7 +326,9 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             // 构建搜索条件
             if (!string.IsNullOrWhiteSpace(request.Keyword))
             {
+                #region and 构建
                 var searchAnalysis = AnalyzeSearchPattern(request.Keyword);
+                var keywordConditions = new List<string>(); // 专门存放关键字相关条件
 
                 // 符号分割的关键字条件
                 foreach (var segment in searchAnalysis.SymbolSegments)
@@ -342,7 +342,8 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                             parameters.Add(new SugarParameter(paramName, $"%{cleanSegment}%"));
                             return $"{field} LIKE {paramName}";
                         });
-                        whereConditions.Add($"({string.Join(" OR ", fieldConditions)})");
+                        // 每个片段内部使用 OR 连接不同字段
+                        keywordConditions.Add($"({string.Join(" OR ", fieldConditions)})");
                     }
                 }
 
@@ -356,8 +357,60 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                         parameters.Add(new SugarParameter(paramName, $"%{charStr}%"));
                         return $"{field} LIKE {paramName}";
                     });
-                    whereConditions.Add($"({string.Join(" OR ", fieldConditions)})");
+                    // 每个单字内部使用 OR 连接不同字段
+                    keywordConditions.Add($"({string.Join(" OR ", fieldConditions)})");
                 }
+
+                // 所有关键字条件使用 OR 连接
+                if (keywordConditions.Any())
+                {
+                    whereConditions.Add($"({string.Join(" OR ", keywordConditions)})");
+                }
+                #endregion
+
+                #region or 构建
+                //var searchAnalysis = AnalyzeSearchPattern(request.Keyword);
+                //var keywordConditions = new List<string>();
+
+                //// 统一处理所有搜索词
+                //var allSearchTerms = new List<string>();
+
+                //// 添加符号分割的片段(去除特殊字符)
+                //allSearchTerms.AddRange(searchAnalysis.SymbolSegments
+                //    .Select(segment => Regex.Replace(segment, @"[^\u4e00-\u9fa5a-zA-Z0-9]", ""))
+                //    .Where(segment => !string.IsNullOrEmpty(segment)));
+
+                //// 添加单字(排除重复)
+                //foreach (var singleChar in searchAnalysis.SingleChars)
+                //{
+                //    var charStr = singleChar.ToString();
+                //    // 只有当单字不在任何符号片段中时才添加
+                //    if (!allSearchTerms.Any(term => term.Contains(charStr)))
+                //    {
+                //        allSearchTerms.Add(charStr);
+                //    }
+                //}
+
+                //// 处理每个搜索词
+                //foreach (var term in allSearchTerms.Distinct())
+                //{
+                //    var fieldConditions = validFields.Select(field =>
+                //    {
+                //        var paramName = $"@term{parameters.Count}";
+                //        parameters.Add(new SugarParameter(paramName, $"%{term}%"));
+                //        return $"{field} LIKE {paramName}";
+                //    });
+                //    keywordConditions.Add($"({string.Join(" OR ", fieldConditions)})");
+                //}
+
+                //// 所有搜索条件使用 AND 连接
+                //if (keywordConditions.Any())
+                //{
+                //    whereConditions.Add($"({string.Join(" AND ", keywordConditions)})");
+                //}
+
+
+                #endregion
             }
 
             // 构建过滤条件
@@ -373,6 +426,9 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
             var tableName = _db.EntityMaintenance.GetTableName(typeof(T));
 
+            // 构建返回字段
+            //var returnFields = BuildReturnFields(request.ReturnFields);
+
             // 先查询总数
             var countSql = $"SELECT COUNT(1) FROM {tableName} {whereClause}";
             var totalCount = await _db.Ado.GetIntAsync(countSql, parameters);
@@ -477,8 +533,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
         #endregion
 
-        #region 私有方法 - 匹配度计算
-
+        #region 私有方法 - 匹配度计算含出现位置权重(单字检索时全部出现)
         /// <summary>
         /// 在应用层计算匹配度
         /// </summary>
@@ -500,7 +555,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
 
             var scoredItems = data.Select(item =>
             {
-                var matchResult = CalculateItemMatchScore(item, searchAnalysis, searchFields, fieldWeights);
+                var matchResult = CalculateItemMatchScore(item, searchAnalysis, searchFields, fieldWeights, request.RequireAllSingleChars);
 
                 return new SearchResultItem<T>
                 {
@@ -524,11 +579,24 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             T item,
             SearchAnalysis analysis,
             List<string> searchFields,
-            Dictionary<string, int> fieldWeights)
+            Dictionary<string, int> fieldWeights,
+            bool requireAllSingleChars)
         {
             int totalScore = 0;
             var matchFields = new List<MatchFieldInfo>();
 
+            // 新增:根据参数检查是否所有单字都出现
+            if (requireAllSingleChars && analysis.SingleChars.Any())
+            {
+                bool allSingleCharsExist = CheckAllSingleCharsExist(item, analysis.SingleChars, searchFields);
+
+                // 如果要求所有单字必须出现但未完全匹配,直接返回0分
+                if (!allSingleCharsExist)
+                {
+                    return (0, matchFields);
+                }
+            }
+
             foreach (var field in searchFields)
             {
                 var fieldValue = GetFieldValue(item, field);
@@ -571,7 +639,7 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                     }
                 }
 
-                // 单字匹配
+                // 单字匹配 - 加入位置权重计算
                 foreach (var singleChar in analysis.SingleChars)
                 {
                     int count = fieldValue.Count(c => c == singleChar);
@@ -579,16 +647,28 @@ namespace OASystem.API.OAMethodLib.GenericSearch
                     {
                         int charScore = count * (int)(weight * 0.3);
 
+                        // 计算位置权重奖励
+                        var positionBonus = CalculateSingleCharPositionBonus(fieldValue, singleChar, weight);
+                        charScore += positionBonus.Bonus;
+
                         if (fieldValue.StartsWith(singleChar.ToString()))
                         {
                             charScore += weight;
-                            fieldMatchReasons.Add($"开头单字 '{singleChar}'");
+                            fieldMatchReasons.Add($"开头单字 '{singleChar}' +{weight}");
                         }
-                        else
+                        else if (positionBonus.Bonus > 0)
                         {
-                            fieldMatchReasons.Add($"包含单字 '{singleChar}'({count}次)");
+                            fieldMatchReasons.Add($"靠前单字 '{singleChar}' +{positionBonus.Bonus}");
                         }
 
+                        // 添加位置信息到原因
+                        if (positionBonus.FirstPosition >= 0)
+                        {
+                            fieldMatchReasons.Add($"位置{positionBonus.FirstPosition + 1}");
+                        }
+
+                        fieldMatchReasons.Add($"包含单字 '{singleChar}'({count}次)");
+
                         fieldScore += charScore;
                     }
                 }
@@ -613,15 +693,68 @@ namespace OASystem.API.OAMethodLib.GenericSearch
         }
 
         /// <summary>
-        /// 获取显示用的字段值(截断过长的内容)
+        /// 新增:检查所有单字是否在任意字段中出现
         /// </summary>
-        private string GetDisplayFieldValue(string fieldValue)
+        private bool CheckAllSingleCharsExist(T item, List<char> singleChars, List<string> searchFields)
         {
-            if (string.IsNullOrEmpty(fieldValue))
+            foreach (var singleChar in singleChars)
+            {
+                bool charExists = false;
+
+                // 检查所有搜索字段中是否包含该单字
+                foreach (var field in searchFields)
+                {
+                    var fieldValue = GetFieldValue(item, field);
+                    if (!string.IsNullOrEmpty(fieldValue) && fieldValue.Contains(singleChar))
+                    {
+                        charExists = true;
+                        break;
+                    }
+                }
+
+                // 如果有一个单字不存在,直接返回false
+                if (!charExists)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 计算单字位置权重奖励
+        /// </summary>
+        private (int Bonus, int FirstPosition) CalculateSingleCharPositionBonus(string fieldValue, char singleChar, int baseWeight)
+        {
+            var firstPosition = fieldValue.IndexOf(singleChar);
+            if (firstPosition == -1)
+                return (0, -1);
+
+            // 计算位置比例 (0-1),0表示开头,1表示结尾
+            double positionRatio = (double)firstPosition / Math.Max(fieldValue.Length - 1, 1);
+
+            // 位置权重系数:位置越靠前,奖励越高
+            double positionFactor = 1.0 - positionRatio;
+
+            // 计算奖励分数(基于基础权重)
+            int positionBonus = (int)(baseWeight * positionFactor * 0.8); // 最大奖励为基础权重的80%
+
+            // 确保奖励至少为1
+            positionBonus = Math.Max(positionBonus, 1);
+
+            return (positionBonus, firstPosition);
+        }
+
+        /// <summary>
+        /// 获取显示字段值(截断过长的值)
+        /// </summary>
+        private string GetDisplayFieldValue(string fieldValue, int maxLength = 50)
+        {
+            if (string.IsNullOrEmpty(fieldValue) || fieldValue.Length <= maxLength)
                 return fieldValue;
 
-            // 如果字段值过长,截断显示
-            return fieldValue.Length > 50 ? fieldValue.Substring(0, 50) + "..." : fieldValue;
+            return fieldValue.Substring(0, maxLength) + "...";
         }
 
         #endregion
@@ -798,7 +931,8 @@ namespace OASystem.API.OAMethodLib.GenericSearch
         private string BuildNativeOrderByClause(string orderBy, bool isDescending)
         {
             if (string.IsNullOrWhiteSpace(orderBy))
-                return "ORDER BY Id DESC";
+                //return "ORDER BY Id DESC";
+                return "";
 
             return $"ORDER BY {orderBy} {(isDescending ? "DESC" : "ASC")}";
         }
@@ -894,18 +1028,6 @@ namespace OASystem.API.OAMethodLib.GenericSearch
             return weights;
         }
 
-        /// <summary>
-        /// 验证字段有效性
-        /// </summary>
-        private List<string> ValidateFields(List<string> fields)
-        {
-            var validFields = typeof(T).GetProperties()
-                .Select(p => p.Name)
-                .ToList();
-
-            return fields.Intersect(validFields).ToList();
-        }
-
         /// <summary>
         /// 验证搜索字段
         /// </summary>

+ 33 - 3
OASystem/OASystem.Api/Program.cs

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.ResponseCompression;
 using Microsoft.AspNetCore.Server.Kestrel.Core;
 using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.IdentityModel.Tokens;
 using OASystem.API.Middlewares;
 using OASystem.API.OAMethodLib;
 using OASystem.API.OAMethodLib.AMapApi;
@@ -210,21 +211,50 @@ builder.Services.AddScoped(options =>
         db.Aop.OnLogExecuting = (sql, pars) =>
         {
             //获取原生SQL推荐 5.1.4.63  性能OK
-            //UtilMethods.GetNativeSql(sql, pars);
+            UtilMethods.GetNativeSql(sql, pars);
 
             //获取无参数化SQL 影响性能只适合调试
-            //UtilMethods.GetSqlString(DbType.SqlServer,sql,pars)
+            //UtilMethods.GetSqlString(DbType.SqlServer, sql, pars);
+
+           
+            if (pars != null || pars.Length > 0)
+            {
+                Console.WriteLine("============== 完整 SQL ==============");
+                var resultSql = sql;
+
+                foreach (var param in pars)
+                {
+                    var par = param.Value;
+                    if (par != null)
+                    {
+                        var formattedValue = par switch
+                        {
+                            string str => $"'{str.Replace("'", "''")}'",
+                            DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss}'",
+                            bool b => b ? "1" : "0",
+                            _ => par.ToString()
+                        };
+                        resultSql = resultSql.Replace(param.ParameterName, formattedValue);
+                    }
+                    
+                }
+                Console.WriteLine(resultSql);
+                Console.WriteLine("=====================================");
+            }
         };
         //SQL报错
         db.Aop.OnError = (exp) =>
         {
             //获取原生SQL推荐 5.1.4.63  性能OK
-            //UtilMethods.GetNativeSql(exp.sql, exp.parameters);
+            //UtilMethods.GetNativeSql(exp.Sql, exp.Parametres);
 
             //获取无参数SQL对性能有影响,特别大的SQL参数多的,调试使用
             //UtilMethods.GetSqlString(DbType.SqlServer, exp.sql, exp.parameters);
 
 
+
+
+
         };
         //修改SQL和参数的值
         db.Aop.OnExecutingChangeSql = (sql, pars) =>

+ 208 - 34
OASystem/OASystem.Domain/Entities/Groups/Grp_GroupCostParameter.cs

@@ -1,45 +1,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace OASystem.Domain.Entities.Groups
+namespace OASystem.Domain.Entities.Groups
 {
     /// <summary>
     /// 团组成本预算表
     /// </summary>
     [SugarTable("Grp_GroupCostParameter")]
-    public class Grp_GroupCostParameter:EntityBase
+    public class Grp_GroupCostParameter : EntityBase
     {
         /// <summary>
         /// 团组ID
         /// </summary>
-        [SugarColumn(IsNullable =true,ColumnDataType ="int")]
+        [SugarColumn(IsNullable = true, ColumnDataType = "int")]
         public int DiId { get; set; }
         /// <summary>
         /// 币种
@@ -322,8 +292,212 @@ namespace OASystem.Domain.Entities.Groups
         public int TDCRS { get; set; }
     }
 
-    public class Grp_GroupCostParameterView: Grp_GroupCostParameter
+    public class Grp_GroupCostParameterView : Grp_GroupCostParameter
+    {
+
+    }
+
+    /// <summary>
+    /// 费用计算
+    /// </summary>
+    public class GroupCostCalculate : Grp_GroupCostParameter
+    {
+        #region 各项费用计算 单人报价、总报价
+        /// <summary>
+        /// 签证单人报价
+        /// </summary>
+        public decimal VisaSinglePrice { get { return VisaCB * VisaXS; } }
+        /// <summary>
+        /// 保险单人报价
+        /// </summary>
+        public decimal PolicySinglePrice { get { return BXCB * BXXS; } }
+
+        /// <summary>
+        /// 火车票单人报价
+        /// </summary>
+        public decimal TicketSinglePrice { get { return HCPCB * HCPXS; } }
+
+        /// <summary>
+        /// 机票经济舱单人报价
+        /// </summary>
+        public decimal EconomySinglePrice { get { return JJCCB * JJCXS; } }
+
+        /// <summary>
+        /// 机票公务舱单人报价
+        /// </summary>
+        public decimal BusinessSinglePrice { get { return GWCCB * GWCXS; } }
+
+        /// <summary>
+        /// 机票头等舱单人报价
+        /// </summary>
+        public decimal FirstClassSinglePrice { get { return TDCCB * TDCXS; } }
+
+        /// <summary>
+        /// 船票单人报价
+        /// </summary>
+        public decimal FerryTicketSinglePrice { get { return CPCB * CPXS; } }
+
+        /// <summary>
+        /// 核酸检测单人报价
+        /// </summary>
+        public decimal NucleicAcidTesSinglePrice { get { return HSCB * HSXS; } }
+
+        /// <summary>
+        /// 酒店TBR 单人报价
+        /// </summary>
+        public decimal HotelTBRSinglePrice { get { return TBRCB * TBRXS; } }
+
+        /// <summary>
+        /// 酒店SGR 单人报价
+        /// </summary>
+        public decimal HotelSGRSinglePrice { get { return SGRCB * SGRXS; } }
+
+        /// <summary>
+        /// 酒店JSES 单人报价
+        /// </summary>
+        public decimal HotelJSESSinglePrice { get { return JSESCB * JSESXS; } }
+
+        /// <summary>
+        /// 酒店Suite 单人报价
+        /// </summary>
+        public decimal HotelSuiteSinglePrice { get { return SUITECB * SUITEXS; } }
+
+        /// <summary>
+        /// 地接 单人报价
+        /// </summary>
+        public decimal GroundSinglePrice { get { return DJCB * DJXS; } }
+
+        /// <summary>
+        /// 公务 单人报价
+        /// </summary>
+        public decimal BusinessActivitySinglePrice { get { return GWCB * GWXS; } }
+
+        /// <summary>
+        /// 零用金 单人报价
+        /// </summary>
+        public decimal PettyCashSinglePrice { get { return LYJCB * LYJXS; } }
+        #endregion
+
+        #region 费用组合计算 单人报价、总报价
+        /// <summary>
+        /// 费用集合
+        /// </summary>
+        public List<ChildFeeInfo> ChildFeeInfos
+        {
+            get
+            {
+                var list = new List<ChildFeeInfo>();
+                var otherSinglePrice = VisaSinglePrice + PolicySinglePrice + TicketSinglePrice + FerryTicketSinglePrice + NucleicAcidTesSinglePrice + GroundSinglePrice + BusinessActivitySinglePrice + PettyCashSinglePrice;
+                //经济舱 TBR、经济舱 SGR、经济舱 JS/ES、经济舱 SUITE、公务舱 TBR、公务舱 SGR、公务舱 JS/ES、公务舱 SUITE、头等舱 JS/ES、头等舱 SUITE
+                if (JJCRS > 0)
+                {
+                    if (TBRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("经济舱 TBR", EconomySinglePrice + HotelTBRSinglePrice + otherSinglePrice, JJCRS));
+                    }
+                    else if (SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("经济舱 SGR", EconomySinglePrice + HotelSGRSinglePrice + otherSinglePrice, JJCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("经济舱 JS/ES", EconomySinglePrice + HotelJSESSinglePrice + otherSinglePrice, JJCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("经济舱 SUITE", EconomySinglePrice + HotelSuiteSinglePrice + otherSinglePrice, JJCRS));
+                    }
+                }
+
+                if (GWCRS > 0)
+                {
+                    if (TBRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("公务舱 TBR", BusinessSinglePrice + HotelTBRSinglePrice + otherSinglePrice, GWCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("公务舱 SGR", BusinessSinglePrice + HotelSGRSinglePrice + otherSinglePrice, GWCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("公务舱 JS/ES", BusinessSinglePrice + HotelJSESSinglePrice + otherSinglePrice, GWCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("公务舱 SUITE", BusinessSinglePrice + HotelSuiteSinglePrice + otherSinglePrice, GWCRS));
+                    }
+                }
+
+                if (TDCRS > 0)
+                {
+                    if (SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("头等舱 JS/ES", FirstClassSinglePrice + HotelJSESSinglePrice + otherSinglePrice, TDCRS));
+                    }
+                    else if(SGRNumber > 0)
+                    {
+                        list.Add(new ChildFeeInfo("头等舱 SUITE", FirstClassSinglePrice + HotelSuiteSinglePrice + otherSinglePrice, TDCRS));
+                    }
+                }
+
+                return list;
+            }
+        }
+
+        #endregion
+
+        public decimal GroupTotalPrice { get { return ChildFeeInfos.Sum(x => x.TotalPrice); } }
+
+    }
+
+    public class ChildFeeInfo
     {
+        /// <summary>
+        /// 费用类型名称
+        /// </summary>
+        public string TypeName { get; set; }
+
+        /// <summary>
+        /// 单人报价
+        /// </summary>
+        public decimal SinglePrice { get; set; }
 
+        /// <summary>
+        /// 总人数
+        /// </summary>
+        public int TotalPeople { get; set; }
+
+        /// <summary>
+        /// 总报价
+        /// </summary>
+        public decimal TotalPrice { get; set; }
+
+        public ChildFeeInfo() { }
+
+        /// <summary>
+        /// 重构
+        /// </summary>
+        /// <param name="typeName">类型名称</param>
+        /// <param name="singlePrice">单人报价</param>
+        /// <param name="totalPeople">人数</param>
+        public ChildFeeInfo(string typeName, decimal singlePrice, int totalPeople)
+        {
+            TypeName = typeName;
+            SinglePrice = TruncateTo(singlePrice, 2);
+            TotalPeople = totalPeople;
+            TotalPrice = TruncateTo(SinglePrice * totalPeople, 2);
+        }
+
+        /// <summary>
+        /// 截断保留指定位数小数(不四舍五入)
+        /// </summary>
+        public decimal TruncateTo(decimal value, int decimals)
+        {
+            decimal factor = (decimal)Math.Pow(10, decimals);
+            return Math.Truncate(value * factor) / factor;
+        }
     }
+
+    
 }

+ 11 - 0
OASystem/OASystem.Domain/ViewModels/Financial/Fin_ForeignReceivablesView.cs

@@ -1,4 +1,5 @@
 using OASystem.Domain.Entities.Financial;
+using OASystem.Domain.Entities.Groups;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -421,6 +422,16 @@ namespace OASystem.Domain.ViewModels.Financial
         /// 子项明细
         /// </summary>
         public List<ClientFeeInfoView>? feeItem { get; set; }
+
+        /// <summary>
+        /// 团组成本 - 报价
+        /// </summary>
+        public decimal groupCost { get; set; }
+
+        /// <summary>
+        /// 团组成本 - 报价 明细
+        /// </summary>
+        public List<ChildFeeInfo> groupCostItem { get; set; }
     }
 
 

+ 2 - 0
OASystem/OASystem.Infrastructure/Repositories/Groups/GroupCostParameterRepository.cs

@@ -426,5 +426,7 @@ namespace OASystem.Infrastructure.Repositories.Groups
 
             return dynamics;
         }
+    
+        
     }
 }