using System.ComponentModel.DataAnnotations; using MAX.Framework.Commons; using MAX.Framework.Validation; using Store.Shops.Dtos; using Store.Shops.Entities; using Store.Shops.Mappers; using YourNamespace; namespace Store.Shops.Services; public sealed partial class ShopService { /// 选品列表跨月时重置300条额度,并同步店铺表;同店多条登录记录只处理一次。 private async Task RefreshQuotas(List shops, CancellationToken ct) { using var work = db.CreateUnitOfWork(); work.IsolationLevel = System.Data.IsolationLevel.Serializable; work.GetOrBeginTransaction(); var orm = work.Orm; // shortcut: 旧表仅保存月份;需要区分跨年同月额度时升级为年月字段。 var month = DateTime.Now.Month; foreach (var group in shops.Where(s => !string.IsNullOrEmpty(s.ShopId)).GroupBy(s => s.ShopId).OrderBy(g => g.Key, StringComparer.Ordinal)) { var quota = await orm.Select().Where(q => q.ShopId == group.Key).ForUpdate().FirstAsync(ct); if (quota == null) { quota = new() { ShopId = group.Key, ShopAccount = group.First().ShopAccount, AddProductMonth = month, AddProductRemaining = 300 }; await orm.Insert(quota).ExecuteAffrowsAsync(ct); } else if (quota.AddProductMonth != month) { quota.AddProductMonth = month; quota.AddProductRemaining = 300; await orm.Update().Where(q => q.ShopId == group.Key) .Set(q => q.AddProductMonth, month).Set(q => q.AddProductRemaining, 300).ExecuteAffrowsAsync(ct); } await orm.Update().Where(s => s.ShopId == group.Key) .Set(s => s.AddProductMonth, (byte)quota.AddProductMonth) .Set(s => s.AddProductRemaining, quota.AddProductRemaining).ExecuteAffrowsAsync(ct); foreach (var item in group) { item.AddProductMonth = (byte)quota.AddProductMonth; item.AddProductRemaining = quota.AddProductRemaining; } } ct.ThrowIfCancellationRequested(); work.Commit(); } /// 管理员设置明确指定店铺的共享额度,同步列表读取的店铺字段。 public async Task UpdateQuota(QuotaRequest request, CancellationToken ct) { RequestValidator.Validate(request); Ids(request.ShopIds); if (request.RemainCount < 0) throw new ValidationException("剩余额度不能小于0"); RequireAdmin(await Actor()); using var work = db.CreateUnitOfWork(); work.IsolationLevel = System.Data.IsolationLevel.Serializable; work.GetOrBeginTransaction(); var orm = work.Orm; var existing = await orm.Select().Where(q => request.ShopIds.Contains(q.ShopId)).ForUpdate().ToListAsync(ct); if (request.ShopIds.Except(existing.Select(q => q.ShopId)).Any()) throw new AppException("部分店铺尚未建立额度记录"); await orm.Update().Where(q => request.ShopIds.Contains(q.ShopId)) .Set(q => q.AddProductRemaining, request.RemainCount).ExecuteAffrowsAsync(ct); await orm.Update().Where(s => request.ShopIds.Contains(s.ShopId)) .Set(s => s.AddProductRemaining, request.RemainCount).ExecuteAffrowsAsync(ct); ct.ThrowIfCancellationRequested(); work.Commit(); } /// 管理员按记录ID或登录名分配选品操作员,空账号表示解除分配。 public async Task Assign(AssignRequest request, CancellationToken ct) { RequestValidator.Validate(request); if (request.DbIds is not { Length: > 0 } && request.ShopLoginNames is not { Length: > 0 }) throw new ValidationException("请选择店铺记录或登录名"); if (request.DbIds?.Any(id => id <= 0) == true) throw new ValidationException("记录ID必须大于0"); if (request.ShopLoginNames != null) Ids(request.ShopLoginNames); RequireAdmin(await Actor()); if (request.OperateSubSaName != "") await users.GetUser(request.OperateSubSaName); var update = db.Update(); if (request.DbIds is { Length: > 0 }) update.Where(s => request.DbIds.Contains(s.Id)); else update.Where(s => request.ShopLoginNames!.Contains(s.LoginName)); if (await update.Set(s => s.OperateSubSaName, request.OperateSubSaName) .Set(s => s.AddSubSaTime, DateTime.Now).ExecuteAffrowsAsync(ct) == 0) throw new AppException("未找到指定店铺"); } /// 查询授权店铺对应的ERP商户余额,不能仅凭外部商户ID读取他人余额。 public async Task> ErpMerchants(ErpRequest request, CancellationToken ct) { RequestValidator.Validate(request); Page(request.Page, request.PageSize); if (request.CorporateAccountUserId == null && request.ManageUserId == null) throw new ValidationException("必须指定公司账号或管理账号"); if (request.CorporateAccountUserId <= 0 || request.ManageUserId <= 0) throw new ValidationException("ERP账号ID必须大于0"); var actor = await Actor(); var ids = await VisibleIds(actor, ct); var linked = await db.Select().Where(s => ids.Contains(s.AppKey)) .WhereIf(!string.IsNullOrEmpty(request.ShopId), s => s.AppKey == request.ShopId).ToListAsync(ct); var managers = new List(); if (request.ManageUserId.HasValue) { if (!linked.Any(s => s.ManageUserId == request.ManageUserId)) throw new AppException("无权查询该ERP管理账号"); managers.Add(request.ManageUserId.Value); } if (request.CorporateAccountUserId.HasValue) { if (!linked.Any(s => s.CorporateAccountUserId == request.CorporateAccountUserId)) throw new AppException("无权查询该ERP公司账号"); managers.AddRange(await db.Select() .Where(g => g.CorporateAccountUserId == request.CorporateAccountUserId) .ToListAsync(g => g.ManageUserId, ct)); } var query = db.Select().Where(a => managers.Contains(a.UserId)); var count = request.NotCount ? -1 : await query.CountAsync(ct); var rows = await query.OrderByDescending(a => a.Balance).OrderBy(a => a.Id).Page(request.Page, request.PageSize).ToListAsync(ct); return new(rows.Select(new ShopDetailsMapper().Map).ToList(), count); } }