1231 lines
42 KiB
C#
1231 lines
42 KiB
C#
using HoneyBox.Admin.Business.Models;
|
||
using HoneyBox.Admin.Business.Models.Goods;
|
||
using HoneyBox.Admin.Business.Services.Interfaces;
|
||
using HoneyBox.Model.Data;
|
||
using HoneyBox.Model.Entities;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace HoneyBox.Admin.Business.Services;
|
||
|
||
/// <summary>
|
||
/// 商品管理服务实现
|
||
/// </summary>
|
||
public class GoodsService : IGoodsService
|
||
{
|
||
private readonly HoneyBoxDbContext _dbContext;
|
||
private readonly ILogger<GoodsService> _logger;
|
||
|
||
// 盒子类型名称映射
|
||
private static readonly Dictionary<int, string> BoxTypeNames = new()
|
||
{
|
||
{ 1, "一番赏" },
|
||
{ 2, "无限赏" },
|
||
{ 3, "擂台赏" },
|
||
{ 4, "抽卡机" },
|
||
{ 5, "福袋" },
|
||
{ 6, "幸运赏" },
|
||
{ 8, "盲盒" },
|
||
{ 9, "扭蛋" },
|
||
{ 10, "积分商城" },
|
||
{ 11, "转转赏" },
|
||
{ 12, "连击赏" },
|
||
{ 15, "福利屋" },
|
||
{ 16, "连抽赏" },
|
||
{ 17, "大乱斗" }
|
||
};
|
||
|
||
public GoodsService(
|
||
HoneyBoxDbContext dbContext,
|
||
ILogger<GoodsService> logger)
|
||
{
|
||
_dbContext = dbContext;
|
||
_logger = logger;
|
||
}
|
||
|
||
#region 商品管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<GoodsListResponse>> GetGoodsListAsync(GoodsListRequest request)
|
||
{
|
||
var query = _dbContext.Goods
|
||
.AsNoTracking()
|
||
.Where(g => g.DeletedAt == null);
|
||
|
||
// 应用过滤条件
|
||
query = ApplyGoodsFilters(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 获取商品列表
|
||
var goods = await query
|
||
.OrderByDescending(g => g.Sort)
|
||
.ThenByDescending(g => g.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 映射结果
|
||
var list = goods.Select(MapToGoodsListResponse).ToList();
|
||
|
||
return PagedResult<GoodsListResponse>.Create(list, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<GoodsDetailResponse?> GetGoodsDetailAsync(int goodsId)
|
||
{
|
||
var goods = await _dbContext.Goods
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
|
||
if (goods == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return MapToGoodsDetailResponse(goods);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<int> CreateGoodsAsync(GoodsCreateRequest request, int operatorId)
|
||
{
|
||
// 验证商品类型
|
||
if (!BoxTypeNames.ContainsKey(request.Type))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的商品类型");
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
var goods = new Good
|
||
{
|
||
Title = request.Title,
|
||
Price = request.Price,
|
||
Type = (byte)request.Type,
|
||
ImgUrl = request.ImgUrl,
|
||
ImgUrlDetail = request.ImgUrlDetail,
|
||
Stock = request.Stock,
|
||
SaleStock = 0,
|
||
Sort = request.Sort,
|
||
DailyXiangou = request.DailyLimit,
|
||
LockIs = (byte)request.LockIs,
|
||
IntegralIs = (byte)request.IntegralIs,
|
||
ShowIs = (byte)request.ShowIs,
|
||
CouponIs = (byte)request.CouponIs,
|
||
CouponPro = request.CouponPro,
|
||
FlwStartTime = request.FlwStartTime,
|
||
FlwEndTime = request.FlwEndTime,
|
||
OpenTime = request.OpenTime,
|
||
ChoujiangXianzhi = request.ChoujiangXianzhi ?? 0,
|
||
CategoryId = request.CategoryId,
|
||
GoodsDescribe = request.GoodsDescribe,
|
||
NewIs = (byte)request.NewIs,
|
||
IsShouZhe = (byte)request.IsShouZhe,
|
||
RageIs = (byte)request.RageIs,
|
||
Rage = request.Rage,
|
||
LingzhuIs = (byte)request.LingzhuIs,
|
||
LingzhuFan = request.LingzhuFan,
|
||
IsAutoXiajia = (byte)request.IsAutoXiajia,
|
||
XiajiaLirun = (int)request.XiajiaLirun,
|
||
XiajiaAutoCoushu = (int)request.XiajiaAutoCoushu,
|
||
XiajiaJine = (int)request.XiajiaJine,
|
||
Status = 0, // 默认下架
|
||
PrizeNum = 0,
|
||
IsFlw = (byte)(request.Type == 15 ? 1 : 0),
|
||
CreatedAt = now,
|
||
UpdatedAt = now
|
||
};
|
||
|
||
_dbContext.Goods.Add(goods);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
// 记录操作日志
|
||
await LogGoodsOperationAsync(goods.Id, "create", $"创建商品: {goods.Title}", operatorId);
|
||
|
||
_logger.LogInformation("创建商品成功: GoodsId={GoodsId}, Title={Title}, Operator={Operator}",
|
||
goods.Id, goods.Title, operatorId);
|
||
|
||
return goods.Id;
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> UpdateGoodsAsync(int goodsId, GoodsUpdateRequest request, int operatorId)
|
||
{
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
|
||
}
|
||
|
||
// 验证商品类型
|
||
if (!BoxTypeNames.ContainsKey(request.Type))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的商品类型");
|
||
}
|
||
|
||
// 防止库存减少
|
||
if (request.Stock < goods.Stock)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "不允许减少库存");
|
||
}
|
||
|
||
// 检查是否需要复制奖品配置(库存增加时)
|
||
var stockIncrease = request.Stock - goods.Stock;
|
||
if (stockIncrease > 0)
|
||
{
|
||
await CopyPrizeConfigurationsAsync(goodsId, stockIncrease);
|
||
}
|
||
|
||
// 更新商品信息
|
||
goods.Title = request.Title;
|
||
goods.Price = request.Price;
|
||
goods.Type = (byte)request.Type;
|
||
goods.ImgUrl = request.ImgUrl;
|
||
goods.ImgUrlDetail = request.ImgUrlDetail;
|
||
goods.Stock = request.Stock;
|
||
goods.Sort = request.Sort;
|
||
goods.DailyXiangou = request.DailyLimit;
|
||
goods.LockIs = (byte)request.LockIs;
|
||
goods.IntegralIs = (byte)request.IntegralIs;
|
||
goods.ShowIs = (byte)request.ShowIs;
|
||
goods.CouponIs = (byte)request.CouponIs;
|
||
goods.CouponPro = request.CouponPro;
|
||
goods.FlwStartTime = request.FlwStartTime;
|
||
goods.FlwEndTime = request.FlwEndTime;
|
||
goods.OpenTime = request.OpenTime;
|
||
goods.ChoujiangXianzhi = request.ChoujiangXianzhi ?? 0;
|
||
goods.CategoryId = request.CategoryId;
|
||
goods.GoodsDescribe = request.GoodsDescribe;
|
||
goods.NewIs = (byte)request.NewIs;
|
||
goods.IsShouZhe = (byte)request.IsShouZhe;
|
||
goods.RageIs = (byte)request.RageIs;
|
||
goods.Rage = request.Rage;
|
||
goods.LingzhuIs = (byte)request.LingzhuIs;
|
||
goods.LingzhuFan = request.LingzhuFan;
|
||
goods.IsAutoXiajia = (byte)request.IsAutoXiajia;
|
||
goods.XiajiaLirun = (int)request.XiajiaLirun;
|
||
goods.XiajiaAutoCoushu = (int)request.XiajiaAutoCoushu;
|
||
goods.XiajiaJine = (int)request.XiajiaJine;
|
||
goods.IsFlw = (byte)(request.Type == 15 ? 1 : 0);
|
||
goods.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
// 记录操作日志
|
||
await LogGoodsOperationAsync(goodsId, "update", $"更新商品: {goods.Title}", operatorId);
|
||
|
||
_logger.LogInformation("更新商品成功: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> DeleteGoodsAsync(int goodsId, int operatorId)
|
||
{
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
|
||
}
|
||
|
||
// 软删除
|
||
goods.DeletedAt = DateTime.Now;
|
||
goods.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
// 记录操作日志
|
||
await LogGoodsOperationAsync(goodsId, "delete", $"删除商品: {goods.Title}", operatorId);
|
||
|
||
_logger.LogInformation("删除商品成功: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> SetGoodsStatusAsync(int goodsId, int status, int operatorId)
|
||
{
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
|
||
}
|
||
|
||
var oldStatus = goods.Status;
|
||
goods.Status = (byte)status;
|
||
goods.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
// 记录操作日志
|
||
var operation = status == 1 ? "上架" : "下架";
|
||
await LogGoodsOperationAsync(goodsId, "status", $"商品{operation}: {goods.Title}", operatorId);
|
||
|
||
_logger.LogInformation("商品状态变更: GoodsId={GoodsId}, OldStatus={OldStatus}, NewStatus={NewStatus}, Operator={Operator}",
|
||
goodsId, oldStatus, status, operatorId);
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 奖品管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<List<PrizeDto>> GetPrizesAsync(int goodsId)
|
||
{
|
||
// 验证商品存在
|
||
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (!goodsExists)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
|
||
}
|
||
|
||
var prizes = await _dbContext.GoodsItems
|
||
.AsNoTracking()
|
||
.Where(gi => gi.GoodsId == goodsId)
|
||
.OrderBy(gi => gi.Sort)
|
||
.ThenBy(gi => gi.Num)
|
||
.ToListAsync();
|
||
|
||
// 获取所有奖品等级ID
|
||
var shangIds = prizes
|
||
.Where(p => p.ShangId.HasValue)
|
||
.Select(p => p.ShangId!.Value)
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
// 批量查询等级信息
|
||
var prizeLevels = await _dbContext.PrizeLevels
|
||
.AsNoTracking()
|
||
.Where(pl => shangIds.Contains(pl.Id))
|
||
.ToDictionaryAsync(pl => pl.Id, pl => new PrizeLevelInfo { Title = pl.Title, Color = pl.Color });
|
||
|
||
return prizes.Select(p => MapToPrizeDto(p, prizeLevels)).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 奖品等级信息
|
||
/// </summary>
|
||
private class PrizeLevelInfo
|
||
{
|
||
public string Title { get; set; } = string.Empty;
|
||
public string? Color { get; set; }
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<int> AddPrizeAsync(int goodsId, PrizeCreateRequest request)
|
||
{
|
||
// 验证商品存在
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "商品不存在");
|
||
}
|
||
|
||
// 获取当前最大编号
|
||
var maxNum = await _dbContext.GoodsItems
|
||
.Where(gi => gi.GoodsId == goodsId)
|
||
.MaxAsync(gi => (int?)gi.Num) ?? 0;
|
||
|
||
var now = DateTime.Now;
|
||
var prize = new GoodsItem
|
||
{
|
||
GoodsId = goodsId,
|
||
Num = maxNum + 1,
|
||
Title = request.Title,
|
||
ImgUrl = request.ImgUrl,
|
||
ImgUrlDetail = request.ImgUrlDetail,
|
||
Stock = request.Stock,
|
||
SurplusStock = request.Stock,
|
||
Price = request.Price,
|
||
Money = request.Money,
|
||
ScMoney = request.ScMoney,
|
||
RealPro = request.RealPro,
|
||
GoodsType = (byte)request.GoodsType,
|
||
Sort = request.Sort,
|
||
ShangId = request.ShangId,
|
||
RewardNum = request.RewardNum,
|
||
Rank = request.Rank,
|
||
GiveMoney = request.GiveMoney,
|
||
CardNo = request.CardNo,
|
||
PrizeCode = GeneratePrizeCode(),
|
||
Type = (byte)request.Type,
|
||
LianJiType = (byte)request.LianJiType,
|
||
RewardId = request.RewardId,
|
||
Doubling = request.Doubling,
|
||
IsLingzhu = (byte)request.IsLingzhu,
|
||
PrizeNum = 1,
|
||
GoodsListId = 0,
|
||
CreatedAt = now,
|
||
UpdatedAt = now
|
||
};
|
||
|
||
_dbContext.GoodsItems.Add(prize);
|
||
|
||
// 更新商品奖品数量
|
||
goods.PrizeNum = await _dbContext.GoodsItems.CountAsync(gi => gi.GoodsId == goodsId) + 1;
|
||
goods.UpdatedAt = now;
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("添加奖品成功: GoodsId={GoodsId}, PrizeId={PrizeId}, Title={Title}",
|
||
goodsId, prize.Id, prize.Title);
|
||
|
||
return prize.Id;
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> UpdatePrizeAsync(int prizeId, PrizeUpdateRequest request)
|
||
{
|
||
var prize = await _dbContext.GoodsItems.FirstOrDefaultAsync(gi => gi.Id == prizeId);
|
||
if (prize == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "奖品不存在");
|
||
}
|
||
|
||
prize.Title = request.Title;
|
||
prize.ImgUrl = request.ImgUrl;
|
||
prize.ImgUrlDetail = request.ImgUrlDetail;
|
||
prize.Stock = request.Stock;
|
||
prize.SurplusStock = request.Stock; // 更新剩余库存
|
||
prize.Price = request.Price;
|
||
prize.Money = request.Money;
|
||
prize.ScMoney = request.ScMoney;
|
||
prize.RealPro = request.RealPro;
|
||
prize.GoodsType = (byte)request.GoodsType;
|
||
prize.Sort = request.Sort;
|
||
prize.ShangId = request.ShangId;
|
||
prize.RewardNum = request.RewardNum;
|
||
prize.Rank = request.Rank;
|
||
prize.GiveMoney = request.GiveMoney;
|
||
prize.CardNo = request.CardNo;
|
||
prize.Type = (byte)request.Type;
|
||
prize.LianJiType = (byte)request.LianJiType;
|
||
prize.RewardId = request.RewardId;
|
||
prize.Doubling = request.Doubling;
|
||
prize.IsLingzhu = (byte)request.IsLingzhu;
|
||
prize.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("更新奖品成功: PrizeId={PrizeId}, Title={Title}", prizeId, prize.Title);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> DeletePrizeAsync(int prizeId)
|
||
{
|
||
var prize = await _dbContext.GoodsItems.FirstOrDefaultAsync(gi => gi.Id == prizeId);
|
||
if (prize == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "奖品不存在");
|
||
}
|
||
|
||
var goodsId = prize.GoodsId;
|
||
|
||
_dbContext.GoodsItems.Remove(prize);
|
||
|
||
// 更新商品奖品数量
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId);
|
||
if (goods != null)
|
||
{
|
||
goods.PrizeNum = await _dbContext.GoodsItems.CountAsync(gi => gi.GoodsId == goodsId) - 1;
|
||
goods.UpdatedAt = DateTime.Now;
|
||
}
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("删除奖品成功: PrizeId={PrizeId}, GoodsId={GoodsId}", prizeId, goodsId);
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 盒子类型管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<List<GoodsTypeDto>> GetGoodsTypesAsync()
|
||
{
|
||
var types = await _dbContext.GoodsTypes
|
||
.AsNoTracking()
|
||
.OrderBy(t => t.SortOrder)
|
||
.ToListAsync();
|
||
|
||
return types.Select(t => new GoodsTypeDto
|
||
{
|
||
Id = t.Id,
|
||
Name = t.Name,
|
||
Value = t.Value,
|
||
SortOrder = t.SortOrder,
|
||
IsShow = t.IsShow,
|
||
IsFenlei = t.IsFenlei,
|
||
FlName = t.FlName,
|
||
CornerText = t.CornerText,
|
||
PayWechat = t.PayWechat,
|
||
PayBalance = t.PayBalance,
|
||
PayCurrency = t.PayCurrency,
|
||
PayCurrency2 = t.PayCurrency2,
|
||
PayCoupon = t.PayCoupon,
|
||
IsDeduction = t.IsDeduction,
|
||
Remark = t.Remark
|
||
}).ToList();
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<int> CreateGoodsTypeAsync(GoodsTypeCreateRequest request)
|
||
{
|
||
// 验证类型名称
|
||
if (string.IsNullOrWhiteSpace(request.Name))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型名称不能为空");
|
||
}
|
||
|
||
// 检查类型值是否已存在
|
||
var exists = await _dbContext.GoodsTypes.AnyAsync(t => t.Value == request.Value);
|
||
if (exists)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型Key已存在,请更换");
|
||
}
|
||
|
||
// 验证分类显示设置
|
||
if (request.IsFenlei == 1 && string.IsNullOrWhiteSpace(request.FlName))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "选择分类显示时,必须填写分类名称");
|
||
}
|
||
|
||
var goodsType = new GoodsType
|
||
{
|
||
Name = request.Name,
|
||
Value = request.Value,
|
||
SortOrder = request.SortOrder,
|
||
IsShow = (byte)request.IsShow,
|
||
IsFenlei = (byte)request.IsFenlei,
|
||
FlName = request.FlName ?? string.Empty,
|
||
CornerText = request.CornerText,
|
||
PayWechat = (byte)request.PayWechat,
|
||
PayBalance = (byte)request.PayBalance,
|
||
PayCurrency = (byte)request.PayCurrency,
|
||
PayCurrency2 = (byte)request.PayCurrency2,
|
||
PayCoupon = (byte)request.PayCoupon,
|
||
IsDeduction = (byte)request.IsDeduction,
|
||
Remark = request.Remark
|
||
};
|
||
|
||
_dbContext.GoodsTypes.Add(goodsType);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
_logger.LogInformation("创建盒子类型成功: TypeId={TypeId}, Name={Name}", goodsType.Id, goodsType.Name);
|
||
|
||
return goodsType.Id;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> UpdateGoodsTypeAsync(int typeId, GoodsTypeUpdateRequest request)
|
||
{
|
||
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
|
||
if (goodsType == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
|
||
}
|
||
|
||
// 验证类型名称
|
||
if (string.IsNullOrWhiteSpace(request.Name))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型名称不能为空");
|
||
}
|
||
|
||
// 检查类型值是否已存在(排除自身)
|
||
var exists = await _dbContext.GoodsTypes.AnyAsync(t => t.Value == request.Value && t.Id != typeId);
|
||
if (exists)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "类型Key已存在,请更换");
|
||
}
|
||
|
||
// 验证分类显示设置
|
||
if (request.IsFenlei == 1 && string.IsNullOrWhiteSpace(request.FlName))
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "选择分类显示时,必须填写分类名称");
|
||
}
|
||
|
||
goodsType.Name = request.Name;
|
||
goodsType.Value = request.Value;
|
||
goodsType.SortOrder = request.SortOrder;
|
||
goodsType.IsShow = (byte)request.IsShow;
|
||
goodsType.IsFenlei = (byte)request.IsFenlei;
|
||
goodsType.FlName = request.FlName ?? string.Empty;
|
||
goodsType.CornerText = request.CornerText;
|
||
goodsType.PayWechat = (byte)request.PayWechat;
|
||
goodsType.PayBalance = (byte)request.PayBalance;
|
||
goodsType.PayCurrency = (byte)request.PayCurrency;
|
||
goodsType.PayCurrency2 = (byte)request.PayCurrency2;
|
||
goodsType.PayCoupon = (byte)request.PayCoupon;
|
||
goodsType.IsDeduction = (byte)request.IsDeduction;
|
||
goodsType.Remark = request.Remark;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("更新盒子类型成功: TypeId={TypeId}, Name={Name}", typeId, goodsType.Name);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> DeleteGoodsTypeAsync(int typeId)
|
||
{
|
||
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
|
||
if (goodsType == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
|
||
}
|
||
|
||
// 检查是否有商品使用此类型
|
||
var hasGoods = await _dbContext.Goods.AnyAsync(g => g.Type == goodsType.Value && g.DeletedAt == null);
|
||
if (hasGoods)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "该类型下存在商品,无法删除");
|
||
}
|
||
|
||
_dbContext.GoodsTypes.Remove(goodsType);
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("删除盒子类型成功: TypeId={TypeId}, Name={Name}", typeId, goodsType.Name);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> SetGoodsTypeStatusAsync(int typeId, GoodsTypeStatusRequest request)
|
||
{
|
||
var goodsType = await _dbContext.GoodsTypes.FirstOrDefaultAsync(t => t.Id == typeId);
|
||
if (goodsType == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子类型不存在");
|
||
}
|
||
|
||
// 根据类型更新对应字段
|
||
switch (request.Type.ToLower())
|
||
{
|
||
case "is_show":
|
||
goodsType.IsShow = (byte)request.Value;
|
||
break;
|
||
case "is_fenlei":
|
||
goodsType.IsFenlei = (byte)request.Value;
|
||
break;
|
||
default:
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "无效的状态类型");
|
||
}
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("更新盒子类型状态成功: TypeId={TypeId}, Type={Type}, Value={Value}",
|
||
typeId, request.Type, request.Value);
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Private Helper Methods
|
||
|
||
/// <summary>
|
||
/// 应用商品过滤条件
|
||
/// </summary>
|
||
private IQueryable<Good> ApplyGoodsFilters(IQueryable<Good> query, GoodsListRequest request)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(request.Title))
|
||
{
|
||
query = query.Where(g => g.Title.Contains(request.Title));
|
||
}
|
||
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(g => g.Status == request.Status.Value);
|
||
}
|
||
|
||
// 只有当 Type 有值且大于0时才过滤(0表示"全部")
|
||
if (request.Type.HasValue && request.Type.Value > 0)
|
||
{
|
||
query = query.Where(g => g.Type == request.Type.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射商品到列表响应
|
||
/// </summary>
|
||
private GoodsListResponse MapToGoodsListResponse(Good goods)
|
||
{
|
||
return new GoodsListResponse
|
||
{
|
||
Id = goods.Id,
|
||
Title = goods.Title,
|
||
ImgUrl = goods.ImgUrl,
|
||
Price = goods.Price,
|
||
Type = goods.Type,
|
||
TypeName = BoxTypeNames.GetValueOrDefault(goods.Type, "未知类型"),
|
||
Status = goods.Status,
|
||
Stock = goods.Stock,
|
||
SaleStock = goods.SaleStock,
|
||
Sort = goods.Sort,
|
||
FlwStartTime = goods.FlwStartTime,
|
||
FlwEndTime = goods.FlwEndTime,
|
||
OpenTime = goods.OpenTime,
|
||
ChoujiangXianzhi = goods.ChoujiangXianzhi,
|
||
CreatedAt = goods.CreatedAt,
|
||
UpdatedAt = goods.UpdatedAt
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射商品到详情响应
|
||
/// </summary>
|
||
private GoodsDetailResponse MapToGoodsDetailResponse(Good goods)
|
||
{
|
||
return new GoodsDetailResponse
|
||
{
|
||
Id = goods.Id,
|
||
Title = goods.Title,
|
||
ImgUrl = goods.ImgUrl,
|
||
ImgUrlDetail = goods.ImgUrlDetail,
|
||
Price = goods.Price,
|
||
Type = goods.Type,
|
||
TypeName = BoxTypeNames.GetValueOrDefault(goods.Type, "未知类型"),
|
||
Status = goods.Status,
|
||
Stock = goods.Stock,
|
||
SaleStock = goods.SaleStock,
|
||
Sort = goods.Sort,
|
||
CategoryId = goods.CategoryId,
|
||
DailyLimit = goods.DailyXiangou,
|
||
LockIs = goods.LockIs,
|
||
LockTime = goods.LockTime,
|
||
IntegralIs = goods.IntegralIs,
|
||
ShowIs = goods.ShowIs,
|
||
CouponIs = goods.CouponIs,
|
||
CouponPro = goods.CouponPro,
|
||
FlwStartTime = goods.FlwStartTime,
|
||
FlwEndTime = goods.FlwEndTime,
|
||
OpenTime = goods.OpenTime,
|
||
ChoujiangXianzhi = goods.ChoujiangXianzhi,
|
||
GoodsDescribe = goods.GoodsDescribe,
|
||
NewIs = goods.NewIs,
|
||
IsShouZhe = goods.IsShouZhe,
|
||
RageIs = goods.RageIs,
|
||
Rage = goods.Rage,
|
||
LingzhuIs = goods.LingzhuIs,
|
||
LingzhuFan = goods.LingzhuFan,
|
||
PrizeNum = goods.PrizeNum,
|
||
IsFlw = goods.IsFlw,
|
||
CreatedAt = goods.CreatedAt,
|
||
UpdatedAt = goods.UpdatedAt
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射奖品到DTO
|
||
/// </summary>
|
||
private PrizeDto MapToPrizeDto(GoodsItem prize, Dictionary<int, PrizeLevelInfo>? prizeLevels = null)
|
||
{
|
||
string? shangTitle = null;
|
||
string? shangColor = null;
|
||
|
||
if (prize.ShangId.HasValue && prizeLevels != null && prizeLevels.TryGetValue(prize.ShangId.Value, out var level))
|
||
{
|
||
shangTitle = level.Title;
|
||
shangColor = level.Color;
|
||
}
|
||
|
||
return new PrizeDto
|
||
{
|
||
Id = prize.Id,
|
||
GoodsId = prize.GoodsId,
|
||
Num = prize.Num,
|
||
Title = prize.Title,
|
||
ImgUrl = prize.ImgUrl,
|
||
ImgUrlDetail = prize.ImgUrlDetail,
|
||
Stock = prize.Stock,
|
||
SurplusStock = prize.SurplusStock,
|
||
Price = prize.Price,
|
||
Money = prize.Money,
|
||
ScMoney = prize.ScMoney,
|
||
RealPro = prize.RealPro,
|
||
GoodsType = prize.GoodsType,
|
||
Sort = prize.Sort,
|
||
ShangId = prize.ShangId,
|
||
ShangTitle = shangTitle,
|
||
ShangColor = shangColor,
|
||
RewardNum = prize.RewardNum,
|
||
Rank = prize.Rank,
|
||
GiveMoney = prize.GiveMoney,
|
||
CardNo = prize.CardNo,
|
||
PrizeCode = prize.PrizeCode,
|
||
Type = prize.Type,
|
||
LianJiType = prize.LianJiType,
|
||
RewardId = prize.RewardId,
|
||
Doubling = prize.Doubling,
|
||
IsLingzhu = prize.IsLingzhu,
|
||
CreatedAt = prize.CreatedAt,
|
||
UpdatedAt = prize.UpdatedAt
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成唯一奖品编码
|
||
/// </summary>
|
||
public static string GeneratePrizeCode()
|
||
{
|
||
return $"PC{DateTime.Now:yyyyMMddHHmmss}{Guid.NewGuid().ToString("N")[..8].ToUpper()}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 库存增加时复制奖品配置
|
||
/// </summary>
|
||
private async Task CopyPrizeConfigurationsAsync(int goodsId, int stockIncrease)
|
||
{
|
||
// 获取第一套奖品配置(作为模板)
|
||
var templatePrizes = await _dbContext.GoodsItems
|
||
.AsNoTracking()
|
||
.Where(gi => gi.GoodsId == goodsId)
|
||
.OrderBy(gi => gi.Num)
|
||
.ToListAsync();
|
||
|
||
if (!templatePrizes.Any())
|
||
{
|
||
return; // 没有奖品配置,无需复制
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
var newPrizes = new List<GoodsItem>();
|
||
|
||
// 获取当前最大编号
|
||
var maxNum = templatePrizes.Max(p => p.Num);
|
||
|
||
// 为每个新增的库存复制奖品配置
|
||
for (int i = 0; i < stockIncrease; i++)
|
||
{
|
||
foreach (var template in templatePrizes)
|
||
{
|
||
var newPrize = new GoodsItem
|
||
{
|
||
GoodsId = goodsId,
|
||
Num = maxNum + 1 + (i * templatePrizes.Count) + templatePrizes.IndexOf(template),
|
||
Title = template.Title,
|
||
ImgUrl = template.ImgUrl,
|
||
ImgUrlDetail = template.ImgUrlDetail,
|
||
Stock = template.Stock,
|
||
SurplusStock = template.Stock,
|
||
Price = template.Price,
|
||
Money = template.Money,
|
||
ScMoney = template.ScMoney,
|
||
RealPro = template.RealPro,
|
||
GoodsType = template.GoodsType,
|
||
Sort = template.Sort,
|
||
ShangId = template.ShangId,
|
||
RewardNum = template.RewardNum,
|
||
Rank = template.Rank,
|
||
GiveMoney = template.GiveMoney,
|
||
CardNo = template.CardNo,
|
||
PrizeCode = GeneratePrizeCode(),
|
||
Type = template.Type,
|
||
LianJiType = template.LianJiType,
|
||
RewardId = template.RewardId,
|
||
Doubling = template.Doubling,
|
||
IsLingzhu = template.IsLingzhu,
|
||
PrizeNum = template.PrizeNum,
|
||
GoodsListId = template.GoodsListId,
|
||
SpecialStock = template.SpecialStock,
|
||
CreatedAt = now,
|
||
UpdatedAt = now
|
||
};
|
||
newPrizes.Add(newPrize);
|
||
}
|
||
}
|
||
|
||
if (newPrizes.Any())
|
||
{
|
||
_dbContext.GoodsItems.AddRange(newPrizes);
|
||
_logger.LogInformation("复制奖品配置: GoodsId={GoodsId}, StockIncrease={StockIncrease}, NewPrizes={NewPrizes}",
|
||
goodsId, stockIncrease, newPrizes.Count);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 记录商品操作日志
|
||
/// </summary>
|
||
private async Task LogGoodsOperationAsync(int goodsId, string operation, string content, int operatorId)
|
||
{
|
||
var log = new AdminOperationLog
|
||
{
|
||
AdminId = operatorId,
|
||
Operation = $"goods:{operation}",
|
||
Content = $"GoodsId={goodsId}, {content}",
|
||
Ip = string.Empty,
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
|
||
_dbContext.AdminOperationLogs.Add(log);
|
||
await _dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 盒子扩展设置
|
||
|
||
/// <inheritdoc />
|
||
public async Task<GoodsExtendDto> GetGoodsExtendAsync(int goodsId)
|
||
{
|
||
// 验证盒子存在
|
||
var goods = await _dbContext.Goods
|
||
.AsNoTracking()
|
||
.Where(g => g.Id == goodsId && g.DeletedAt == null)
|
||
.Select(g => new { g.Id, g.Type })
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
|
||
}
|
||
|
||
// 1. 先尝试从goods_extensions表获取盒子特定配置
|
||
var goodsExtend = await _dbContext.GoodsExtensions
|
||
.AsNoTracking()
|
||
.Where(ge => ge.GoodsId == goodsId)
|
||
.Select(ge => new GoodsExtendDto
|
||
{
|
||
Id = ge.Id,
|
||
GoodsId = ge.GoodsId,
|
||
PayWechat = ge.PayWechat,
|
||
PayBalance = ge.PayBalance,
|
||
PayCurrency = ge.PayCurrency,
|
||
PayCurrency2 = ge.PayCurrency2,
|
||
PayCoupon = ge.PayCoupon,
|
||
IsDeduction = ge.IsDeduction,
|
||
IsInherited = false
|
||
})
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (goodsExtend != null)
|
||
{
|
||
return goodsExtend;
|
||
}
|
||
|
||
// 2. 如果没有盒子特定配置,从goods_types表获取类型默认配置
|
||
var typeConfig = await _dbContext.GoodsTypes
|
||
.AsNoTracking()
|
||
.Where(gt => gt.Value == goods.Type)
|
||
.Select(gt => new GoodsExtendDto
|
||
{
|
||
Id = 0,
|
||
GoodsId = goodsId,
|
||
PayWechat = gt.PayWechat,
|
||
PayBalance = gt.PayBalance,
|
||
PayCurrency = gt.PayCurrency,
|
||
PayCurrency2 = gt.PayCurrency2,
|
||
PayCoupon = gt.PayCoupon,
|
||
IsDeduction = gt.IsDeduction,
|
||
IsInherited = true
|
||
})
|
||
.FirstOrDefaultAsync();
|
||
|
||
if (typeConfig != null)
|
||
{
|
||
return typeConfig;
|
||
}
|
||
|
||
// 3. 如果都没有找到,返回默认值(全部启用)
|
||
return new GoodsExtendDto
|
||
{
|
||
Id = 0,
|
||
GoodsId = goodsId,
|
||
PayWechat = 1,
|
||
PayBalance = 1,
|
||
PayCurrency = 1,
|
||
PayCurrency2 = 1,
|
||
PayCoupon = 1,
|
||
IsDeduction = 1,
|
||
IsInherited = true
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> UpdateGoodsExtendAsync(int goodsId, GoodsExtendUpdateRequest request)
|
||
{
|
||
// 验证盒子存在
|
||
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (!goodsExists)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
|
||
}
|
||
|
||
// 查找现有扩展配置
|
||
var existingExtend = await _dbContext.GoodsExtensions
|
||
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
|
||
|
||
if (existingExtend != null)
|
||
{
|
||
// 更新现有配置
|
||
existingExtend.PayWechat = (byte)request.PayWechat;
|
||
existingExtend.PayBalance = (byte)request.PayBalance;
|
||
existingExtend.PayCurrency = (byte)request.PayCurrency;
|
||
existingExtend.PayCurrency2 = (byte)request.PayCurrency2;
|
||
existingExtend.PayCoupon = (byte)request.PayCoupon;
|
||
existingExtend.IsDeduction = (byte)request.IsDeduction;
|
||
}
|
||
else
|
||
{
|
||
// 创建新配置
|
||
var newExtend = new GoodsExtension
|
||
{
|
||
GoodsId = goodsId,
|
||
PayWechat = (byte)request.PayWechat,
|
||
PayBalance = (byte)request.PayBalance,
|
||
PayCurrency = (byte)request.PayCurrency,
|
||
PayCurrency2 = (byte)request.PayCurrency2,
|
||
PayCoupon = (byte)request.PayCoupon,
|
||
IsDeduction = (byte)request.IsDeduction
|
||
};
|
||
_dbContext.GoodsExtensions.Add(newExtend);
|
||
}
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("更新盒子扩展设置: GoodsId={GoodsId}", goodsId);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> DeleteGoodsExtendAsync(int goodsId)
|
||
{
|
||
// 验证盒子存在
|
||
var goodsExists = await _dbContext.Goods.AnyAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (!goodsExists)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
|
||
}
|
||
|
||
// 查找并删除扩展配置
|
||
var existingExtend = await _dbContext.GoodsExtensions
|
||
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
|
||
|
||
if (existingExtend == null)
|
||
{
|
||
// 没有独立配置,无需删除
|
||
return true;
|
||
}
|
||
|
||
_dbContext.GoodsExtensions.Remove(existingExtend);
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
_logger.LogInformation("删除盒子扩展设置: GoodsId={GoodsId}", goodsId);
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 盒子操作
|
||
|
||
/// <inheritdoc />
|
||
public async Task<int> CopyGoodsAsync(int goodsId, int operatorId)
|
||
{
|
||
// 获取源盒子
|
||
var sourceGoods = await _dbContext.Goods
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
|
||
if (sourceGoods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
|
||
// 创建新盒子(复制基本信息)
|
||
var newGoods = new Good
|
||
{
|
||
Title = $"{sourceGoods.Title}_副本",
|
||
Price = sourceGoods.Price,
|
||
Type = sourceGoods.Type,
|
||
ImgUrl = sourceGoods.ImgUrl,
|
||
ImgUrlDetail = sourceGoods.ImgUrlDetail,
|
||
Stock = sourceGoods.Stock,
|
||
SaleStock = 0, // 新盒子销量为0
|
||
Sort = sourceGoods.Sort,
|
||
DailyXiangou = sourceGoods.DailyXiangou,
|
||
LockIs = sourceGoods.LockIs,
|
||
LockTime = sourceGoods.LockTime,
|
||
IntegralIs = sourceGoods.IntegralIs,
|
||
ShowIs = sourceGoods.ShowIs,
|
||
CouponIs = sourceGoods.CouponIs,
|
||
CouponPro = sourceGoods.CouponPro,
|
||
FlwStartTime = sourceGoods.FlwStartTime,
|
||
FlwEndTime = sourceGoods.FlwEndTime,
|
||
OpenTime = sourceGoods.OpenTime,
|
||
ChoujiangXianzhi = sourceGoods.ChoujiangXianzhi,
|
||
CategoryId = sourceGoods.CategoryId,
|
||
GoodsDescribe = sourceGoods.GoodsDescribe,
|
||
NewIs = sourceGoods.NewIs,
|
||
IsShouZhe = sourceGoods.IsShouZhe,
|
||
RageIs = sourceGoods.RageIs,
|
||
Rage = sourceGoods.Rage,
|
||
LingzhuIs = sourceGoods.LingzhuIs,
|
||
LingzhuFan = sourceGoods.LingzhuFan,
|
||
Status = 0, // 新盒子默认下架
|
||
PrizeNum = 0,
|
||
IsFlw = sourceGoods.IsFlw,
|
||
QuanjuXiangou = sourceGoods.QuanjuXiangou,
|
||
IsAutoXiajia = sourceGoods.IsAutoXiajia,
|
||
XiajiaLirun = sourceGoods.XiajiaLirun,
|
||
XiajiaAutoCoushu = sourceGoods.XiajiaAutoCoushu,
|
||
XiajiaJine = sourceGoods.XiajiaJine,
|
||
UnlockAmount = sourceGoods.UnlockAmount,
|
||
ItemCardId = sourceGoods.ItemCardId,
|
||
LianJiNum = sourceGoods.LianJiNum,
|
||
LianJiShangId = sourceGoods.LianJiShangId,
|
||
LingzhuShangId = sourceGoods.LingzhuShangId,
|
||
CreatedAt = now,
|
||
UpdatedAt = now
|
||
};
|
||
|
||
_dbContext.Goods.Add(newGoods);
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
// 复制奖品
|
||
var sourcePrizes = await _dbContext.GoodsItems
|
||
.AsNoTracking()
|
||
.Where(gi => gi.GoodsId == goodsId)
|
||
.ToListAsync();
|
||
|
||
if (sourcePrizes.Any())
|
||
{
|
||
var newPrizes = sourcePrizes.Select(p => new GoodsItem
|
||
{
|
||
GoodsId = newGoods.Id,
|
||
Num = p.Num,
|
||
Title = p.Title,
|
||
ImgUrl = p.ImgUrl,
|
||
ImgUrlDetail = p.ImgUrlDetail,
|
||
Stock = p.Stock,
|
||
SurplusStock = p.Stock, // 重置剩余库存
|
||
Price = p.Price,
|
||
Money = p.Money,
|
||
ScMoney = p.ScMoney,
|
||
RealPro = p.RealPro,
|
||
GoodsType = p.GoodsType,
|
||
Sort = p.Sort,
|
||
ShangId = p.ShangId,
|
||
RewardNum = p.RewardNum,
|
||
Rank = p.Rank,
|
||
GiveMoney = p.GiveMoney,
|
||
CardNo = p.CardNo,
|
||
PrizeCode = GeneratePrizeCode(),
|
||
Type = p.Type,
|
||
LianJiType = p.LianJiType,
|
||
RewardId = p.RewardId,
|
||
Doubling = p.Doubling,
|
||
IsLingzhu = p.IsLingzhu,
|
||
PrizeNum = p.PrizeNum,
|
||
GoodsListId = 0,
|
||
SpecialStock = p.SpecialStock,
|
||
CreatedAt = now,
|
||
UpdatedAt = now
|
||
}).ToList();
|
||
|
||
_dbContext.GoodsItems.AddRange(newPrizes);
|
||
|
||
// 更新新盒子的奖品数量
|
||
newGoods.PrizeNum = newPrizes.Count;
|
||
}
|
||
|
||
// 复制扩展设置(如果有)
|
||
var sourceExtend = await _dbContext.GoodsExtensions
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(ge => ge.GoodsId == goodsId);
|
||
|
||
if (sourceExtend != null)
|
||
{
|
||
var newExtend = new GoodsExtension
|
||
{
|
||
GoodsId = newGoods.Id,
|
||
PayWechat = sourceExtend.PayWechat,
|
||
PayBalance = sourceExtend.PayBalance,
|
||
PayCurrency = sourceExtend.PayCurrency,
|
||
PayCurrency2 = sourceExtend.PayCurrency2,
|
||
PayCoupon = sourceExtend.PayCoupon,
|
||
IsDeduction = sourceExtend.IsDeduction
|
||
};
|
||
_dbContext.GoodsExtensions.Add(newExtend);
|
||
}
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
|
||
// 记录操作日志
|
||
await LogGoodsOperationAsync(newGoods.Id, "copy", $"复制盒子: 源ID={goodsId}, 新ID={newGoods.Id}", operatorId);
|
||
|
||
_logger.LogInformation("复制盒子成功: SourceId={SourceId}, NewId={NewId}, Operator={Operator}",
|
||
goodsId, newGoods.Id, operatorId);
|
||
|
||
return newGoods.Id;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> ClearGoodsLotteryAsync(int goodsId, int operatorId)
|
||
{
|
||
// 验证盒子存在
|
||
var goods = await _dbContext.Goods.FirstOrDefaultAsync(g => g.Id == goodsId && g.DeletedAt == null);
|
||
if (goods == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "盒子不存在");
|
||
}
|
||
|
||
// 使用事务确保数据一致性
|
||
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
||
|
||
try
|
||
{
|
||
// 1. 重置盒子销量
|
||
goods.SaleStock = 0;
|
||
goods.UpdatedAt = DateTime.Now;
|
||
|
||
// 2. 重置所有奖品的剩余库存
|
||
var prizes = await _dbContext.GoodsItems
|
||
.Where(gi => gi.GoodsId == goodsId)
|
||
.ToListAsync();
|
||
|
||
foreach (var prize in prizes)
|
||
{
|
||
prize.SurplusStock = prize.Stock;
|
||
prize.UpdatedAt = DateTime.Now;
|
||
}
|
||
|
||
// 3. 删除抽奖记录(如果有lottery_records表)
|
||
// 注意:这里需要根据实际的抽奖记录表结构来实现
|
||
// 暂时只重置库存,抽奖记录的清理可能需要额外处理
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
await transaction.CommitAsync();
|
||
|
||
// 记录操作日志
|
||
await LogGoodsOperationAsync(goodsId, "clear_lottery", $"清空抽奖记录: {goods.Title}", operatorId);
|
||
|
||
_logger.LogInformation("清空盒子抽奖记录: GoodsId={GoodsId}, Operator={Operator}", goodsId, operatorId);
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await transaction.RollbackAsync();
|
||
_logger.LogError(ex, "清空盒子抽奖记录失败: GoodsId={GoodsId}", goodsId);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|