@@ -1,4 +1,5 @@
using System.Diagnostics ;
using Dlhis.Canteen.Order.Dto ;
using Dlhis.Canteen.Setting.Services ;
using Dlhis.Entity.Basic.Entity ;
using Dlhis.Entity.Canteen.Entities ;
@@ -161,7 +162,11 @@ public class OrderService : BaseOrderService, IOrderService
}
else if ( request . UserUniqueNo . StartsWith ( "02" ) )
{
request . Source = BalanceRecordSourceEnumType . EVoucher ;
request . Source = BalanceRecordSourceEnumType . EMeal Voucher ;
var eMealVoucherQrCode = await DbContext . Queryable < EMealVoucherDetailQrCodeEntity > ( ) . Where ( x = > x . QrCode = = request . UserUniqueNo ) . SingleAsync ( ) ;
eMealVoucherQrCode . ThrowIfNull ( "无法识别" ) ;
var code = eMealVoucherQrCode . OriginData . Split ( ':' ) [ 2 ] ;
gainfo = await DbContext . Queryable < GainfoEntity > ( ) . Where ( x = > x . Code = = code ) . SingleAsync ( ) ;
}
}
else
@@ -180,8 +185,7 @@ public class OrderService : BaseOrderService, IOrderService
{
gainfo . ThrowIfNull ( "无法识别,无权消费" ) ;
}
if ( request . IsOffline )
else
{
//兼容hpt尚未删除人员后产生的消费数据
gainfo ? ? = await DbContext . Queryable < GainfoEntity > ( ) . AS ( "GainfoLeave" )
@@ -305,7 +309,6 @@ public class OrderService : BaseOrderService, IOrderService
_logs . Add ( "消费请求校验:" + _sw . ElapsedMilliseconds ) ;
_sw . Restart ( ) ;
await CommonHelper . RetryAsync ( async ( ) = >
{
var gainfoMeal = await DbContext . Queryable < GainfoMealEntity > ( ) . Where ( x = > x . UserNo = = order . UserNo ) . SingleAsync ( ) ;
@@ -329,42 +332,6 @@ public class OrderService : BaseOrderService, IOrderService
_sw . Restart ( ) ;
}
/// <summary>
/// 根据二维码获取消费用户
/// </summary>
/// <param name="qrCode"></param>
/// <returns></returns>
private async Task < GainfoEntity > GetUserByQrCodeAsync ( string qrCode )
{
var gainfoQrCode = await DbContext . Queryable < GainfoQrCodeEntity > ( ) . Where ( x = > x . QrCode = = qrCode ) . SingleAsync ( ) ;
gainfoQrCode . ThrowIfNull ( "消费码已过期" ) ;
( gainfoQrCode . ModifiedTime . AddMinutes ( 60 ) < DateTime . Now ) . ThrowIfTrue ( "消费码已过期" ) ;
return await DbContext . Queryable < GainfoEntity > ( ) . Where ( x = > x . Code = = gainfoQrCode . UserCode ) . SingleAsync ( ) ;
}
/// <summary>
/// 检查是否可以离线消费
/// </summary>
/// <param name="thirdNo"></param>
/// <returns></returns>
private async Task < bool > ToCheckAbleOfflineConsumeAsync ( string thirdNo )
{
var order = await DbContext . Queryable < CanteenOrderEntity > ( )
. Where ( x = > x . ThirdNo = = thirdNo )
. OrderBy ( x = > x . CreateTime , OrderByType . Desc )
. FirstAsync ( ) ;
if ( order = = null )
{
return true ;
}
//存在在线order数据,但是状态未成功
return ! order . IsOffline & & order . OrderStatus ! = CanteenOrderStatusEnumType . Success ;
}
/// <summary>
/// 离线消费处理
/// </summary>
@@ -418,7 +385,6 @@ public class OrderService : BaseOrderService, IOrderService
DeviceUniqueNo = request . DeviceUniqueNo ,
No = SnowFlakeHelper . Instance . NextId ( ) . ToString ( ) ,
SubsidiesFromMonth = request . CostDateTime . ToString ( "yyyyMM" ) ,
IsOffline = request . IsOffline ,
IsCostCrossTenant = businessPlaceInfo ! . TenantId ! = gainfo . Factory ,
@@ -492,8 +458,6 @@ public class OrderService : BaseOrderService, IOrderService
device . ThrowIfNull ( "设备尚未登记" ) ;
device . Enable . ThrowIfFalse ( "设备已被停用" ) ;
( request . Source = = BalanceRecordSourceEnumType . QrCode & & device . TenantId ! = gainfo . Factory ) . ThrowIfTrue ( "二维码在此设备禁止消费" ) ;
var businessPlaceInfo = await DbContext . Queryable < BusinessPlaceEntity > ( )
. LeftJoin < BusinessPlaceShiftRelationshipEntity > ( ( x , sr ) = > x . Code = = sr . PlaceCode )
. Where ( x = > x . Code = = registry . BusinessPlaceCode )
@@ -523,6 +487,8 @@ public class OrderService : BaseOrderService, IOrderService
var costDate = await GetCostDateAsync ( shift , request . CostDateTime ) ;
( request . Source = = BalanceRecordSourceEnumType . QrCode & & businessPlaceInfo . TenantId ! = gainfo . Factory ) . ThrowIfTrue ( "禁止跨厂区消费" ) ;
var order = new CanteenOrderEntity
{
RequestJson = request . ToJson ( ) ,
@@ -543,9 +509,8 @@ public class OrderService : BaseOrderService, IOrderService
DeviceUniqueNo = request . DeviceUniqueNo ,
No = SnowFlakeHelper . Instance . NextId ( ) . ToString ( ) ,
SubsidiesFromMonth = request . CostDateTime . ToString ( "yyyyMM" ) ,
IsOffline = request . IsOffline ,
IsCostCrossTenant = await GetSettingAsync < bool > ( gainfo . No , businessPlaceInfo ! . TenantId , CanteenSettingItemEnumType . AllowCrossTenant ) ,
IsCostCrossTenant = gainfo . Factory ! = businessPlaceInfo ! . TenantId ,
OriginTenantId = gainfo . Factory ,
Werks = gainfo . Werks ,
@@ -610,17 +575,29 @@ public class OrderService : BaseOrderService, IOrderService
/// <returns></returns>
private async Task < string > ToDoAsync ( OrderAddRequest request , GainfoEntity gainfo )
{
var message = "" ;
if ( request . IsOffline )
{
if ( request . ThirdNo . IsPresent ( ) )
{
var redisCache = _serviceProvider . GetService < RedisCache > ( ) ;
var message = string . Empty ;
await using var handler = await redisCache . LockAsync ( $"consume:device:{request.ThirdNo}" ) ;
if ( handler . Acquired )
if ( request . Source = = BalanceRecordSourceEnumType . EMealVoucher )
{
try
{
await EMealVoucherConsumeAsync ( request . Adapt < ConsumeRequest > ( ) , gainfo ) ;
}
catch ( Exception ex )
{
message = ex . Message ;
}
}
else
{
if ( request . IsOffline )
{
if ( request . ThirdNo . IsPresent ( ) )
{
if ( await ToCheckAbleOfflineConsumeAsync ( request . ThirdNo ) )
var redisCache = _serviceProvider . GetService < RedisCache > ( ) ;
await using var handler = await redisCache . LockAsync ( $"consume:device:{request.ThirdNo}" ) ;
if ( handler . Acquired & & await ToCheckAbleOfflineConsumeAsync ( request . ThirdNo ) )
{
try
{
@@ -632,32 +609,219 @@ public class OrderService : BaseOrderService, IOrderService
}
}
}
else
{
try
{
await HandleOfflineAsync ( request . Adapt < ConsumeRequest > ( ) , gainfo ) ;
}
catch ( Exception ex )
{
Logger . LogError ( ex , "离线入库错误: 订单号:{no} .{message}" , request . ThirdNo , ex . Message ) ;
}
}
}
else
{
try
{
await HandleOfflineAsync ( request . Adapt < ConsumeRequest > ( ) , gainfo ) ;
( await DbContext . Queryable < GainfoMealEntity > ( ) . Where ( x = > x . UserCode = = gainfo . Code ) . AnyAsync ( ) ) . ThrowIfFalse ( "就餐余额不足" ) ;
await ConsumeAsync ( request . Adapt < ConsumeRequest > ( ) , gainfo ) ;
}
catch ( Exception ex )
{
Logger . LogError ( ex , "离线入库错误: 订单号:{no} .{message}" , request . ThirdNo , ex . Message ) ;
message = ex . Message ;
}
}
}
else
{
try
{
( await DbContext . Queryable < GainfoMealEntity > ( ) . Where ( x = > x . UserCode = = gainfo . Code ) . AnyAsync ( ) ) . ThrowIfFalse ( "就餐余额不足" ) ;
await ConsumeAsync ( request . Adapt < ConsumeRequest > ( ) , gainfo ) ;
}
catch ( Exception ex )
{
message = ex . Message ;
}
}
return message ;
async Task < bool > ToCheckAbleOfflineConsumeAsync ( string thirdNo )
{
var order = await DbContext . Queryable < CanteenOrderEntity > ( )
. Where ( x = > x . ThirdNo = = thirdNo )
. OrderBy ( x = > x . CreateTime , OrderByType . Desc )
. FirstAsync ( ) ;
if ( order = = null )
{
return true ;
}
//存在在线order数据,但是状态未成功
return ! order . IsOffline & & order . OrderStatus ! = CanteenOrderStatusEnumType . Success ;
}
}
/// <summary>
/// 根据二维码获取消费用户
/// </summary>
/// <param name="qrCode"></param>
/// <returns></returns>
private async Task < GainfoEntity > GetUserByQrCodeAsync ( string qrCode )
{
var gainfoQrCode = await DbContext . Queryable < GainfoQrCodeEntity > ( ) . Where ( x = > x . QrCode = = qrCode ) . SingleAsync ( ) ;
gainfoQrCode . ThrowIfNull ( "消费码已过期" ) ;
( gainfoQrCode . ModifiedTime . AddMinutes ( 60 ) < DateTime . Now ) . ThrowIfTrue ( "消费码已过期" ) ;
return await DbContext . Queryable < GainfoEntity > ( ) . Where ( x = > x . Code = = gainfoQrCode . UserCode ) . SingleAsync ( ) ;
}
private async Task EMealVoucherConsumeAsync ( ConsumeRequest request , GainfoEntity gainfo )
{
var eMealVoucherQrCode = await DbContext . Queryable < EMealVoucherDetailQrCodeEntity > ( ) . Where ( x = > x . QrCode = = request . UserUniqueNo ) . SingleAsync ( ) ;
var originData = eMealVoucherQrCode . OriginData . Split ( ':' ) ;
var no = originData [ 0 ] ;
var idNo = originData [ 3 ] ;
var nowDate = DateTime . Now . Date ;
var current = await DbContext . Queryable < EMealVoucherDetailEntity > ( )
. Where ( x = > x . No = = no & & x . IdNo = = idNo ) . SingleAsync ( ) ;
( nowDate > = current . Start & & nowDate < = current . End ) . ThrowIfFalse ( "餐券已过期" ) ;
var time = DateTimeOffset . FromUnixTimeMilliseconds ( Convert . ToInt64 ( originData [ 1 ] . Reverse ( ) . Join ( string . Empty ) ) ) . ToLocalTime ( ) . DateTime ;
( time . AddMinutes ( 60 ) > = DateTime . Now ) . ThrowIfFalse ( "二维码已过期" ) ;
var eMealVoucher = await DbContext . Queryable < EMealVoucherEntity > ( ) . Where ( x = > x . No = = no ) . SingleAsync ( ) ;
var checkData = await PreCheckAsync ( request . DeviceUniqueNo , request . CostDateTime ) ;
eMealVoucher . Factories . Contains ( checkData . BusinessPlaceTenantId ) . ThrowIfFalse ( "禁止在当前厂区消费" ) ;
_logs . Add ( "检验设备和营业点参数:" + _sw . ElapsedMilliseconds ) ;
_sw . Restart ( ) ;
var order = new CanteenOrderEntity
{
RequestJson = request . ToJson ( ) ,
ThirdNo = request . ThirdNo ,
Type = BalanceRecordTypeEnumType . Out ,
ApplicationType = CanteenApplicationTypeEnumType . Device ,
Name = gainfo . Name ,
Source = request . Source ,
UserNo = eMealVoucherQrCode . OriginData , //记录为当前消费券
UserCode = gainfo . Code ,
Amount = request . Amount ,
BusinessPlaceCode = checkData . BusinessPlaceCode ,
BusinessShiftCode = checkData . BusinessShiftCode ,
ShiftType = checkData . Shift . ShiftType ,
CostDate = checkData . CostDate ,
CostId = gainfo . CostId ,
DepartmentNo = gainfo . DepartmentNo ,
DeviceUniqueNo = request . DeviceUniqueNo ,
No = SnowFlakeHelper . Instance . NextId ( ) . ToString ( ) ,
IsOffline = false ,
IsCostCrossTenant = gainfo . Factory ! = checkData . BusinessPlaceTenantId ,
OriginTenantId = gainfo . Factory ,
Werks = gainfo . Werks ,
Status = BalanceRecordStatusEnumType . Valid ,
OrderStatus = CanteenOrderStatusEnumType . Wait ,
CostDateTime = request . CostDateTime ,
//标识当前消费的基地
TenantId = checkData . BusinessPlaceTenantId ,
} ;
var orderId = await DbContext . InsertReturnIdentityAsync ( order ) ;
_logs . Add ( "临时order写入:" + _sw . ElapsedMilliseconds ) ;
_sw . Restart ( ) ;
try
{
await CommonHelper . RetryAsync ( async ( ) = >
{
var detail = await DbContext . Queryable < EMealVoucherDetailEntity > ( ) . Where ( x = > x . No = = no & & x . IdNo = = idNo ) . SingleAsync ( ) ;
//金额+-
detail . Balance + = order . Amount ;
( detail . Balance < 0 ) . ThrowIfTrue ( "余额不足,无法消费" ) ;
await DbContext . Updateable < EMealVoucherDetailEntity > ( )
. SetColumns ( x = > x . Balance = = detail . Balance )
. SetColumns ( x = > x . Version = = x . Version + 1 )
. SetColumns ( x = > x . ModifiedTime = = DateTime . Now )
. Where ( x = > x . No = = no & & x . IdNo = = idNo & & x . Version = = detail . Version ) //必须与查询时的version一致
. ExecuteCommandThrowVersionAsync ( ) ;
//写入记录
var balanceRecord = order . Adapt < BalanceRecordEntity > ( ) ;
await DbContext . InsertAsync ( balanceRecord ) ;
} , 5 ) ;
_logs . Add ( "消费写入:" + _sw . ElapsedMilliseconds ) ;
_sw . Restart ( ) ;
await DbContext . Updateable < CanteenOrderEntity > ( )
. SetColumns ( x = > x . OrderStatus = = CanteenOrderStatusEnumType . Success )
. SetColumns ( x = > x . ModifiedTime = = DateTime . Now )
. Where ( x = > x . Id = = orderId )
. ExecuteCommandAsync ( ) ;
_logs . Add ( "更新image和订单信息:" + _sw . ElapsedMilliseconds ) ;
_sw . Restart ( ) ;
}
catch ( Exception ex )
{
await DbContext . Updateable < CanteenOrderEntity > ( )
. SetColumns ( x = > x . OrderStatus = = CanteenOrderStatusEnumType . Failure )
. SetColumns ( x = > x . Result = = ex . Message )
. SetColumns ( x = > x . ModifiedTime = = DateTime . Now )
. Where ( x = > x . Id = = orderId )
. ExecuteCommandAsync ( ) ;
throw ;
}
}
/// <summary>
/// 消费前预检
/// </summary>
/// <param name="deviceUniqueNo"></param>
/// <param name="costDateTime"></param>
/// <returns></returns>
private async Task < OrderPreCheckDto > PreCheckAsync ( string deviceUniqueNo , DateTime costDateTime )
{
var registry = await DbContext . Queryable < DeviceRegistryEntity > ( ) . Where ( x = > x . DeviceUniqueNo = = deviceUniqueNo ) . SingleAsync ( ) ;
registry . ThrowIfNull ( "设备尚未登记" ) ;
var device = await DbContext . Queryable < DeviceEntity > ( ) . Where ( x = > x . UniqueNo = = deviceUniqueNo ) . SingleAsync ( ) ;
device . ThrowIfNull ( "设备尚未登记" ) ;
device . Enable . ThrowIfFalse ( "设备已被停用" ) ;
var businessPlaceInfo = await DbContext . Queryable < BusinessPlaceEntity > ( )
. LeftJoin < BusinessPlaceShiftRelationshipEntity > ( ( x , sr ) = > x . Code = = sr . PlaceCode )
. Where ( x = > x . Code = = registry . BusinessPlaceCode )
. Select ( ( x , sr ) = > new
{
x . Code ,
sr . ShiftCode ,
x . TenantId
} )
. SingleAsync ( ) ;
businessPlaceInfo . ThrowIfNull ( "设备尚未登记营业点" ) ;
//获取当前消费餐次
var shifts = await DbContext . Queryable < BusinessShiftMealRelationshipEntity > ( ) . Where ( x = > x . ShiftCode = = businessPlaceInfo . ShiftCode ) . ToListAsync ( ) ;
shifts . ThrowIfEmpty ( "营业点餐次尚未设置" ) ;
var shift = shifts . FirstOrDefault ( x = > new TimePeriod
{
Start = x . Start ,
End = x . End
} . IsPresent ( costDateTime ) ) ;
shift . ThrowIfNull ( "非就餐时间,无法消费" ) ;
var costDate = await GetCostDateAsync ( shift , costDateTime ) ;
return new OrderPreCheckDto
{
CostDate = costDate ,
Shift = shift ,
BusinessPlaceTenantId = businessPlaceInfo . TenantId ,
BusinessPlaceCode = businessPlaceInfo . Code ,
BusinessShiftCode = businessPlaceInfo . ShiftCode
} ;
}
}