using CampusErrand.Data; using CampusErrand.Models; using CampusErrand.Models.Dtos; using Microsoft.EntityFrameworkCore; namespace CampusErrand.Helpers; /// /// 业务辅助方法集合 /// public static class BusinessHelpers { /// /// Banner 请求校验 /// internal static List ValidateBannerRequest(BannerRequest request) { var errors = new List(); if (string.IsNullOrWhiteSpace(request.ImageUrl)) errors.Add(new { field = "imageUrl", message = "图片地址不能为空" }); if (string.IsNullOrWhiteSpace(request.LinkUrl)) errors.Add(new { field = "linkUrl", message = "链接地址不能为空" }); if (string.IsNullOrWhiteSpace(request.LinkType)) errors.Add(new { field = "linkType", message = "链接类型不能为空" }); else if (!Enum.TryParse(request.LinkType, true, out var parsed) || !Enum.IsDefined(parsed)) errors.Add(new { field = "linkType", message = "链接类型不合法" }); return errors; } /// /// 门店请求校验 /// internal static List ValidateShopRequest(ShopRequest request) { var errors = new List(); if (string.IsNullOrWhiteSpace(request.Name)) errors.Add(new { field = "name", message = "门店名称不能为空" }); if (string.IsNullOrWhiteSpace(request.Photo)) errors.Add(new { field = "photo", message = "门店照片不能为空" }); if (string.IsNullOrWhiteSpace(request.Location)) errors.Add(new { field = "location", message = "门店位置不能为空" }); if (string.IsNullOrWhiteSpace(request.PackingFeeType)) errors.Add(new { field = "packingFeeType", message = "打包费类型不能为空" }); if (request.PackingFeeAmount < 0) errors.Add(new { field = "packingFeeAmount", message = "打包费金额不能为负数" }); return errors; } /// /// 菜品请求校验 /// internal static List ValidateDishRequest(DishRequest request) { var errors = new List(); if (string.IsNullOrWhiteSpace(request.Name)) errors.Add(new { field = "name", message = "菜品名称不能为空" }); if (string.IsNullOrWhiteSpace(request.Photo)) errors.Add(new { field = "photo", message = "菜品照片不能为空" }); if (request.Price < 0) errors.Add(new { field = "price", message = "菜品价格不能为负数" }); return errors; } /// /// 计算单个门店的打包费 /// Fixed 模式:固定金额,不论菜品数量 /// PerItem 模式:菜品总份数 × 单份打包费 /// internal static decimal CalculatePackingFee(PackingFeeType feeType, decimal feeAmount, int totalQuantity) { return feeType switch { PackingFeeType.Fixed => feeAmount, PackingFeeType.PerItem => feeAmount * totalQuantity, _ => 0m }; } /// /// 计算平台佣金分成 /// 根据佣金区间规则计算:百分比类型按比例,固定类型扣除固定金额 /// internal static decimal CalculatePlatformFee(decimal commission, List rules) { // 查找匹配的佣金区间 var matchedRule = rules .Where(r => commission >= r.MinAmount && (r.MaxAmount == null || commission <= r.MaxAmount)) .FirstOrDefault(); if (matchedRule == null) return 0m; return matchedRule.RateType switch { CommissionRateType.Percentage => Math.Round(commission * matchedRule.Rate / 100m, 2), CommissionRateType.Fixed => Math.Min(matchedRule.Rate, commission), _ => 0m }; } /// /// 获取用户可见的系统消息(按目标类型过滤) /// internal static async Task> GetVisibleSystemMessages(AppDbContext db, int userId, User? user) { var allMessages = await db.SystemMessages.ToListAsync(); return allMessages.Where(m => { if (m.TargetType == MessageTargetType.All) return true; if (m.TargetType == MessageTargetType.OrderUser) return user != null && user.Role != UserRole.Runner; if (m.TargetType == MessageTargetType.RunnerUser) return user != null && (user.Role == UserRole.Runner || user.Role == UserRole.Admin); if (m.TargetType == MessageTargetType.Specific && m.TargetUserIds != null) { try { var ids = System.Text.Json.JsonSerializer.Deserialize>(m.TargetUserIds); return ids != null && ids.Contains(userId); } catch { return false; } } return false; }).ToList(); } /// /// 解冻已到期的收益记录:将冻结期满的收益从 Frozen 变为 Available /// internal static async Task UnfreezeEarnings(AppDbContext db) { var now = DateTime.UtcNow; var frozenEarnings = await db.Earnings .Where(e => e.Status == EarningStatus.Frozen && e.FrozenUntil <= now) .ToListAsync(); foreach (var earning in frozenEarnings) { earning.Status = EarningStatus.Available; } if (frozenEarnings.Count > 0) { await db.SaveChangesAsync(); } } /// /// 自动确认超过24小时未处理的待确认订单 /// internal static async Task AutoConfirmExpiredOrders(AppDbContext db) { var cutoff = DateTime.UtcNow.AddHours(-24); var expiredOrders = await db.Orders .Where(o => o.Status == OrderStatus.WaitConfirm && o.CompletedAt != null && o.CompletedAt <= cutoff) .ToListAsync(); foreach (var order in expiredOrders) { order.Status = OrderStatus.Completed; // 计算佣金收益(商品金额平台不抽成,全额给跑腿) var rules = await db.CommissionRules.OrderBy(r => r.MinAmount).ToListAsync(); var platformFee = CalculatePlatformFee(order.Commission, rules); var netEarning = order.Commission - platformFee + (order.GoodsAmount ?? 0); var freezeDaysConfig = await db.SystemConfigs.FirstOrDefaultAsync(c => c.Key == "freeze_days"); var freezeDays = 1; if (freezeDaysConfig != null && int.TryParse(freezeDaysConfig.Value, out var configDays)) freezeDays = configDays; db.Earnings.Add(new Earning { UserId = order.RunnerId!.Value, OrderId = order.Id, GoodsAmount = order.GoodsAmount, Commission = order.Commission, PlatformFee = platformFee, NetEarning = netEarning, Status = EarningStatus.Frozen, FrozenUntil = DateTime.UtcNow.AddDays(freezeDays), CreatedAt = DateTime.UtcNow }); } if (expiredOrders.Count > 0) { await db.SaveChangesAsync(); Console.WriteLine($"[定时任务] 自动确认了 {expiredOrders.Count} 个超时订单"); } } /// /// 生成唯一的随机6位数字UID /// internal static async Task GenerateUniqueUid(AppDbContext db) { for (var i = 0; i < 100; i++) { var uid = Random.Shared.Next(100000, 999999).ToString(); var exists = await db.Users.AnyAsync(u => u.Uid == uid); if (!exists) return uid; } // 极端情况:用7位 return Random.Shared.Next(1000000, 9999999).ToString(); } }