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();
}
}