admin/src/Oms.Application/Services/Orders/OrderService.SenderAddress.cs
zzh 341a318f0b feat: 客户发件地址与揽收订单,复用小包创建全链路。
新增地址簿 CRUD、pickup/create、订单 orderSource 筛选;附带包裹商品图片上传接口。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 18:58:48 +08:00

254 lines
9.7 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.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);
ValidateSenderAddressInput(input);
var countryId = await ResolveSenderCountryIdAsync(input.CountryCode);
if (input.IsDefault)
{
await CustomerSenderAddressRepository.ClearDefaultAsync(customer.Id);
}
var entity = new CustomerSenderAddress
{
CustomerId = customer.Id,
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);
ValidateSenderAddressInput(input);
var countryId = await ResolveSenderCountryIdAsync(input.CountryCode);
if (input.IsDefault)
{
await CustomerSenderAddressRepository.ClearDefaultAsync(entity.CustomerId, entity.Id);
}
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);
await CustomerSenderAddressRepository.ClearDefaultAsync(entity.CustomerId, 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 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;
return x =>
x.CustomerId == customerId
&& (!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;
}
return new CustomerSenderAddressOutput
{
Id = entity.Id,
CustomerId = entity.CustomerId,
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();
}
}