admin/src/Oms.Application/Services/Orders/OrderService.SenderAddress.cs
zzh 674cbdd011 feat: 店铺真授权跳转与地址类型(发件/退件/收件)
对接 Lazada/Shopee/TikTok/Temu OAuth 与回调落库,移除手工店;地址管理支持按类型默认与揽收校验。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 10:25:13 +08:00

285 lines
11 KiB
C#

using System.Linq.Expressions;
using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Orders;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
private ICustomerSenderAddressRepository CustomerSenderAddressRepository =>
LazyServiceProvider.GetRequiredService<ICustomerSenderAddressRepository>();
/// <summary>
/// 客户地址分页列表(发件/退件/收件)。
/// </summary>
[HttpPost("/order/sender-address/list")]
public async Task<OmsPageResult<CustomerSenderAddressOutput>> GetSenderAddressListAsync(
[FromBody] CustomerSenderAddressPaginatedInput input)
{
var customer = await OmsSessionHelper.ResolveSessionCustomerAsync(CustomerRepository, CurrentUser);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var predicate = BuildSenderAddressPredicate(customer.Id, input);
var entities = await CustomerSenderAddressRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary<string, bool>
{
[nameof(CustomerSenderAddress.IsDefault)] = false,
[nameof(CustomerSenderAddress.Id)] = false
});
var total = await CustomerSenderAddressRepository.CountAsync(predicate);
var countryMap = await LoadCountryCodeMapAsync(entities.Select(x => x.Country).Where(x => x.HasValue).Select(x => x!.Value));
return new OmsPageResult<CustomerSenderAddressOutput>
{
Items = entities.Select(x => MapSenderAddressOutput(x, countryMap)).ToList(),
Total = total
};
}
/// <summary>
/// 客户地址详情。
/// </summary>
[HttpGet("/order/sender-address/detail")]
public async Task<CustomerSenderAddressOutput> GetSenderAddressDetailAsync([FromQuery] int id)
{
var entity = await GetOwnedSenderAddressOrThrowAsync(id);
var countryMap = await LoadCountryCodeMapAsync(entity.Country.HasValue ? [entity.Country.Value] : []);
return MapSenderAddressOutput(entity, countryMap);
}
/// <summary>
/// 新增客户地址。
/// </summary>
[HttpPost("/order/sender-address/create")]
public async Task<object> CreateSenderAddressAsync([FromBody] CreateCustomerSenderAddressInput input)
{
var customer = await OmsSessionHelper.ResolveSessionCustomerAsync(CustomerRepository, CurrentUser);
var addressType = NormalizeAddressTypeOrThrow(input.AddressType);
ValidateSenderAddressInput(input);
var countryId = await ResolveSenderCountryIdAsync(input.CountryCode);
if (input.IsDefault)
{
await CustomerSenderAddressRepository.ClearDefaultAsync(customer.Id, addressType);
}
var entity = new CustomerSenderAddress
{
CustomerId = customer.Id,
AddressType = addressType,
AddressName = TrimOrNull(input.AddressName),
SenderName = input.SenderName.Trim(),
Company = TrimOrNull(input.Company),
Country = countryId,
Province = TrimOrNull(input.Province),
City = TrimOrNull(input.City),
ZipCode = TrimOrNull(input.ZipCode),
StreetAddress1 = input.StreetAddress1.Trim(),
StreetAddress2 = TrimOrNull(input.StreetAddress2),
StreetAddress3 = TrimOrNull(input.StreetAddress3),
HouseNumber = TrimOrNull(input.HouseNumber),
Phone = TrimOrNull(input.Phone),
TelePhone = TrimOrNull(input.TelePhone),
Email = TrimOrNull(input.Email),
IsDefault = input.IsDefault ? (byte)1 : (byte)0
};
entity = await CustomerSenderAddressRepository.InsertAsync(entity);
return new { id = entity.Id };
}
/// <summary>
/// 更新客户地址。
/// </summary>
[HttpPost("/order/sender-address/update")]
public async Task UpdateSenderAddressAsync([FromBody] UpdateCustomerSenderAddressInput input)
{
var entity = await GetOwnedSenderAddressOrThrowAsync(input.Id);
var addressType = NormalizeAddressTypeOrThrow(input.AddressType);
ValidateSenderAddressInput(input);
var countryId = await ResolveSenderCountryIdAsync(input.CountryCode);
if (input.IsDefault)
{
await CustomerSenderAddressRepository.ClearDefaultAsync(entity.CustomerId, addressType, entity.Id);
}
entity.AddressType = addressType;
entity.AddressName = TrimOrNull(input.AddressName);
entity.SenderName = input.SenderName.Trim();
entity.Company = TrimOrNull(input.Company);
entity.Country = countryId;
entity.Province = TrimOrNull(input.Province);
entity.City = TrimOrNull(input.City);
entity.ZipCode = TrimOrNull(input.ZipCode);
entity.StreetAddress1 = input.StreetAddress1.Trim();
entity.StreetAddress2 = TrimOrNull(input.StreetAddress2);
entity.StreetAddress3 = TrimOrNull(input.StreetAddress3);
entity.HouseNumber = TrimOrNull(input.HouseNumber);
entity.Phone = TrimOrNull(input.Phone);
entity.TelePhone = TrimOrNull(input.TelePhone);
entity.Email = TrimOrNull(input.Email);
entity.IsDefault = input.IsDefault ? (byte)1 : (byte)0;
await CustomerSenderAddressRepository.UpdateAsync(entity);
}
/// <summary>
/// 删除客户地址(软删)。
/// </summary>
[HttpPost("/order/sender-address/delete")]
public async Task DeleteSenderAddressAsync([FromBody] IdInput input)
{
var entity = await GetOwnedSenderAddressOrThrowAsync(input.Id);
await CustomerSenderAddressRepository.DeleteAsync(entity);
}
/// <summary>
/// 设为同类型默认地址。
/// </summary>
[HttpPost("/order/sender-address/set-default")]
public async Task SetDefaultSenderAddressAsync([FromBody] IdInput input)
{
var entity = await GetOwnedSenderAddressOrThrowAsync(input.Id);
var addressType = string.IsNullOrWhiteSpace(entity.AddressType)
? CustomerAddressTypeCodes.Sender
: entity.AddressType;
await CustomerSenderAddressRepository.ClearDefaultAsync(entity.CustomerId, addressType, entity.Id);
entity.IsDefault = 1;
await CustomerSenderAddressRepository.UpdateAsync(entity);
}
private async Task<CustomerSenderAddress> GetOwnedSenderAddressOrThrowAsync(int id)
{
var customer = await OmsSessionHelper.ResolveSessionCustomerAsync(CustomerRepository, CurrentUser);
var entity = await CustomerSenderAddressRepository.GetOwnedAsync(id, customer.Id);
if (entity == null)
{
throw new BusinessException("地址不存在");
}
return entity;
}
private static string NormalizeAddressTypeOrThrow(string? addressType)
{
try
{
return CustomerAddressTypeCodes.NormalizeOrThrow(addressType);
}
catch (ArgumentException)
{
throw new BusinessException("地址类型不合法,仅支持发件/退件/收件");
}
}
private static void ValidateSenderAddressInput(CreateCustomerSenderAddressInput input)
{
if (string.IsNullOrWhiteSpace(input.SenderName))
{
throw new BusinessException("联系人姓名不能为空");
}
if (string.IsNullOrWhiteSpace(input.StreetAddress1))
{
throw new BusinessException("详细地址不能为空");
}
}
private async Task<int?> ResolveSenderCountryIdAsync(string? countryCode)
{
var code = string.IsNullOrWhiteSpace(countryCode) ? "CN" : countryCode.Trim();
var country = await GetCountryOrThrowAsync(code, "国家不存在");
return country.Id;
}
private static Expression<Func<CustomerSenderAddress, bool>> BuildSenderAddressPredicate(
int customerId,
CustomerSenderAddressPaginatedInput input)
{
var keyword = input.Keyword?.Trim();
var onlyDefault = input.OnlyDefault;
var addressType = input.AddressType?.Trim();
var filterByType = !string.IsNullOrWhiteSpace(addressType)
&& !string.Equals(addressType, "all", StringComparison.OrdinalIgnoreCase);
return x =>
x.CustomerId == customerId
&& (!filterByType || x.AddressType == addressType)
&& (!onlyDefault || x.IsDefault == 1)
&& (string.IsNullOrWhiteSpace(keyword)
|| (x.AddressName ?? string.Empty).Contains(keyword)
|| x.SenderName.Contains(keyword)
|| (x.Phone ?? string.Empty).Contains(keyword)
|| x.StreetAddress1.Contains(keyword));
}
private async Task<Dictionary<int, string>> LoadCountryCodeMapAsync(IEnumerable<int> countryIds)
{
var ids = countryIds.Distinct().ToArray();
if (ids.Length == 0)
{
return new Dictionary<int, string>();
}
var countries = await CountryRepository.GetListAsync(x => ids.Contains(x.Id));
return countries.ToDictionary(x => x.Id, x => x.CountryCode ?? string.Empty);
}
private static CustomerSenderAddressOutput MapSenderAddressOutput(
CustomerSenderAddress entity,
IReadOnlyDictionary<int, string> countryMap)
{
string? countryCode = null;
if (entity.Country.HasValue && countryMap.TryGetValue(entity.Country.Value, out var code))
{
countryCode = code;
}
var addressType = string.IsNullOrWhiteSpace(entity.AddressType)
? CustomerAddressTypeCodes.Sender
: entity.AddressType;
return new CustomerSenderAddressOutput
{
Id = entity.Id,
CustomerId = entity.CustomerId,
AddressType = addressType,
AddressTypeName = CustomerAddressTypeCodes.GetDisplayName(addressType),
AddressName = entity.AddressName,
SenderName = entity.SenderName,
Company = entity.Company,
Country = entity.Country,
CountryCode = countryCode,
Province = entity.Province,
City = entity.City,
ZipCode = entity.ZipCode,
StreetAddress1 = entity.StreetAddress1,
StreetAddress2 = entity.StreetAddress2,
StreetAddress3 = entity.StreetAddress3,
HouseNumber = entity.HouseNumber,
Phone = entity.Phone,
TelePhone = entity.TelePhone,
Email = entity.Email,
IsDefault = entity.IsDefault > 0,
CreateTime = entity.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
};
}
private static string? TrimOrNull(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}