147 lines
7.9 KiB
C#
147 lines
7.9 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using MAX.Framework.Commons;
|
|
using MAX.Framework.Validation;
|
|
using Store.Shops.Dtos;
|
|
using Store.Shops.Entities;
|
|
using Store.Shops.Mappers;
|
|
|
|
namespace Store.Shops.Services;
|
|
|
|
public sealed partial class ShopService
|
|
{
|
|
/// <summary>查询本人运单;跨账号按订单查询仅供管理员使用且必须指定订单。</summary>
|
|
public async Task<ShopPage<WaybillInfo>> Waybills(WaybillSearchRequest request, bool allUsers, CancellationToken ct)
|
|
{
|
|
RequestValidator.Validate(request);
|
|
Page(request.Page, request.PageSize);
|
|
if (request.Status?.Any(s => s is < 0 or > 2) == true) throw new ValidationException("运单状态只能为0、1、2");
|
|
var actor = await Actor();
|
|
if (allUsers)
|
|
{
|
|
RequireAdmin(actor);
|
|
if (request.OrderIds == null) throw new ValidationException("订单编号不能为空");
|
|
Ids(request.OrderIds);
|
|
}
|
|
var query = db.Select<WaybillEntity>().WhereIf(!allUsers, w => w.UserName == actor.UserName)
|
|
.WhereIf(request.Status != null, w => request.Status!.Contains(w.Status))
|
|
.WhereIf(request.OrderIds != null, w => request.OrderIds!.Contains(w.OrderId));
|
|
var count = request.NotCount ? -1 : await query.CountAsync(ct);
|
|
var rows = await query.OrderByDescending(w => w.Id).Page(request.Page, request.PageSize).ToListAsync(ct);
|
|
return new(rows.Select(new ShopDetailsMapper().Map).ToList(), count);
|
|
}
|
|
|
|
/// <summary>导入运单;揽收时间或绑定订单至少提供一个,运单号全库去重。</summary>
|
|
public async Task AddWaybill(WaybillAddRequest request, CancellationToken ct)
|
|
{
|
|
RequestValidator.Validate(request);
|
|
if (request.WaybillPickupTime == null && string.IsNullOrWhiteSpace(request.BindOrderId))
|
|
throw new ValidationException("揽收时间和绑定订单不能同时为空");
|
|
var actor = await Actor();
|
|
using var work = db.CreateUnitOfWork();
|
|
work.IsolationLevel = System.Data.IsolationLevel.Serializable;
|
|
work.GetOrBeginTransaction();
|
|
var orm = work.Orm;
|
|
if (await orm.Select<WaybillEntity>().Where(w => w.WaybillId == request.WaybillId).ForUpdate().AnyAsync(ct))
|
|
throw new AppException("运单号已存在");
|
|
await orm.Insert(new WaybillEntity
|
|
{
|
|
UserName = actor.UserName, DeliveryId = request.DeliveryId, DeliveryName = request.DeliveryName,
|
|
WaybillId = request.WaybillId, BindOrderId = request.BindOrderId ?? "", WaybillPickupTime = request.WaybillPickupTime,
|
|
Status = 0, AddTime = DateTime.Now
|
|
}).ExecuteAffrowsAsync(ct);
|
|
ct.ThrowIfCancellationRequested();
|
|
work.Commit();
|
|
}
|
|
|
|
/// <summary>只删除本人尚未使用的运单,已预占或使用的运单保持不变。</summary>
|
|
public async Task DeleteWaybills(WaybillDeleteRequest request, CancellationToken ct)
|
|
{
|
|
RequestValidator.Validate(request);
|
|
Ids(request.WaybillIds);
|
|
var actor = await Actor();
|
|
await db.Delete<WaybillEntity>().Where(w => w.UserName == actor.UserName && w.Status == 0
|
|
&& request.WaybillIds.Contains(w.WaybillId)).ExecuteAffrowsAsync(ct);
|
|
}
|
|
|
|
/// <summary>预占或直接使用运单;先复用同订单记录,再按绑定订单及次日揽收条件选择。</summary>
|
|
public async Task<WaybillInfo> SelectWaybill(WaybillSelectRequest request, bool useImmediately, CancellationToken ct)
|
|
{
|
|
RequestValidator.Validate(request);
|
|
if (request.OrderTime?.Date >= DateTime.MaxValue.Date.AddDays(-2))
|
|
throw new ValidationException("订单时间超出可匹配范围");
|
|
var actor = await Actor();
|
|
using var work = db.CreateUnitOfWork();
|
|
work.IsolationLevel = System.Data.IsolationLevel.Serializable;
|
|
work.GetOrBeginTransaction();
|
|
var orm = work.Orm;
|
|
await RequireShop(orm, actor, request.ShopId, ct);
|
|
var row = await orm.Select<WaybillEntity>()
|
|
.Where(w => w.UserName == actor.UserName && w.OrderId == request.OrderId && (w.Status == 1 || w.Status == 2))
|
|
.OrderBy(w => w.Id).ForUpdate().FirstAsync(ct);
|
|
if (row != null && !string.IsNullOrEmpty(row.ShopId) && row.ShopId != request.ShopId)
|
|
throw new AppException("订单已绑定其他店铺的运单");
|
|
if (row == null)
|
|
{
|
|
row = await orm.Select<WaybillEntity>().Where(w => w.UserName == actor.UserName && w.Status == 0
|
|
&& w.BindOrderId == request.OrderId && (w.OrderId == "" || w.OrderId == null))
|
|
.OrderBy("RAND()").ForUpdate().FirstAsync(ct);
|
|
if (row == null && (request.OrderTime.HasValue || useImmediately))
|
|
{
|
|
var available = orm.Select<WaybillEntity>().Where(w => w.UserName == actor.UserName && w.Status == 0
|
|
&& (w.OrderId == "" || w.OrderId == null) && (w.BindOrderId == "" || w.BindOrderId == null));
|
|
if (!useImmediately)
|
|
{
|
|
var start = request.OrderTime!.Value.Date.AddDays(1);
|
|
var end = start.AddDays(1);
|
|
available.Where(w => w.WaybillPickupTime >= start && w.WaybillPickupTime < end);
|
|
}
|
|
row = await available.OrderBy("RAND()").ForUpdate().FirstAsync(ct);
|
|
}
|
|
if (row == null) throw new AppException("没有可用的运单号");
|
|
}
|
|
if (row.Status != 2)
|
|
{
|
|
var status = useImmediately ? 2 : 1;
|
|
var useTime = useImmediately ? DateTime.Now : (DateTime?)null;
|
|
// 行锁与状态条件一起保护分配;请求重试不会重复占用第二张运单。
|
|
var affected = await orm.Update<WaybillEntity>().Where(w => w.Id == row.Id && w.Status == row.Status)
|
|
.Set(w => w.OrderId, request.OrderId).Set(w => w.ShopId, request.ShopId)
|
|
.Set(w => w.Status, status).Set(w => w.UseTime, useTime).ExecuteAffrowsAsync(ct);
|
|
if (affected == 0 && row.Status == 0) throw new AppException("运单已被占用,请重试");
|
|
row.OrderId = request.OrderId;
|
|
row.ShopId = request.ShopId;
|
|
row.Status = status;
|
|
row.UseTime = useTime;
|
|
}
|
|
ct.ThrowIfCancellationRequested();
|
|
work.Commit();
|
|
return new ShopDetailsMapper().Map(row);
|
|
}
|
|
|
|
/// <summary>仅对本人的预占运单确认使用或释放;不能释放已正式使用的运单。</summary>
|
|
public async Task UseWaybill(WaybillUseRequest request, CancellationToken ct)
|
|
{
|
|
RequestValidator.Validate(request);
|
|
var actor = await Actor();
|
|
using var work = db.CreateUnitOfWork();
|
|
work.GetOrBeginTransaction();
|
|
var orm = work.Orm;
|
|
await RequireShop(orm, actor, request.ShopId, ct);
|
|
var row = await orm.Select<WaybillEntity>().Where(w => w.UserName == actor.UserName
|
|
&& w.OrderId == request.OrderId && w.WaybillId == request.WaybillId)
|
|
.ForUpdate().FirstAsync(ct);
|
|
if (row == null || (row.ShopId != "" && row.ShopId != null && row.ShopId != request.ShopId))
|
|
throw new AppException("运单不存在或不属于指定店铺订单");
|
|
if (!request.IsRollback && row.Status == 2) return;
|
|
if (row.Status != 1) throw new AppException("只有预占运单可以确认或释放");
|
|
var changed = await orm.Update<WaybillEntity>().Where(w => w.Id == row.Id && w.Status == 1)
|
|
.Set(w => w.ShopId, request.IsRollback ? "" : request.ShopId)
|
|
.Set(w => w.OrderId, request.IsRollback ? "" : request.OrderId)
|
|
.Set(w => w.Status, request.IsRollback ? 0 : 2)
|
|
.Set(w => w.UseTime, request.IsRollback ? null : (DateTime?)DateTime.Now).ExecuteAffrowsAsync(ct);
|
|
if (changed != 1) throw new AppException("运单状态已变化,请重新查询");
|
|
ct.ThrowIfCancellationRequested();
|
|
work.Commit();
|
|
}
|
|
}
|