admin/src/Oms.Application/Services/Orders/PlatformStoreService.cs
zzh fb61fbb072 修复仓储/店铺列表 ConcurrencyStamp 缺失与可空客户ID的 SQL 翻译错误。
补迁移脚本为 C_* 与 t_sales_* 表增加 ConcurrencyStamp;列表查询改用 hasCustomerFilter 模式避免 ORM 生成非法 SQL。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 16:48:05 +08:00

217 lines
8.3 KiB
C#

using System.Linq.Expressions;
using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.PlatformStore;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.WarehouseAggregate.Consts;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 店铺授权 API 服务。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Order")]
public class PlatformStoreService : ApplicationService
{
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
private ISalesStoreRepository StoreRepository => LazyServiceProvider.GetRequiredService<ISalesStoreRepository>();
/// <summary>
/// 查询店铺授权列表。
/// </summary>
/// <param name="input">入参:店铺分页查询条件。</param>
/// <returns>店铺分页结果。</returns>
[HttpPost("/order/platform-store/list")]
public async Task<OmsPageResult<PlatformStoreOutput>> GetListAsync(PlatformStorePageInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var predicate = BuildListPredicate(customerId, input);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await StoreRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary<string, bool> { [nameof(SalesStore.UpdateTime)] = false, [nameof(SalesStore.Id)] = false });
var total = await StoreRepository.CountAsync(predicate);
return new OmsPageResult<PlatformStoreOutput>
{
Items = entities.Select(MapOutput).ToList(),
Total = total
};
}
/// <summary>
/// 获取店铺 OAuth 授权地址。
/// </summary>
/// <param name="input">入参:平台编码与回调地址。</param>
/// <returns>授权跳转 URL。</returns>
[HttpPost("/order/platform-store/auth-url")]
public async Task<PlatformStoreAuthUrlOutput> GetAuthUrlAsync(PlatformStoreAuthUrlInput input)
{
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var platform = input.PlatformCode.Trim().ToLowerInvariant();
var redirect = string.IsNullOrWhiteSpace(input.RedirectUri)
? "https://localhost/oauth/callback"
: input.RedirectUri.Trim();
return new PlatformStoreAuthUrlOutput
{
AuthUrl = $"https://oauth.example.com/{platform}?redirect={Uri.EscapeDataString(redirect)}",
Message = "OAuth 对接待平台 SDK 接入,当前返回占位地址"
};
}
/// <summary>
/// 保存店铺授权(新增或编辑)。
/// </summary>
/// <param name="input">入参:店铺保存数据。</param>
/// <returns>保存后的店铺信息。</returns>
[HttpPost("/order/platform-store/save")]
public async Task<PlatformStoreOutput> SaveAsync(PlatformStoreSaveInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var nickname = input.StoreNickname.Trim();
if (string.IsNullOrWhiteSpace(nickname))
{
throw new BusinessException("店铺昵称不能为空");
}
SalesStore entity;
if (input.Id > 0)
{
entity = await GetStoreOrThrowAsync(customerId, input.Id);
}
else
{
entity = new SalesStore
{
CustomerId = customerId,
PlatformCode = string.IsNullOrWhiteSpace(input.PlatformCode) ? "manual" : input.PlatformCode.Trim(),
AuthStatus = SalesStoreAuthStatusCodes.Active,
AuthTime = DateTime.Now
};
}
entity.StoreNickname = nickname;
entity.PlatformAccount = string.IsNullOrWhiteSpace(input.PlatformAccount) ? null : input.PlatformAccount.Trim();
entity.SiteCountry = string.IsNullOrWhiteSpace(input.SiteCountry) ? null : input.SiteCountry.Trim();
entity.IsAutoFulfill = input.IsAutoFulfill;
entity.Remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
if (input.Id > 0)
{
await StoreRepository.UpdateAsync(entity);
}
else
{
await StoreRepository.InsertAsync(entity);
}
return MapOutput(entity);
}
/// <summary>
/// 删除店铺授权。
/// </summary>
/// <param name="input">入参:店铺 ID 列表。</param>
[HttpPost("/order/platform-store/delete")]
public async Task DeleteAsync(PlatformStoreIdsInput input)
{
if (input.Ids.Count == 0)
{
throw new BusinessException("请选择要删除的店铺");
}
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
var entities = await StoreRepository.GetListAsync(x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue) && input.Ids.Contains(x.Id));
foreach (var entity in entities)
{
await StoreRepository.DeleteAsync(entity);
}
}
private async Task<SalesStore> GetStoreOrThrowAsync(int? customerId, int id)
{
var entity = customerId.HasValue
? await StoreRepository.FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId.Value)
: await StoreRepository.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null)
{
throw new BusinessException("店铺不存在");
}
return entity;
}
private static Expression<Func<SalesStore, bool>> BuildListPredicate(int? customerId, PlatformStorePageInput input)
{
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
var platformCode = input.PlatformCode?.Trim();
var nickname = input.StoreNickname?.Trim();
var filter = input.ExpirationFilter?.Trim().ToLowerInvariant();
var now = DateTime.Now;
var within7Days = now.AddDays(7);
return x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (string.IsNullOrWhiteSpace(platformCode) || x.PlatformCode == platformCode)
&& (string.IsNullOrWhiteSpace(nickname) || x.StoreNickname.Contains(nickname))
&& (string.IsNullOrWhiteSpace(filter) || filter == "normal"
? string.IsNullOrWhiteSpace(filter) || !x.TokenExpireTime.HasValue || x.TokenExpireTime > within7Days
: filter == "expired"
? x.TokenExpireTime.HasValue && x.TokenExpireTime < now
: filter == "within7days"
? x.TokenExpireTime.HasValue && x.TokenExpireTime >= now && x.TokenExpireTime <= within7Days
: true);
}
private static PlatformStoreOutput MapOutput(SalesStore entity) =>
new()
{
Id = entity.Id,
CustomerId = entity.CustomerId,
PlatformCode = entity.PlatformCode,
PlatformName = ResolvePlatformName(entity.PlatformCode),
StoreNickname = entity.StoreNickname,
PlatformAccount = entity.PlatformAccount,
SiteCountry = entity.SiteCountry,
AuthStatus = entity.AuthStatus,
AuthTime = entity.AuthTime,
TokenExpireTime = entity.TokenExpireTime,
IsAutoFulfill = entity.IsAutoFulfill,
Remark = entity.Remark,
CreateTime = entity.CreateTime
};
private static string ResolvePlatformName(string platformCode) => platformCode.ToLowerInvariant() switch
{
"lazada" => "Lazada",
"shopee" => "Shopee",
"tiktok" => "TikTok",
"temu" => "Temu",
"manual" => "手工店铺",
_ => platformCode
};
}