fix 优化消费代码处理

This commit is contained in:
2024-01-11 11:53:26 +08:00
parent 8c5819b18b
commit 16813c10f4
8 changed files with 203 additions and 65 deletions

View File

@@ -40,8 +40,8 @@ public class OrdersController : ControllerBase
/// <returns></returns>
[AllowAnonymous]
[HttpPost]
public async Task AddAsync(OrderAddRequest request)
public async Task<OrderConsumeResponse> AddAsync(OrderAddRequest request)
{
await _orderService.AddAsync(request);
return await _orderService.AddAsync(request);
}
}

View File

@@ -13,7 +13,7 @@ public class BalanceRecordResponse
/// <summary>
/// 档案号/QRCode
/// </summary>
public string UserId { get; set; }
public string UserNo { get; set; }
/// <summary>
/// 姓名

View File

@@ -0,0 +1,29 @@
namespace Dlhis.Canteen.Order.Response;
public class OrderConsumeResponse
{
/// <summary>
/// 用户工号
/// </summary>
public string UserCode { get; set; }
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 部门号
/// </summary>
public string DepartmentNo { get; set; }
/// <summary>
/// 部门名称
/// </summary>
public string DepartmentName { get; set; }
/// <summary>
/// 余额
/// </summary>
public decimal Balance { get; set; }
}

View File

@@ -8,5 +8,5 @@ public interface IOrderService : IScopeDependency
{
Task<Pagination<OrderResponse>> GetPageAsync(OrderPageRequest request);
Task AddAsync(OrderAddRequest request);
Task<OrderConsumeResponse> AddAsync(OrderAddRequest request);
}

View File

@@ -1,5 +1,6 @@
using Dlhis.Canteen.Order.Request;
using Dlhis.Canteen.Order.Response;
using Dlhis.Entity.Basic.Entity;
using Dlhis.Entity.Canteen.Entities;
using Kurisu.Core.DataAccess.Dto;
using Kurisu.SqlSugar.Extensions;
@@ -19,10 +20,37 @@ public class BalanceService : IBalanceService
public async Task<Pagination<BalanceRecordResponse>> GetPageAsync(BalanceRecordPageRequest request)
{
var page = await _dbContext.Queryable<BalanceRecordEntity>()
.LeftJoin<GainfoEntity>((record, ga) => record.UserNo == ga.No)
.LeftJoin<DepartmentEntity>((record, ga, department) => ga.DepartmentNo == department.No)
.LeftJoin<CanteenBusinessPlaceEntity>((record, ga, department, bplace) => record.BusinessPlaceCode == bplace.Code)
.LeftJoin<CanteenBusinessShiftEntity>((record, ga, department, bplace, bshift) => record.BusinessShiftCode == bshift.Code)
.WhereIF(request.SearchDateTimePeriod?.Start.HasValue == true, record => record.CreateTime >= request.SearchDateTimePeriod.Start)
.WhereIF(request.SearchDateTimePeriod?.End.HasValue == true, record => record.CreateTime <= request.SearchDateTimePeriod.End)
.OrderBy(record => record.CreateTime)
.Select<BalanceRecordResponse>()
.Select((record, ga, department, bplace, bshift) => new BalanceRecordResponse
{
Amount = record.Amount,
BusinessPlaceCode = record.BusinessPlaceCode,
BusinessShiftCode = record.BusinessShiftCode,
CostDate = record.CostDate,
CostId = record.CostId,
DepartmentNo = record.DepartmentNo,
DeviceInfo = record.DeviceInfo,
IP = record.IP,
UserNo = record.UserNo,
UserCode = record.UserCode,
Type = record.Type,
SubsidiesFromMonth = record.SubsidiesFromMonth,
Status = record.Status,
Source = record.Source,
No = record.No,
IsCostCrossTenant = record.IsCostCrossTenant,
OriginTenantId = record.OriginTenantId,
Name = record.Name,
DepartmentName = department.Name,
BusinessPlaceName = bplace.Name,
BusinessShiftName = bshift.Name,
})
.ToPageListAsync(request);
return page;

View File

@@ -41,7 +41,7 @@ public class OrderService : IOrderService
}
[IgnoreTenant]
public async Task AddAsync(OrderAddRequest request)
public async Task<OrderConsumeResponse> AddAsync(OrderAddRequest request)
{
var device = await _dbContext.Queryable<DeviceEntity>().Where(x => x.UniqueNo == request.DeviceUniqueNo).Where(x => x.Enable).FirstAsync();
device.ThrowIfNull("设备不存在或者尚未启用");
@@ -107,10 +107,9 @@ public class OrderService : IOrderService
RequestJson = request.ToJson(),
ThirdNo = request.ThirdNo,
Type = request.Type.Value,
OrderStatus = CanteenOrderStatusEnumType.Wait,
Name = gafinfo.Name,
Source = request.Source,
UserId = canteenUser.UserNo,
UserNo = canteenUser.UserNo,
UserCode = canteenUser.UserCode,
Amount = request.Amount,
BusinessPlaceCode = businessPlace.Code,
@@ -127,14 +126,139 @@ public class OrderService : IOrderService
OriginTenantId = canteenUser.TenantId,//人员可能跨基地消费
Status = BalanceRecordStatusEnumType.Doubtful,
TenantId = device.TenantId, //设备才能标识当前消费的基地
//餐别异常处理
Status = shift == null ? BalanceRecordStatusEnumType.Doubtful : BalanceRecordStatusEnumType.Valid,
OrderStatus = CanteenOrderStatusEnumType.Wait,
//设备才能标识当前消费的基地
TenantId = device.TenantId,
//默认为admin
CreatedBy = 1,
CreateTime = DateTime.Now,
ModifiedBy = 1,
ModifiedTime = DateTime.Now,
};
var id = await _dbContext.InsertReturnIdentityAsync(order);
await GenerateChargeRecordAsync(id, settings, shifts, request.CostDateTime);
var result = await _dbContext.UseTransactionAsync(async () =>
{
try
{
var r = await GenerateChargeRecordAsync(id, settings, shifts, request.CostDateTime);
return r;
}
catch (Exception)
{
throw;
}
finally
{
//订单消费成功
order.OrderStatus = CanteenOrderStatusEnumType.Successful;
await _dbContext.UpdateAsync(order);
}
});
if (!result.IsSuccess)
{
throw new UserFriendlyException(result.ErrorMessage);
}
return result.Data;
}
public async Task<OrderConsumeResponse> GenerateChargeRecordAsync(long orderId,
Dictionary<CanteenSettingItemEnumType, string> settings,
List<CanteenBusinessShiftEntity> shifts,
DateTime costDateTime)
{
var order = await _dbContext.Queryable<CanteenOrderEntity>().Where(x => x.Id == orderId).FirstAsync();
//校验
await Verify(settings, shifts, costDateTime, order);
return await ContinueUpdateAsync(async () =>
{
var canteenUser = await _dbContext.Queryable<CanteenUserEntity>().Where(x => x.UserNo == order.UserNo).FirstAsync();
//金额+-
canteenUser.Balance += order.Amount;
if (order.Type == BalanceRecordTypeEnumType.Out)
{
if (canteenUser.Balance < 0)
{
throw new UserFriendlyException("余额不足,无法消费");
}
}
var effect = await _dbContext.Updateable<CanteenUserEntity>()
.SetColumns(x => x.Balance == canteenUser.Balance + order.Amount)
.SetColumns(x => x.Version == x.Version + 1)
.Where(x => x.UserNo == order.UserNo && x.Version == canteenUser.Version)//必须与查询时的version一致
.ExecuteCommandAsync();
if (effect <= 0) throw new VersionExceptions("更新版本冲突");
//写入消费记录
var balanceRecord = order.Adapt<BalanceRecordEntity>();
await _dbContext.InsertAsync(balanceRecord);
return await _dbContext.Queryable<GainfoEntity>()
.LeftJoin<DepartmentEntity>((ga, department) => ga.DepartmentNo == department.No)
.LeftJoin<CanteenUserEntity>((ga, department, mealUser) => ga.No == mealUser.UserNo)
.Select((ga, department, mealUser) => new OrderConsumeResponse
{
UserCode = ga.Code,
UserName = ga.Name,
DepartmentNo = ga.DepartmentNo,
DepartmentName = department.Name,
Balance = mealUser.Balance
})
.FirstAsync();
}, 5);
}
//******************************************************* help methods ************************************************************/
/// <summary>
/// 设备并发更新
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="func"></param>
/// <param name="retry"></param>
/// <returns></returns>
/// <exception cref="UserFriendlyException"></exception>
private async Task<T> ContinueUpdateAsync<T>(Func<Task<T>> func, int retry)
{
for (int i = 0; i < retry; i++)
{
try
{
return await func();
}
catch (Exception ex) when (ex is VersionExceptions)
{
_logger.LogWarning(ex, ex.Message);
}
catch (Exception)
{
throw;
}
}
throw new UserFriendlyException("消费失败,请重试");
}
/// <summary>
/// 获取参数配置
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="settings"></param>
/// <param name="item"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
private static T GetSetting<T>(Dictionary<CanteenSettingItemEnumType, string> settings, CanteenSettingItemEnumType item, T defaultValue = default)
{
if (settings.TryGetValue(item, out var v))
@@ -145,59 +269,17 @@ public class OrderService : IOrderService
return defaultValue;
}
public async Task GenerateChargeRecordAsync(long orderId,
Dictionary<CanteenSettingItemEnumType, string> settings,
List<CanteenBusinessShiftEntity> shifts,
DateTime costDateTime)
{
var order = await _dbContext.Queryable<CanteenOrderEntity>().Where(x => x.Id == orderId).FirstAsync();
await Verify(settings, shifts, costDateTime, order);//校验
for (int i = 0; i < 5; i++)
{
try
{
var canteenUser = await _dbContext.Queryable<CanteenUserEntity>().Where(x => x.UserNo == order.UserId).FirstAsync();
//金额+-
canteenUser.Balance += order.Amount;
if (order.Type == BalanceRecordTypeEnumType.Out)
{
if (canteenUser.Balance < 0)
{
throw new UserFriendlyException("余额不足,无法消费");
}
}
var effect = await _dbContext.Updateable<CanteenUserEntity>()
.SetColumns(x => x.Balance == canteenUser.Balance + order.Amount)
.SetColumns(x => x.Version == x.Version + 1)
.Where(x => x.UserNo == order.UserId && x.Version == canteenUser.Version)//必须与查询时的version一致
.ExecuteCommandAsync();
if (effect <= 0)
{
throw new VersionExceptions("更新版本冲突");
}
var balanceRecord = order.Adapt<BalanceRecordEntity>();
await _dbContext.InsertAsync(balanceRecord);
order.Status = BalanceRecordStatusEnumType.Valid; //订单改为有效
break;
}
catch (Exception ex) when (ex is VersionExceptions)
{
_logger.LogError(ex, ex.Message);
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// 验证参数
/// </summary>
/// <param name="settings"></param>
/// <param name="shifts"></param>
/// <param name="costDateTime"></param>
/// <param name="order"></param>
/// <returns></returns>
/// <exception cref="UserFriendlyException"></exception>
private async Task Verify(Dictionary<CanteenSettingItemEnumType, string> settings, List<CanteenBusinessShiftEntity> shifts, DateTime costDateTime, CanteenOrderEntity order)
{
//在线
@@ -215,7 +297,7 @@ public class OrderService : IOrderService
var endOfDay = beginOfDay.AddHours(23).AddMinutes(59).AddSeconds(59);
var balanceRecords = await _dbContext.Queryable<BalanceRecordEntity>()
.Where(x => x.UserId == order.UserId)
.Where(x => x.UserNo == order.UserNo)
.Where(x => x.CreateTime <= beginOfDay && x.CreateTime <= endOfDay) //今天消费记录
.ToListAsync();