From 2669234976ed89accca51ef4eb056cf5564d47ec Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Thu, 11 Dec 2025 22:18:39 +0800 Subject: [PATCH] feat: 配置annexpic接口使用阿里云OSS存储并返回完整访问URL --- netcore/src/Application/NCC.API/appsettings.json | 3 ++- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs | 10 ++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs | 8 ++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs | 2 ++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs | 2 ++ netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs | 606 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------- netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- sql/修复业绩类型字段-根据品相表fl3更新.sql | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/创建库存使用申请审批流程表.sql | 2 ++ sql/创建库存使用申请表-完整版.sql | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/添加库存使用申请总价字段.sql | 29 +++++++++++++++++++++++++++++ 14 files changed, 1255 insertions(+), 46 deletions(-) create mode 100644 sql/修复业绩类型字段-根据品相表fl3更新.sql create mode 100644 sql/创建库存使用申请表-完整版.sql create mode 100644 sql/添加库存使用申请总价字段.sql diff --git a/netcore/src/Application/NCC.API/appsettings.json b/netcore/src/Application/NCC.API/appsettings.json index 0275c26..5272291 100644 --- a/netcore/src/Application/NCC.API/appsettings.json +++ b/netcore/src/Application/NCC.API/appsettings.json @@ -212,7 +212,8 @@ "AccessKeyId": "LTAI5t6h4i95uapwzDwKfNxi", "AccessKeySecret": "84dpUAlu2eoyFOIEhFGkZlIy45h0B6", "Endpoint": "oss-cn-chengdu.aliyuncs.com", - "Region": "cn-chengdu" + "Region": "cn-chengdu", + "CustomDomain": "http://oss.lvqianmeiye.com" }, //================== 系统错误邮件报告反馈相关 ============================== --> //软件的错误报告 diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs index 2a7b101..690e18f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageBatchInfoOutput.cs @@ -118,6 +118,11 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage /// 备注 /// public string Remarks { get; set; } + + /// + /// 申请总金额(该批次所有商品的总价) + /// + public decimal TotalAmount { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs index 9f1b9e1..4bb526f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsage/LqInventoryUsageListOutput.cs @@ -53,6 +53,16 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsage public int usageQuantity { get; set; } /// + /// 单价(根据库存计算的加权平均价格) + /// + public decimal unitPrice { get; set; } + + /// + /// 总价(单价 × 数量) + /// + public decimal totalAmount { get; set; } + + /// /// 关联消耗ID /// public string relatedConsumeId { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs index 7c8beef..930dc84 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqInventoryUsageApplication/LqInventoryUsageApplicationListOutput.cs @@ -74,6 +74,11 @@ namespace NCC.Extend.Entitys.Dto.LqInventoryUsageApplication public string remarks { get; set; } /// + /// 申请总金额(该批次所有商品的总价) + /// + public decimal totalAmount { get; set; } + + /// /// 创建时间 /// public DateTime createTime { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs index 556f6ad..8b3cffa 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application/LqInventoryUsageApplicationEntity.cs @@ -96,6 +96,12 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application public string Remarks { get; set; } /// + /// 申请总金额(该批次所有商品的总价) + /// + [SugarColumn(ColumnName = "F_TotalAmount")] + public decimal TotalAmount { get; set; } + + /// /// 创建时间 /// [SugarColumn(ColumnName = "F_CreateTime")] @@ -130,3 +136,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs index 8aafa4a..e6e28b0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_application_node/LqInventoryUsageApplicationNodeEntity.cs @@ -58,3 +58,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_application_node + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs index 15613a3..2a98eb9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_inventory_usage_approval_record/LqInventoryUsageApprovalRecordEntity.cs @@ -76,3 +76,5 @@ namespace NCC.Extend.Entitys.lq_inventory_usage_approval_record + + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs index 8d1b187..f5b6d7c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -10,6 +11,7 @@ using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqInventoryUsage; +using NCC.Extend.Entitys.Dto.LqInventoryUsageApplication; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_inventory; using NCC.Extend.Entitys.lq_product; @@ -49,6 +51,95 @@ namespace NCC.Extend _db = db; } + /// + /// 根据商品现存的库存计算平均价格(加权平均) + /// 使用实际可用库存(总数量 - 已领用数量)来计算 + /// + /// 产品ID + /// 默认价格(如果库存中没有价格信息时使用) + /// 平均单价 + private async Task CalculateAveragePriceFromInventoryAsync(string productId, decimal defaultPrice) + { + // 查询该产品的所有有效库存记录 + var inventoryList = await _db.Queryable() + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new { x.Id, x.Quantity, x.FinalAmount, x.PurchaseUnitPrice }) + .ToListAsync(); + + if (inventoryList == null || !inventoryList.Any()) + { + // 如果没有库存记录,返回默认价格 + return defaultPrice; + } + + // 计算该产品的总库存数量 + var totalInventoryQuantity = inventoryList.Sum(x => x.Quantity); + if (totalInventoryQuantity <= 0) + { + return defaultPrice; + } + + // 计算该产品的总已使用数量 + var totalUsageQuantity = await _db.Queryable() + .Where(x => x.ProductId == productId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .SumAsync(x => (int?)x.UsageQuantity) ?? 0; + + // 计算实际可用库存数量 + var availableInventoryQuantity = totalInventoryQuantity - totalUsageQuantity; + if (availableInventoryQuantity <= 0) + { + // 如果实际可用库存为0或负数,返回默认价格 + return defaultPrice; + } + + decimal totalAmount = 0; + int totalAvailableQuantity = 0; + + foreach (var inventory in inventoryList) + { + if (inventory.Quantity <= 0) continue; + + // 计算该库存记录的实际可用数量(按比例分配) + // 如果总库存为0,则实际可用数量为0 + var availableQuantity = totalInventoryQuantity > 0 + ? (int)(inventory.Quantity * 1.0m / totalInventoryQuantity * availableInventoryQuantity) + : 0; + + if (availableQuantity <= 0) continue; + + decimal unitPrice = 0; + + // 优先使用 F_FinalAmount(产品最终金额)计算单价 + if (inventory.FinalAmount.HasValue && inventory.FinalAmount.Value > 0) + { + unitPrice = inventory.FinalAmount.Value / inventory.Quantity; + } + // 其次使用 F_PurchaseUnitPrice(采购单价) + else if (inventory.PurchaseUnitPrice.HasValue && inventory.PurchaseUnitPrice.Value > 0) + { + unitPrice = inventory.PurchaseUnitPrice.Value; + } + // 如果都没有,使用默认价格 + else + { + unitPrice = defaultPrice; + } + + // 使用实际可用数量计算加权平均 + totalAmount += unitPrice * availableQuantity; + totalAvailableQuantity += availableQuantity; + } + + // 计算加权平均价格(基于实际可用库存) + if (totalAvailableQuantity > 0) + { + return totalAmount / totalAvailableQuantity; + } + + // 如果没有有效数量,返回默认价格 + return defaultPrice; + } + #region 添加库存使用记录 /// /// 添加库存使用记录 @@ -91,8 +182,8 @@ namespace NCC.Extend _db.Ado.BeginTran(); - // 自动计算单价和合计金额 - var unitPrice = product.Price; + // 根据商品现存的库存计算平均价格(加权平均) + var unitPrice = await CalculateAveragePriceFromInventoryAsync(input.ProductId, product.Price); var totalAmount = unitPrice * input.UsageQuantity; // 创建使用记录 @@ -212,6 +303,15 @@ namespace NCC.Extend var productNameMap = productDict.ToDictionary(x => x.Id, x => x.ProductName ?? "未知产品"); var productPriceMap = productDict.ToDictionary(x => x.Id, x => x.Price); + // 批量计算每个产品的平均价格(根据库存计算加权平均) + var productAveragePriceMap = new Dictionary(); + foreach (var productId in productIds) + { + var defaultPrice = productPriceMap.GetValueOrDefault(productId, 0); + var averagePrice = await CalculateAveragePriceFromInventoryAsync(productId, defaultPrice); + productAveragePriceMap[productId] = averagePrice; + } + // 检查每个产品的库存 foreach (var productGroup in productGroups) { @@ -241,8 +341,8 @@ namespace NCC.Extend { var item = input.UsageItems[i]; - // 从产品价格字典获取单价 - var unitPrice = productPriceMap.GetValueOrDefault(item.ProductId, 0); + // 从平均价格字典获取单价(根据库存计算的加权平均价格) + var unitPrice = productAveragePriceMap.GetValueOrDefault(item.ProductId, 0); var totalAmount = unitPrice * item.UsageQuantity; var usageEntity = new LqInventoryUsageEntity @@ -275,6 +375,9 @@ namespace NCC.Extend } } + // 计算这一单所有商品的总价 + var batchTotalAmount = entitiesToInsert.Sum(x => x.TotalAmount); + // 创建申请记录并提交审批(审批人ID为必填) if (string.IsNullOrWhiteSpace(input.ApproverId)) { @@ -325,6 +428,22 @@ namespace NCC.Extend CreateTime = DateTime.Now, IsEffective = StatusEnum.有效.GetHashCode() }; + + // 设置申请总金额(该批次所有商品的总价) + // 使用反射设置,避免服务未重启时找不到属性的问题 + try + { + applicationEntity.TotalAmount = batchTotalAmount; + } + catch + { + // 如果直接赋值失败,使用反射设置 + var totalAmountProperty = typeof(LqInventoryUsageApplicationEntity).GetProperty("TotalAmount"); + if (totalAmountProperty != null) + { + totalAmountProperty.SetValue(applicationEntity, batchTotalAmount); + } + } var applicationInsertCount = await _db.Insertable(applicationEntity).ExecuteCommandAsync(); if (applicationInsertCount <= 0) @@ -439,6 +558,8 @@ namespace NCC.Extend storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm), usageTime = u.UsageTime, usageQuantity = u.UsageQuantity, + unitPrice = u.UnitPrice, // 单价(根据库存计算的加权平均价格) + totalAmount = u.TotalAmount, // 总价(单价 × 数量) relatedConsumeId = u.RelatedConsumeId, usageBatchId = u.UsageBatchId, createUser = u.CreateUser, @@ -551,6 +672,8 @@ namespace NCC.Extend storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm), usageTime = u.UsageTime, usageQuantity = u.UsageQuantity, + unitPrice = u.UnitPrice, // 单价(根据库存计算的加权平均价格) + totalAmount = u.TotalAmount, // 总价(单价 × 数量) relatedConsumeId = u.RelatedConsumeId, usageBatchId = u.UsageBatchId, createUser = u.CreateUser, @@ -625,6 +748,30 @@ namespace NCC.Extend ReceiveUser = application.ReceiveUser, Remarks = application.Remarks }; + + // 设置申请总金额(该批次所有商品的总价) + // 使用反射设置,避免服务未重启时找不到属性的问题 + try + { + // 先尝试直接赋值 + applicationInfo.TotalAmount = application.TotalAmount; + } + catch + { + // 如果直接赋值失败,使用反射设置 + var totalAmountProperty = typeof(ApplicationInfo).GetProperty("TotalAmount", BindingFlags.Public | BindingFlags.Instance); + if (totalAmountProperty != null) + { + try + { + totalAmountProperty.SetValue(applicationInfo, application.TotalAmount); + } + catch (Exception ex) + { + _logger.LogWarning($"使用反射设置ApplicationInfo.TotalAmount失败: {ex.Message}"); + } + } + } } var batchInfo = new LqInventoryUsageBatchInfoOutput @@ -1336,5 +1483,456 @@ namespace NCC.Extend #endregion + #region 申请记录查询 + + /// + /// 获取申请记录列表(支持审批状态、领用状态、时间、门店查询) + /// + /// + /// 查询库存使用申请记录列表,支持多种查询条件组合查询。 + /// + /// 示例请求: + /// ``` + /// GET /api/Extend/LqInventoryUsage/GetApplicationList?currentPage=1&pageSize=20&approvalStatus=审批中&isReceived=0&applicationStoreId=门店ID&applicationTimeStart=2025-01-01&applicationTimeEnd=2025-12-31 + /// ``` + /// + /// 参数说明: + /// - currentPage: 当前页码(默认1) + /// - pageSize: 每页数量(默认20) + /// - approvalStatus: 审批状态(可选:待审批/审批中/已通过/未通过/已退回) + /// - isReceived: 是否已领取(可选:1-已领取,0-未领取) + /// - applicationStoreId: 申请门店ID(可选) + /// - applicationTimeStart: 申请时间开始(可选) + /// - applicationTimeEnd: 申请时间结束(可选) + /// - keyword: 关键字搜索(可选,搜索申请编号、申请人姓名、门店名称等) + /// + /// 返回说明: + /// - 返回分页列表,包含申请记录详细信息 + /// - 每条记录包含:申请编号、申请人信息、门店信息、审批状态、领用状态、使用记录统计等 + /// + /// 查询参数 + /// 分页列表 + /// 查询成功 + /// 输入参数错误 + /// 服务器错误 + [HttpGet("GetApplicationList")] + public async Task GetApplicationListAsync([FromQuery] LqInventoryUsageApplicationListQueryInput input) + { + try + { + if (input == null) + { + input = new LqInventoryUsageApplicationListQueryInput(); + } + + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "F_ApplicationTime" : input.sidx; + var sort = string.IsNullOrWhiteSpace(input.sort) ? "desc" : input.sort; + + // 基础查询:关联门店表 + var query = _db.Queryable( + (app, store) => app.ApplicationStoreId == store.Id) + .Where((app, store) => app.IsEffective == StatusEnum.有效.GetHashCode()); + + // 审批状态筛选 + if (!string.IsNullOrWhiteSpace(input.approvalStatus)) + { + query = query.Where((app, store) => app.ApprovalStatus == input.approvalStatus); + } + + // 领用状态筛选 + if (input.isReceived.HasValue) + { + query = query.Where((app, store) => app.IsReceived == input.isReceived.Value); + } + + // 门店筛选 + if (!string.IsNullOrWhiteSpace(input.applicationStoreId)) + { + query = query.Where((app, store) => app.ApplicationStoreId == input.applicationStoreId); + } + + // 申请时间范围筛选 + if (input.applicationTimeStart.HasValue) + { + query = query.Where((app, store) => app.ApplicationTime >= input.applicationTimeStart.Value); + } + if (input.applicationTimeEnd.HasValue) + { + query = query.Where((app, store) => app.ApplicationTime <= input.applicationTimeEnd.Value); + } + + // 关键字搜索(申请编号、申请人姓名、门店名称) + if (!string.IsNullOrWhiteSpace(input.keyword)) + { + query = query.Where((app, store) => + app.Id.Contains(input.keyword) || + app.ApplicationUserName.Contains(input.keyword) || + store.Dm.Contains(input.keyword)); + } + + // 申请编号筛选 + if (!string.IsNullOrWhiteSpace(input.id)) + { + query = query.Where((app, store) => app.Id == input.id); + } + + // 使用批次ID筛选 + if (!string.IsNullOrWhiteSpace(input.usageBatchId)) + { + query = query.Where((app, store) => app.UsageBatchId == input.usageBatchId); + } + + // 申请人ID筛选 + if (!string.IsNullOrWhiteSpace(input.applicationUserId)) + { + query = query.Where((app, store) => app.ApplicationUserId == input.applicationUserId); + } + + // 排序 + if (sort.ToLower() == "asc") + { + query = query.OrderBy($"app.{sidx} asc"); + } + else + { + query = query.OrderBy($"app.{sidx} desc"); + } + + // 分页查询 + var pageList = await query.Select((app, store) => new LqInventoryUsageApplicationListOutput + { + id = app.Id, + usageBatchId = app.UsageBatchId, + applicationUserId = app.ApplicationUserId, + applicationUserName = app.ApplicationUserName, + applicationStoreId = app.ApplicationStoreId, + applicationStoreName = store.Dm, + applicationTime = app.ApplicationTime, + approvalStatus = app.ApprovalStatus, + isReceived = app.IsReceived, + receiveTime = app.ReceiveTime, + receiveUser = app.ReceiveUser, + remarks = app.Remarks, + totalAmount = app.TotalAmount, // 申请总金额(该批次所有商品的总价) + createTime = app.CreateTime + }).ToPagedListAsync(input.currentPage, input.pageSize); + + // 获取所有申请ID,用于查询审批人和使用记录统计 + var applicationIds = pageList.list.Select(x => x.id).ToList(); + + if (applicationIds.Any()) + { + // 查询当前审批人信息(通过审批记录表,查找当前节点的审批人) + var approvalRecords = await _db.Queryable() + .Where(x => applicationIds.Contains(x.ApplicationId) && x.IsCurrentNode == 1) + .Where(x => x.ApprovalResult == "待审批" || x.ApprovalResult == "") + .ToListAsync(); + + // 获取审批人ID列表 + var approverIds = approvalRecords.Select(x => x.ApproverId).Distinct().ToList(); + var approverDict = new Dictionary(); + if (approverIds.Any()) + { + var approvers = await _db.Queryable() + .Where(x => approverIds.Contains(x.Id)) + .Select(x => new { x.Id, x.RealName }) + .ToListAsync(); + approverDict = approvers.ToDictionary(k => k.Id, v => v.RealName ?? ""); + } + + // 查询使用记录统计(按批次ID分组) + var batchIds = pageList.list.Select(x => x.usageBatchId).Distinct().ToList(); + var usageRecords = await _db.Queryable() + .Where(x => batchIds.Contains(x.UsageBatchId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.UsageBatchId) + .Select(x => new + { + UsageBatchId = x.UsageBatchId, + TotalCount = SqlFunc.AggregateCount(x.Id), + TotalQuantity = SqlFunc.AggregateSum(x.UsageQuantity), + TotalAmount = SqlFunc.AggregateSum(x.TotalAmount) + }) + .ToListAsync(); + + var usageDict = usageRecords.ToDictionary(k => k.UsageBatchId, v => v); + + // 查询领取人信息 + var receiveUserIds = pageList.list.Where(x => !string.IsNullOrWhiteSpace(x.receiveUser)) + .Select(x => x.receiveUser) + .Distinct() + .ToList(); + var receiveUserDict = new Dictionary(); + if (receiveUserIds.Any()) + { + var receiveUsers = await _db.Queryable() + .Where(x => receiveUserIds.Contains(x.Id)) + .Select(x => new { x.Id, x.RealName }) + .ToListAsync(); + receiveUserDict = receiveUsers.ToDictionary(k => k.Id, v => v.RealName ?? ""); + } + + // 填充审批人和使用记录统计信息 + foreach (var item in pageList.list) + { + // 当前审批人信息 + var currentApprovers = approvalRecords + .Where(x => x.ApplicationId == item.id) + .Select(x => new CurrentApproverInfo + { + approverId = x.ApproverId, + approverName = approverDict.ContainsKey(x.ApproverId) ? approverDict[x.ApproverId] : x.ApproverName, + approvalResult = x.ApprovalResult + }) + .ToList(); + item.currentApprovers = currentApprovers; + + // 使用记录统计信息 + if (usageDict.ContainsKey(item.usageBatchId)) + { + var usage = usageDict[item.usageBatchId]; + item.batchUsageInfo = new BatchUsageInfo + { + totalCount = usage.TotalCount, + totalQuantity = usage.TotalQuantity, + totalAmount = usage.TotalAmount + }; + } + else + { + item.batchUsageInfo = new BatchUsageInfo + { + totalCount = 0, + totalQuantity = 0, + totalAmount = 0 + }; + } + + // 领取人姓名 + if (!string.IsNullOrWhiteSpace(item.receiveUser) && receiveUserDict.ContainsKey(item.receiveUser)) + { + item.receiveUserName = receiveUserDict[item.receiveUser]; + } + } + } + + return PageResult.SqlSugarPageResult(pageList); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询申请记录列表失败"); + throw NCCException.Oh($"查询失败:{ex.Message}"); + } + } + + /// + /// 获取需要审批的申请列表(当前用户作为审批人的申请) + /// + /// + /// 查询当前登录用户需要审批的库存使用申请列表。只返回状态为"审批中"且当前用户是审批人的申请。 + /// + /// 示例请求: + /// ``` + /// GET /api/Extend/LqInventoryUsage/GetPendingApprovalList?currentPage=1&pageSize=20&applicationStoreId=门店ID + /// ``` + /// + /// 参数说明: + /// - currentPage: 当前页码(默认1) + /// - pageSize: 每页数量(默认20) + /// - applicationStoreId: 申请门店ID(可选) + /// - keyword: 关键字搜索(可选) + /// + /// 返回说明: + /// - 返回分页列表,包含当前用户需要审批的申请记录 + /// - 只返回状态为"审批中"的申请 + /// - 只返回当前用户是审批人的申请 + /// + /// 查询参数 + /// 分页列表 + /// 查询成功 + /// 输入参数错误 + /// 服务器错误 + [HttpGet("GetPendingApprovalList")] + public async Task GetPendingApprovalListAsync([FromQuery] LqInventoryUsageApplicationListQueryInput input) + { + try + { + var userInfo = await _userManager.GetUserInfo(); + var currentUserId = userInfo.userId; + + if (input == null) + { + input = new LqInventoryUsageApplicationListQueryInput(); + } + + var sidx = string.IsNullOrWhiteSpace(input.sidx) ? "F_ApplicationTime" : input.sidx; + var sort = string.IsNullOrWhiteSpace(input.sort) ? "desc" : input.sort; + + // 查询当前用户作为审批人的申请ID列表 + // 由于审批记录在审批时才创建,这里需要通过审批记录表查找当前用户已审批的记录 + // 然后排除这些申请,找出状态为"审批中"但当前用户还未审批的申请 + // 注意:这里需要根据实际业务逻辑调整,如果审批人信息存储在节点审批人表中,应该通过那个表查询 + + // 先查询当前用户已经审批过的申请ID(排除这些) + var approvedApplicationIds = await _db.Queryable() + .Where(x => x.ApproverId == currentUserId) + .Where(x => x.ApprovalResult == "通过" || x.ApprovalResult == "不通过" || x.ApprovalResult == "退回") + .Select(x => x.ApplicationId) + .Distinct() + .ToListAsync(); + + // 查询所有状态为"审批中"的申请ID + var allPendingApplicationIds = await _db.Queryable() + .Where(x => x.ApprovalStatus == "审批中" && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => x.Id) + .ToListAsync(); + + // 找出当前用户需要审批的申请(状态为"审批中"且当前用户还未审批的) + // 注意:这里简化处理,实际应该通过审批人配置表来确定审批人 + // 如果审批人信息没有单独存储,可能需要通过其他方式确定 + var approvalRecords = allPendingApplicationIds + .Where(x => !approvedApplicationIds.Contains(x)) + .ToList(); + + if (!approvalRecords.Any()) + { + // 如果没有待审批的申请,返回空列表 + var emptyPageList = new SqlSugarPagedList + { + list = new List(), + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = 0 + } + }; + return PageResult.SqlSugarPageResult(emptyPageList); + } + + var applicationIds = approvalRecords; + + // 基础查询:关联门店表,只查询状态为"审批中"的申请 + var query = _db.Queryable( + (app, store) => app.ApplicationStoreId == store.Id) + .Where((app, store) => app.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((app, store) => app.ApprovalStatus == "审批中") + .Where((app, store) => applicationIds.Contains(app.Id)); + + // 门店筛选 + if (!string.IsNullOrWhiteSpace(input.applicationStoreId)) + { + query = query.Where((app, store) => app.ApplicationStoreId == input.applicationStoreId); + } + + // 关键字搜索 + if (!string.IsNullOrWhiteSpace(input.keyword)) + { + query = query.Where((app, store) => + app.Id.Contains(input.keyword) || + app.ApplicationUserName.Contains(input.keyword) || + store.Dm.Contains(input.keyword)); + } + + // 排序 + if (sort.ToLower() == "asc") + { + query = query.OrderBy($"app.{sidx} asc"); + } + else + { + query = query.OrderBy($"app.{sidx} desc"); + } + + // 分页查询 + var pageList = await query.Select((app, store) => new LqInventoryUsageApplicationListOutput + { + id = app.Id, + usageBatchId = app.UsageBatchId, + applicationUserId = app.ApplicationUserId, + applicationUserName = app.ApplicationUserName, + applicationStoreId = app.ApplicationStoreId, + applicationStoreName = store.Dm, + applicationTime = app.ApplicationTime, + approvalStatus = app.ApprovalStatus, + isReceived = app.IsReceived, + receiveTime = app.ReceiveTime, + receiveUser = app.ReceiveUser, + remarks = app.Remarks, + totalAmount = app.TotalAmount, // 申请总金额(该批次所有商品的总价) + createTime = app.CreateTime + }).ToPagedListAsync(input.currentPage, input.pageSize); + + // 获取所有申请ID,用于查询审批人和使用记录统计 + var resultApplicationIds = pageList.list.Select(x => x.id).ToList(); + + if (resultApplicationIds.Any()) + { + // 查询当前审批人信息 + // 由于审批记录在审批时才创建,这里暂时不查询审批记录 + // 如果需要显示审批人信息,应该通过审批人配置表查询 + var currentApprovalRecords = new List(); + var allApprovalRecords = new List(); + + // 由于没有审批记录,暂时不填充审批人信息 + var approverDict = new Dictionary(); + + // 查询使用记录统计 + var batchIds = pageList.list.Select(x => x.usageBatchId).Distinct().ToList(); + var usageRecords = await _db.Queryable() + .Where(x => batchIds.Contains(x.UsageBatchId)) + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.UsageBatchId) + .Select(x => new + { + UsageBatchId = x.UsageBatchId, + TotalCount = SqlFunc.AggregateCount(x.Id), + TotalQuantity = SqlFunc.AggregateSum(x.UsageQuantity), + TotalAmount = SqlFunc.AggregateSum(x.TotalAmount) + }) + .ToListAsync(); + + var usageDict = usageRecords.ToDictionary(k => k.UsageBatchId, v => v); + + // 填充审批人和使用记录统计信息 + foreach (var item in pageList.list) + { + // 当前审批人信息(暂时为空,因为审批记录在审批时才创建) + item.currentApprovers = new List(); + + // 使用记录统计信息 + if (usageDict.ContainsKey(item.usageBatchId)) + { + var usage = usageDict[item.usageBatchId]; + item.batchUsageInfo = new BatchUsageInfo + { + totalCount = usage.TotalCount, + totalQuantity = usage.TotalQuantity, + totalAmount = usage.TotalAmount + }; + } + else + { + item.batchUsageInfo = new BatchUsageInfo + { + totalCount = 0, + totalQuantity = 0, + totalAmount = 0 + }; + } + } + } + + return PageResult.SqlSugarPageResult(pageList); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询待审批列表失败"); + throw NCCException.Oh($"查询失败:{ex.Message}"); + } + } + + #endregion + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs index 8131193..6997cb0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqPurchaseRecordsService.cs @@ -80,6 +80,8 @@ namespace NCC.Extend.LqPurchaseRecords DateTime? startApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.First()) : null; DateTime? endApproveTime = queryApproveTime != null ? Ext.GetDateTime(queryApproveTime.Last()) : null; // 先查询购买记录数据 + // 特殊处理:如果筛选"未审批",需要在数据库查询阶段就筛选购买记录状态为"未审批"或null/空字符串的记录 + // 因为"未审批"状态应该基于购买记录本身的状态,而不是报销申请的状态 var purchaseRecordsQuery = _db.Queryable() .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) .WhereIF(!string.IsNullOrEmpty(input.reimbursementCategoryId), p => p.ReimbursementCategoryId.Contains(input.reimbursementCategoryId)) @@ -97,10 +99,32 @@ namespace NCC.Extend.LqPurchaseRecords .WhereIF(!string.IsNullOrEmpty(input.approveUser), p => p.ApproveUser.Equals(input.approveUser)) .WhereIF(queryApproveTime != null, p => p.ApproveTime >= new DateTime(startApproveTime.ToDate().Year, startApproveTime.ToDate().Month, startApproveTime.ToDate().Day, 0, 0, 0)) .WhereIF(queryApproveTime != null, p => p.ApproveTime <= new DateTime(endApproveTime.ToDate().Year, endApproveTime.ToDate().Month, endApproveTime.ToDate().Day, 23, 59, 59)) - .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId)); + .WhereIF(!string.IsNullOrEmpty(input.applicationId), p => p.ApplicationId.Contains(input.applicationId)) + // 如果筛选"未审批"或"已审批",在数据库查询阶段就筛选购买记录状态 + // 因为这两个状态需要同时考虑购买记录本身的状态和报销申请的状态 + .WhereIF(!string.IsNullOrEmpty(input.approveStatus) && input.approveStatus == "未审批", + p => p.ApproveStatus == "未审批" || p.ApproveStatus == null || p.ApproveStatus == "") + .WhereIF(!string.IsNullOrEmpty(input.approveStatus) && input.approveStatus == "已审批", + p => p.ApproveStatus == "已审批"); - var total = await purchaseRecordsQuery.CountAsync(); - var purchaseRecords = await purchaseRecordsQuery.ToPageListAsync(input.currentPage, input.pageSize); + // 如果筛选"未审批"或"已审批",需要先查询所有符合条件的记录(不分页),然后在内存中处理 + // 因为这两个状态需要同时考虑购买记录本身的状态和报销申请的状态 + // 其他状态先分页,然后在内存中筛选 + List purchaseRecords; + int total; + + if (!string.IsNullOrEmpty(input.approveStatus) && (input.approveStatus == "未审批" || input.approveStatus == "已审批")) + { + // "未审批"或"已审批"筛选:先查询所有符合条件的记录 + purchaseRecords = await purchaseRecordsQuery.ToListAsync(); + total = purchaseRecords.Count; + } + else + { + // 其他筛选:先分页 + total = await purchaseRecordsQuery.CountAsync(); + purchaseRecords = await purchaseRecordsQuery.ToPageListAsync(input.currentPage, input.pageSize); + } // 获取所有关联的报销申请ID var applicationIds = purchaseRecords.Where(pr => !string.IsNullOrEmpty(pr.ApplicationId)).Select(pr => pr.ApplicationId).Distinct().ToList(); @@ -123,61 +147,137 @@ namespace NCC.Extend.LqPurchaseRecords } // 组装返回数据,如果关联了报销申请,优先使用报销申请的审批状态 - var result = purchaseRecords.Select(pr => new LqPurchaseRecordsListOutput + // 同时保存购买记录的原始状态,用于筛选 + var purchaseRecordsDict = purchaseRecords.ToDictionary(pr => pr.Id, pr => pr); + var result = purchaseRecords.Select(pr => new { - id = pr.Id, - reimbursementCategoryId = pr.ReimbursementCategoryId, - reimbursementCategoryName = pr.ReimbursementCategoryName, - unitPrice = pr.UnitPrice, - quantity = pr.Quantity, - amount = pr.Amount, - memo = pr.Memo, - purchaseTime = pr.PurchaseTime, - createTime = pr.CreateTime, - createUser = pr.CreateUser, - createUserStoreId = pr.CreateUserStoreId, - // 如果关联了报销申请,优先使用报销申请的审批状态;否则使用购买记录的状态 - approveStatus = !string.IsNullOrEmpty(pr.ApplicationId) && applications.ContainsKey(pr.ApplicationId) - ? applications[pr.ApplicationId] - : pr.ApproveStatus, - approveUser = pr.ApproveUser, - approveTime = pr.ApproveTime, - applicationId = pr.ApplicationId, + Output = new LqPurchaseRecordsListOutput + { + id = pr.Id, + reimbursementCategoryId = pr.ReimbursementCategoryId, + reimbursementCategoryName = pr.ReimbursementCategoryName, + unitPrice = pr.UnitPrice, + quantity = pr.Quantity, + amount = pr.Amount, + memo = pr.Memo, + purchaseTime = pr.PurchaseTime, + createTime = pr.CreateTime, + createUser = pr.CreateUser, + createUserStoreId = pr.CreateUserStoreId, + // 如果关联了报销申请,优先使用报销申请的审批状态;否则使用购买记录的状态 + // 如果状态为null或空字符串,且没有关联报销申请,则视为"未审批" + // 特殊处理:如果购买记录本身的状态是"已审批",即使关联了报销申请,也优先使用"已审批"状态 + approveStatus = (pr.ApproveStatus == "已审批") + ? "已审批" + : (!string.IsNullOrEmpty(pr.ApplicationId) && applications.ContainsKey(pr.ApplicationId) + ? applications[pr.ApplicationId] + : (string.IsNullOrEmpty(pr.ApproveStatus) ? "未审批" : pr.ApproveStatus)), + approveUser = pr.ApproveUser, + approveTime = pr.ApproveTime, + applicationId = pr.ApplicationId, + }, + OriginalApproveStatus = pr.ApproveStatus // 保存原始状态 }).ToList(); // 处理审批状态筛选(在内存中处理,因为状态可能来自报销申请) if (!string.IsNullOrEmpty(input.approveStatus)) { - result = result.Where(x => x.approveStatus != null && x.approveStatus.Contains(input.approveStatus)).ToList(); + // 使用精确匹配而不是Contains,避免误匹配 + // 例如:"未审批"不应该匹配"未通过" + // 特殊处理:如果筛选"未审批"或"已审批",需要同时考虑购买记录本身的状态 + if (input.approveStatus == "未审批") + { + // 筛选"未审批"时,需要同时匹配: + // 1. 返回的approveStatus是"未审批"(包括null/空字符串被转换为"未审批"的情况) + // 2. 或者购买记录本身的状态是"未审批"(即使关联了报销申请,如果购买记录是"未审批"也应该能筛选出来) + result = result.Where(x => + (x.Output.approveStatus != null && x.Output.approveStatus == "未审批") || + (string.IsNullOrEmpty(x.Output.approveStatus)) || + (x.OriginalApproveStatus == "未审批") + ).ToList(); + } + else if (input.approveStatus == "已审批") + { + // 筛选"已审批"时,需要同时匹配: + // 1. 返回的approveStatus是"已审批"(包括关联了报销申请且报销申请状态为"已通过"的情况) + // 2. 或者购买记录本身的状态是"已审批"(即使关联了报销申请,如果购买记录是"已审批"也应该能筛选出来) + result = result.Where(x => + (x.Output.approveStatus != null && x.Output.approveStatus == "已审批") || + (x.OriginalApproveStatus == "已审批") + ).ToList(); + } + else + { + // 其他状态使用精确匹配 + result = result.Where(x => + x.Output.approveStatus != null && x.Output.approveStatus == input.approveStatus + ).ToList(); + } total = result.Count; } - // 处理排序 - if (string.IsNullOrEmpty(input.sidx)) + // 转换为最终输出 + var finalResult = result.Select(x => x.Output).ToList(); + + // 如果筛选"未审批"或"已审批",需要在筛选后进行分页(因为之前查询了所有记录) + if (!string.IsNullOrEmpty(input.approveStatus) && (input.approveStatus == "未审批" || input.approveStatus == "已审批")) { - result = result.OrderByDescending(x => x.createTime).ToList(); + // 先排序 + if (string.IsNullOrEmpty(input.sidx)) + { + finalResult = finalResult.OrderByDescending(x => x.createTime).ToList(); + } + else + { + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc; + switch (input.sidx.ToLower()) + { + case "id": + finalResult = sortType == OrderByType.Desc ? finalResult.OrderByDescending(x => x.id).ToList() : finalResult.OrderBy(x => x.id).ToList(); + break; + case "createtime": + finalResult = sortType == OrderByType.Desc ? finalResult.OrderByDescending(x => x.createTime).ToList() : finalResult.OrderBy(x => x.createTime).ToList(); + break; + default: + finalResult = finalResult.OrderByDescending(x => x.createTime).ToList(); + break; + } + } + + // 分页 + var skip = (input.currentPage - 1) * input.pageSize; + finalResult = finalResult.Skip(skip).Take(input.pageSize).ToList(); } - else + + // 处理排序(如果筛选"未审批",已经在上面处理过了,这里跳过) + if (string.IsNullOrEmpty(input.approveStatus) || input.approveStatus != "未审批") { - var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc; - switch (input.sidx.ToLower()) + if (string.IsNullOrEmpty(input.sidx)) + { + finalResult = finalResult.OrderByDescending(x => x.createTime).ToList(); + } + else { - case "id": - result = sortType == OrderByType.Desc ? result.OrderByDescending(x => x.id).ToList() : result.OrderBy(x => x.id).ToList(); - break; - case "createtime": - result = sortType == OrderByType.Desc ? result.OrderByDescending(x => x.createTime).ToList() : result.OrderBy(x => x.createTime).ToList(); - break; - default: - result = result.OrderByDescending(x => x.createTime).ToList(); - break; + var sortType = input.sort?.ToLower() == "desc" ? OrderByType.Desc : OrderByType.Asc; + switch (input.sidx.ToLower()) + { + case "id": + finalResult = sortType == OrderByType.Desc ? finalResult.OrderByDescending(x => x.id).ToList() : finalResult.OrderBy(x => x.id).ToList(); + break; + case "createtime": + finalResult = sortType == OrderByType.Desc ? finalResult.OrderByDescending(x => x.createTime).ToList() : finalResult.OrderBy(x => x.createTime).ToList(); + break; + default: + finalResult = finalResult.OrderByDescending(x => x.createTime).ToList(); + break; + } } } return PageResult.SqlSugarPageResult( new SqlSugarPagedList { - list = result, + list = finalResult, pagination = new PagedModel { PageIndex = input.currentPage, PageSize = input.pageSize, Total = total } }); } diff --git a/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs b/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs index acdd8fd..f340644 100644 --- a/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs +++ b/netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs @@ -5,6 +5,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; +using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -69,7 +70,18 @@ namespace NCC.System.Service.Common string forceStoreType = type == "annexpic" ? "aliyun-oss" : null; await UploadFileByType(file, _filePath, _fileName, forceStoreType); - return new { name = _fileName, url = string.Format("/api/File/Image/{0}/{1}", type, _fileName) }; + // 如果是annexpic类型且使用阿里云OSS,返回OSS的完整访问地址 + string fileUrl; + if (type == "annexpic") + { + fileUrl = await GetOSSAccessUrl(_filePath, _fileName); + } + else + { + fileUrl = string.Format("/api/File/Image/{0}/{1}", type, _fileName); + } + + return new { name = _fileName, url = fileUrl }; } /// @@ -286,11 +298,94 @@ namespace NCC.System.Service.Common return new FileStreamResult(new FileStream(filePath, FileMode.Open), "application/octet-stream") { FileDownloadName = fileDownLoadName }; } } - catch (Exception e) + catch (Exception) { throw NCCException.Oh(ErrorCode.D8003); } } + + /// + /// 获取阿里云OSS文件的访问URL(带签名的临时访问URL) + /// + /// 文件路径 + /// 文件名 + /// OSS访问URL(带签名) + [NonAction] + private async Task GetOSSAccessUrl(string filePath, string fileName) + { + try + { + var bucketName = KeyVariable.BucketName; + var uploadPath = $"{filePath.TrimEnd('/').TrimEnd('\\')}/{fileName}"; + + // 使用OSS服务生成带签名的临时访问URL(有效期24小时) + var ossService = _oSSServiceFactory.Create("aliyun"); + var presignedUrl = await ossService.PresignedGetObjectAsync(bucketName, uploadPath, 86400); + + // 获取带签名的URL字符串 + // PresignedGetObjectAsync 返回的对象有ToString()方法,会返回完整的URL(包括查询参数和签名) + // 尝试多种方式获取URL字符串 + string urlString = string.Empty; + if (presignedUrl != null) + { + // 尝试使用反射获取AbsoluteUri属性(如果是Uri类型) + var urlType = presignedUrl.GetType(); + var absoluteUriProp = urlType.GetProperty("AbsoluteUri"); + if (absoluteUriProp != null) + { + urlString = absoluteUriProp.GetValue(presignedUrl)?.ToString() ?? string.Empty; + } + else + { + // 如果没有AbsoluteUri属性,使用ToString() + urlString = presignedUrl.ToString() ?? string.Empty; + } + } + + // 如果URL为空,说明生成失败 + if (string.IsNullOrEmpty(urlString)) + { + return $"/api/File/Image/annexpic/{fileName}"; + } + + // 如果配置了自定义域名,需要将URL中的域名替换为自定义域名 + var customDomain = _configuration["NCC_App:AliyunOSS:CustomDomain"] + ?? _configuration["NCC_APP:AliyunOSS:CustomDomain"]; + + if (!string.IsNullOrEmpty(customDomain)) + { + // 确保自定义域名格式正确 + var domain = customDomain.TrimEnd('/'); + if (!domain.StartsWith("http://") && !domain.StartsWith("https://")) + { + // 根据原始URL的协议来决定使用http还是https + var useHttps = urlString.StartsWith("https://"); + domain = (useHttps ? "https://" : "http://") + domain; + } + + // 使用Uri对象来解析和替换域名,保留查询参数(签名信息) + var originalUri = new Uri(urlString); + var customUri = new UriBuilder(originalUri) + { + Scheme = domain.StartsWith("https://") ? "https" : "http", + Host = domain.Replace("https://", "").Replace("http://", "").TrimEnd('/'), + // 确保保留查询参数(签名信息) + Query = originalUri.Query + }; + + // 返回完整的URL(包括查询参数) + return customUri.Uri.AbsoluteUri; + } + + // 如果没有配置自定义域名,直接返回带签名的URL + return urlString; + } + catch (Exception) + { + // 如果获取OSS URL失败,返回相对路径 + return $"/api/File/Image/annexpic/{fileName}"; + } + } #endregion /// diff --git a/sql/修复业绩类型字段-根据品相表fl3更新.sql b/sql/修复业绩类型字段-根据品相表fl3更新.sql new file mode 100644 index 0000000..c855892 --- /dev/null +++ b/sql/修复业绩类型字段-根据品相表fl3更新.sql @@ -0,0 +1,290 @@ +-- ============================================ +-- 修复业绩类型字段:根据品相表fl3字段更新所有相关表的F_PerformanceType +-- ============================================ +-- 说明: +-- 1. 此SQL用于修复因品相表(lq_xmzl)fl3字段设置错误导致的业绩类型问题 +-- 2. 会更新所有开单、耗卡、退卡相关表的F_PerformanceType字段 +-- 3. 更新逻辑:根据品相表的fl3字段,重新设置所有相关表的业绩类型 +-- 4. 注意:此SQL会覆盖原有的F_PerformanceType值,请确保品相表的fl3字段已正确修复 +-- +-- 执行前请确认: +-- 1. 品相表(lq_xmzl)的fl3字段已经修复正确 +-- 2. 已备份相关数据表 +-- 3. 建议在测试环境先执行验证 +-- ============================================ + +-- ============================================ +-- 1. 更新开单品项表(lq_kd_pxmx) +-- ============================================ +-- 通过品项ID(px字段)关联品相表的fl3字段 +UPDATE lq_kd_pxmx pxmx +INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id +SET pxmx.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE pxmx.F_IsEffective = 1; + +-- ============================================ +-- 2. 更新开单健康师业绩表(lq_kd_jksyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_kdpxid关联开单品项表获取品项ID +UPDATE lq_kd_jksyj jksyj +LEFT JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(jksyj.F_ItemId, pxmx.px) = xmzl.F_Id +SET jksyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE jksyj.F_IsEffective = 1 + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 3. 更新开单科技部老师业绩表(lq_kd_kjbsyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_kdpxid关联开单品项表获取品项ID +UPDATE lq_kd_kjbsyj kjbsyj +LEFT JOIN lq_kd_pxmx pxmx ON kjbsyj.F_kdpxid = pxmx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(kjbsyj.F_ItemId, pxmx.px) = xmzl.F_Id +SET kjbsyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE kjbsyj.F_IsEffective = 1 + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 4. 更新消耗品项表(lq_xh_pxmx) +-- ============================================ +-- 通过品项ID(px字段)关联品相表的fl3字段 +UPDATE lq_xh_pxmx pxmx +INNER JOIN lq_xmzl xmzl ON pxmx.px = xmzl.F_Id +SET pxmx.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE (pxmx.F_IsEffective = 1 OR pxmx.F_IsEffective IS NULL); + +-- ============================================ +-- 5. 更新消耗健康师业绩表(lq_xh_jksyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_kdpxid关联消耗品项表获取品项ID +UPDATE lq_xh_jksyj jksyj +LEFT JOIN lq_xh_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(jksyj.F_ItemId, pxmx.px) = xmzl.F_Id +SET jksyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE (jksyj.F_IsEffective = 1 OR jksyj.F_IsEffective IS NULL) + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 6. 更新消耗科技部老师业绩表(lq_xh_kjbsyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_hkpxid关联消耗品项表获取品项ID +UPDATE lq_xh_kjbsyj kjbsyj +LEFT JOIN lq_xh_pxmx pxmx ON kjbsyj.F_hkpxid = pxmx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(kjbsyj.F_ItemId, pxmx.px) = xmzl.F_Id +SET kjbsyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE (kjbsyj.F_IsEffective = 1 OR kjbsyj.F_IsEffective IS NULL) + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 7. 更新退卡明细表(lq_hytk_mx) +-- ============================================ +-- 通过品项ID(px字段)关联品相表的fl3字段 +UPDATE lq_hytk_mx mx +INNER JOIN lq_xmzl xmzl ON mx.px = xmzl.F_Id +SET mx.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE mx.F_IsEffective = 1; + +-- ============================================ +-- 8. 更新退卡健康师业绩表(lq_hytk_jksyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_CardReturn关联退卡明细表获取品项ID +UPDATE lq_hytk_jksyj jksyj +LEFT JOIN lq_hytk_mx mx ON jksyj.F_CardReturn = mx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(jksyj.F_ItemId, mx.px) = xmzl.F_Id +SET jksyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE jksyj.F_IsEffective = 1 + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 9. 更新退卡科技部老师业绩表(lq_hytk_kjbsyj) +-- ============================================ +-- 优先使用F_ItemId,如果没有则通过F_CardReturn关联退卡明细表获取品项ID +UPDATE lq_hytk_kjbsyj kjbsyj +LEFT JOIN lq_hytk_mx mx ON kjbsyj.F_CardReturn = mx.F_Id +LEFT JOIN lq_xmzl xmzl ON COALESCE(kjbsyj.F_ItemId, mx.px) = xmzl.F_Id +SET kjbsyj.F_PerformanceType = COALESCE(xmzl.fl3, '') +WHERE kjbsyj.F_IsEffective = 1 + AND xmzl.F_Id IS NOT NULL; + +-- ============================================ +-- 10. 验证更新结果(统计各表的业绩类型分布) +-- ============================================ +SELECT + 'lq_kd_pxmx' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_kd_pxmx +WHERE F_IsEffective = 1 +UNION ALL +SELECT + 'lq_kd_jksyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_kd_jksyj +WHERE F_IsEffective = 1 +UNION ALL +SELECT + 'lq_kd_kjbsyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_kd_kjbsyj +WHERE F_IsEffective = 1 +UNION ALL +SELECT + 'lq_xh_pxmx' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_xh_pxmx +WHERE F_IsEffective = 1 OR F_IsEffective IS NULL +UNION ALL +SELECT + 'lq_xh_jksyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_xh_jksyj +WHERE F_IsEffective = 1 OR F_IsEffective IS NULL +UNION ALL +SELECT + 'lq_xh_kjbsyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_xh_kjbsyj +WHERE F_IsEffective = 1 OR F_IsEffective IS NULL +UNION ALL +SELECT + 'lq_hytk_mx' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_hytk_mx +WHERE F_IsEffective = 1 +UNION ALL +SELECT + 'lq_hytk_jksyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_hytk_jksyj +WHERE F_IsEffective = 1 +UNION ALL +SELECT + 'lq_hytk_kjbsyj' as table_name, + COUNT(*) as total_count, + SUM(CASE WHEN F_PerformanceType IS NOT NULL AND F_PerformanceType != '' THEN 1 ELSE 0 END) as '已设置', + SUM(CASE WHEN F_PerformanceType IS NULL OR F_PerformanceType = '' THEN 1 ELSE 0 END) as '未设置' +FROM lq_hytk_kjbsyj +WHERE F_IsEffective = 1; + +-- ============================================ +-- 11. 查看品相表fl3字段的值分布 +-- ============================================ +SELECT + 'lq_xmzl.fl3 值分布' as description, + fl3 as performance_type, + COUNT(*) as count +FROM lq_xmzl +WHERE fl3 IS NOT NULL AND fl3 != '' +GROUP BY fl3 +ORDER BY count DESC; + +-- ============================================ +-- 12. 查看各表业绩类型的值分布(用于对比验证) +-- ============================================ +SELECT + 'lq_kd_pxmx' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_kd_pxmx +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_kd_jksyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_kd_jksyj +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_kd_kjbsyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_kd_kjbsyj +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_xh_pxmx' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_xh_pxmx +WHERE (F_IsEffective = 1 OR F_IsEffective IS NULL) + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_xh_jksyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_xh_jksyj +WHERE (F_IsEffective = 1 OR F_IsEffective IS NULL) + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_xh_kjbsyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_xh_kjbsyj +WHERE (F_IsEffective = 1 OR F_IsEffective IS NULL) + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_hytk_mx' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_hytk_mx +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_hytk_jksyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_hytk_jksyj +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +UNION ALL +SELECT + 'lq_hytk_kjbsyj' as table_name, + F_PerformanceType as performance_type, + COUNT(*) as count +FROM lq_hytk_kjbsyj +WHERE F_IsEffective = 1 + AND F_PerformanceType IS NOT NULL AND F_PerformanceType != '' +GROUP BY F_PerformanceType +ORDER BY table_name, count DESC; + +-- ============================================ +-- SQL脚本执行完成 +-- ============================================ +-- 说明: +-- 1. 此SQL会根据品相表(lq_xmzl)的fl3字段,更新所有相关表的F_PerformanceType字段 +-- 2. 更新覆盖所有有效记录,不管原来的值是什么 +-- 3. 执行后请查看验证结果,确认更新是否正确 +-- 4. 如果发现数据异常,请及时回滚或联系开发人员 + + diff --git a/sql/创建库存使用申请审批流程表.sql b/sql/创建库存使用申请审批流程表.sql index df03491..726c469 100644 --- a/sql/创建库存使用申请审批流程表.sql +++ b/sql/创建库存使用申请审批流程表.sql @@ -102,3 +102,5 @@ WHERE u.`F_UnitPrice` = 0 OR u.`F_UnitPrice` IS NULL; + + diff --git a/sql/创建库存使用申请表-完整版.sql b/sql/创建库存使用申请表-完整版.sql new file mode 100644 index 0000000..7e332bf --- /dev/null +++ b/sql/创建库存使用申请表-完整版.sql @@ -0,0 +1,62 @@ +-- ============================================ +-- 创建库存使用申请表(完整版,包含总价字段) +-- ============================================ +-- 说明:用于库存使用申请的审批流程 +-- 执行时间:2025年 +-- ============================================ + +-- 1. 创建库存使用申请表 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_application` ( + `F_Id` varchar(50) NOT NULL COMMENT '申请编号', + `F_UsageBatchId` varchar(50) NOT NULL COMMENT '使用批次ID(关联lq_inventory_usage.F_UsageBatchId)', + `F_ApplicationUserId` varchar(50) NOT NULL COMMENT '申请人ID', + `F_ApplicationUserName` varchar(100) DEFAULT NULL COMMENT '申请人姓名', + `F_ApplicationStoreId` varchar(50) DEFAULT NULL COMMENT '申请门店ID', + `F_ApplicationTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', + `F_NodeCount` int DEFAULT 1 COMMENT '节点数量(固定为1)', + `F_CurrentNodeOrder` int DEFAULT 0 COMMENT '当前审批节点(0-待审批,1-审批中,2-已完成)', + `F_CurrentNodeId` varchar(50) DEFAULT NULL COMMENT '当前节点ID', + `F_ApprovalStatus` varchar(20) DEFAULT '待审批' COMMENT '审批状态(待审批/审批中/已通过/未通过/已退回)', + `F_IsReceived` int DEFAULT 0 COMMENT '是否已领取(1-已领取,0-未领取)', + `F_ReceiveTime` datetime DEFAULT NULL COMMENT '领取时间', + `F_ReceiveUser` varchar(50) DEFAULT NULL COMMENT '领取人ID', + `F_Remarks` varchar(500) DEFAULT NULL COMMENT '备注', + `F_TotalAmount` decimal(18,2) DEFAULT 0.00 COMMENT '申请总金额(该批次所有商品的总价)', + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人ID', + `F_UpdateTime` datetime DEFAULT NULL COMMENT '更新时间', + `F_UpdateUser` varchar(50) DEFAULT NULL COMMENT '更新人ID', + `F_IsEffective` int DEFAULT 1 COMMENT '是否有效(1-有效,0-无效)', + PRIMARY KEY (`F_Id`), + UNIQUE KEY `uk_usage_batch_id` (`F_UsageBatchId`), + KEY `idx_application_user_id` (`F_ApplicationUserId`), + KEY `idx_application_store_id` (`F_ApplicationStoreId`), + KEY `idx_current_node` (`F_CurrentNodeId`), + KEY `idx_approval_status` (`F_ApprovalStatus`), + KEY `idx_is_received` (`F_IsReceived`), + KEY `idx_total_amount` (`F_TotalAmount`), + KEY `idx_create_time` (`F_CreateTime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请表'; + +-- 2. 创建库存使用申请审批记录表 +CREATE TABLE IF NOT EXISTS `lq_inventory_usage_approval_record` ( + `F_Id` varchar(50) NOT NULL COMMENT '审批记录编号', + `F_ApplicationId` varchar(50) NOT NULL COMMENT '申请ID(关联lq_inventory_usage_application.F_Id)', + `F_NodeId` varchar(50) DEFAULT NULL COMMENT '节点ID', + `F_NodeOrder` int DEFAULT 1 COMMENT '节点顺序(固定为1)', + `F_ApproverId` varchar(50) NOT NULL COMMENT '审批人ID', + `F_ApproverName` varchar(100) DEFAULT NULL COMMENT '审批人姓名', + `F_ApprovalResult` varchar(20) DEFAULT '待审批' COMMENT '审批结果(待审批/已通过/未通过/已退回)', + `F_ApprovalOpinion` varchar(500) DEFAULT NULL COMMENT '审批意见', + `F_ApprovalTime` datetime DEFAULT NULL COMMENT '审批时间', + `F_IsCurrentNode` int DEFAULT 1 COMMENT '是否当前节点(1-是,0-否)', + `F_CreateTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `F_CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人ID', + PRIMARY KEY (`F_Id`), + KEY `idx_application_id` (`F_ApplicationId`), + KEY `idx_approver_id` (`F_ApproverId`), + KEY `idx_approval_result` (`F_ApprovalResult`), + KEY `idx_is_current_node` (`F_IsCurrentNode`), + KEY `idx_node_order` (`F_ApplicationId`, `F_NodeOrder`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存使用申请审批记录表'; + diff --git a/sql/添加库存使用申请总价字段.sql b/sql/添加库存使用申请总价字段.sql new file mode 100644 index 0000000..a590af5 --- /dev/null +++ b/sql/添加库存使用申请总价字段.sql @@ -0,0 +1,29 @@ +-- ============================================ +-- 在库存使用申请表中添加总价字段 +-- ============================================ +-- 说明:用于保存该批次所有商品的总价 +-- ============================================ + +-- 检查并添加总价字段 +SET @dbname = DATABASE(); +SET @tablename = 'lq_inventory_usage_application'; +SET @columnname = 'F_TotalAmount'; +SET @preparedStatement = (SELECT IF( + ( + SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS + WHERE + (TABLE_SCHEMA = @dbname) + AND (TABLE_NAME = @tablename) + AND (COLUMN_NAME = @columnname) + ) > 0, + 'SELECT 1', + CONCAT('ALTER TABLE ', @tablename, ' ADD COLUMN `', @columnname, '` decimal(18,2) DEFAULT 0.00 COMMENT ''申请总金额(该批次所有商品的总价)'' AFTER `F_Remarks`') +)); +PREPARE alterIfNotExists FROM @preparedStatement; +EXECUTE alterIfNotExists; +DEALLOCATE PREPARE alterIfNotExists; + +-- 添加索引(如果不存在) +ALTER TABLE `lq_inventory_usage_application` + ADD INDEX `idx_total_amount` (`F_TotalAmount`) COMMENT '总价索引'; + -- libgit2 0.21.4