@ -1,13 +1,9 @@
using DS.Module.Core ;
using DS.Module.Core.Condition ;
using DS.Module.Core.Data ;
using DS.WMS.Core.Fee.Dtos ;
using DS.WMS.Core.Fee.Entity ;
using System.Net.Http.Headers ;
using DS.Module.Core ;
using DS.WMS.Core.HangfireJob.Interface ;
using DS.WMS.Core. Op.Dtos ;
using DS.WMS.Core.Op.Entity ;
using DS.WMS.Core.Sys.Entity ;
using Microsoft.Extensions.Configuration ;
using Microsoft.Extensions.DependencyInjection ;
using Newtonsoft.Json ;
using SqlSugar ;
namespace DS.WMS.Core.HangfireJob.Method
@ -17,7 +13,14 @@ namespace DS.WMS.Core.HangfireJob.Method
/// </summary>
public class FeeCustTemplateJobService : IFeeCustTemplateJobService
{
static readonly ApiFox api ;
ISqlSugarClient ? db ;
IConfiguration configuration ;
static FeeCustTemplateJobService ( )
{
api = new ApiFox ( ) ;
}
/// <summary>
/// 初始化
@ -26,6 +29,7 @@ namespace DS.WMS.Core.HangfireJob.Method
public FeeCustTemplateJobService ( IServiceProvider serviceProvider )
{
db = serviceProvider . GetRequiredService < ISqlSugarClient > ( ) ;
configuration = serviceProvider . GetRequiredService < IConfiguration > ( ) ;
}
/// <summary>
@ -34,502 +38,71 @@ namespace DS.WMS.Core.HangfireJob.Method
/// <returns></returns>
public async Task GenerateFeesAsync ( )
{
db . QueryFilter . Clear ( ) ;
var dbLinks = await db . Queryable < Module . SqlSugar . SysTenantLink > ( ) . ToListAsync ( ) ;
SqlSugarClient ? tenantDb = null ;
try
{
foreach ( var dbLink in dbLinks )
{
string configId = dbLinks . IndexOf ( dbLink ) . ToString ( ) ;
//if (db.IsAnyConnection(configId))
//{
// var config = new ConnectionConfig
// {
// ConfigId = configId,
// DbType = dbLink.DbType,
// IsAutoCloseConnection = true,
// ConnectionString = dbLink.Connection
// };
// db.AddConnection(config);
//}
tenantDb = new SqlSugarClient ( new ConnectionConfig
{
ConfigId = configId ,
DbType = dbLink . DbType ,
IsAutoCloseConnection = true ,
ConnectionString = dbLink . Connection
} ) ;
await GenerateFeesCoreAsync ( tenantDb , DateTime . Now . Date ) ;
}
}
finally
{
tenantDb ? . Dispose ( ) ;
}
}
/// <summary>
/// 根据开船日生成费用
/// </summary>
/// <param name="tenantDb"></param>
/// <param name="etd">开船日</param>
/// <returns></returns>
internal async Task GenerateFeesCoreAsync ( SqlSugarClient tenantDb , DateTime etd )
{
DateTime dt = DateTime . Now ;
var list = await tenantDb . Queryable < FeeCustTemplate > ( )
. Where ( x = > ! x . IsDisabled & & SqlFunc . Between ( dt , x . StartTime , x . EndTime ) & & ( x . IsShared | | x . CustomerId ! = null ) )
. Select ( x = > new FeeCustTemplate
{
Id = x . Id ,
CustomerId = x . CustomerId ,
FeeType = x . FeeType ,
FeeCategoryId = x . FeeCategoryId ,
FeeCategoryName = x . FeeCategoryName ,
Priority = x . Priority ,
IsShared = x . IsShared ,
POLCode = x . POLCode ,
PODCode = x . PODCode ,
LaneId = x . LaneId ,
SourceId = x . SourceId ,
CarrierId = x . CarrierId ,
ForwarderId = x . ForwarderId ,
MBLFrtCode = x . MBLFrtCode ,
Condition = x . Condition
} ) . ToListAsync ( ) ;
if ( list . Count = = 0 )
return ;
var tids = list . Select ( x = > x . Id ) ;
var orders = await tenantDb . Queryable < SeaExport > ( ) . Where ( x = > SqlFunc . DateIsSame ( x . ETD , etd ) & &
SqlFunc . Subqueryable < FeeCustTemplateRecord > ( ) . Where ( y = > y . BusinessId = = x . Id & & y . BusinessType = = BusinessType . OceanShippingExport & & tids . Contains ( y . TemplateId ) ) . NotAny ( ) )
. Select < SeaExportRes > ( ) . ToListAsync ( ) ;
if ( orders . Count = = 0 )
return ;
List < FeeRecord > feeList = [ ] ;
await tenantDb . Ado . BeginTranAsync ( ) ;
try
{
foreach ( var order in orders )
{
var custList = list . Where ( x = > x . CustomerId = = order . CustomerId ) . OrderBy ( x = > x . Priority ) ;
foreach ( var template in custList ) //遍历客户费用模板,查找匹配项
{
var fees = await CreateFeesIfMatchAsync ( tenantDb , order , template ) ;
if ( fees ! = null )
{
feeList . AddRange ( fees ) ;
var adminUser = await db . Queryable < SysUser > ( )
. Where ( x = > x . TenantId = = dbLink . TenantId & & x . Status = = 0 & & x . UserType = = 1 )
. OrderByDescending ( x = > x . CreateTime )
. Select ( x = > new
{
x . Id ,
x . UserName ,
x . Password ,
x . DefaultOrgId ,
x . DefaultOrgName ,
x . TenantId ,
x . TenantName
} ) . FirstAsync ( ) ;
var record = new FeeCustTemplateRecord
if ( adminUser = = null )
{
BusinessId = order . CustomerId ,
BusinessType = BusinessType . OceanShippingExport ,
CreateTime = dt ,
FeeCategoryId = template . FeeCategoryId ,
FeeType = template . FeeType ,
TemplateId = template . Id
} ;
await tenantDb . Insertable ( record ) . ExecuteCommandAsync ( ) ;
Console . WriteLine ( $"未能获取租户系统管理员, 租户ID: {dbLink.TenantId}" ) ;
continue ;
}
}
//未找到客户模板,开始匹配共享模板
if ( feeList . Count = = 0 )
{
var sharedList = list . Where ( x = > x . IsShared ) . OrderBy ( x = > x . Priority ) . ToList ( ) ;
foreach ( var template in sharedList )
{
var fees = await CreateFeesIfMatchAsync ( tenantDb , order , template ) ;
if ( fees ! = null )
{
feeList . AddRange ( fees ) ;
var record = new FeeCustTemplateRecord
var tokenModel = new JwtHelper . JwtTokenModel
{
BusinessId = order . CustomerId ,
BusinessType = BusinessType . OceanShippingExport ,
CreateTime = dt ,
FeeCategoryId = template . FeeCategoryId ,
FeeType = template . FeeType ,
TemplateId = template . Id
Uid = adminUser . Id . ToString ( ) ,
Name = adminUser . UserName ,
OrgId = adminUser . DefaultOrgId . ToString ( ) ,
TenantId = adminUser . TenantId . ToString ( ) ,
TenantName = adminUser . TenantName
} ;
await tenantDb . Insertable ( record ) . ExecuteCommandAsync ( ) ;
var token = JwtHelper . Encrypt ( tokenModel , false , true ) ;
await SendRequestAsync ( token ) ;
}
}
}
//写入当前业务的费用
if ( feeList . Count > 0 )
finally
{
await SaveAsync ( tenantDb , feeList ) ;
feeList . Clear ( ) ;
}
tenantDb ? . Dispose ( ) ;
}
await tenantDb . Ado . CommitTranAsync ( ) ;
}
catch ( Exception ex )
{
await tenantDb . Ado . RollbackTranAsync ( ) ;
await ex . LogAsync ( db ) ;
}
}
/// <summary>
/// 费用保存
/// </summary>
/// <param name="tenantDb"></param>
/// <param name="items">要提交的费用记录</param>
/// <returns></returns>
static async Task SaveAsync ( SqlSugarClient tenantDb , List < FeeRecord > items )
internal async Task SendRequestAsync ( string token )
{
var first = items . Select ( x = > new { x . BusinessType , x . BusinessId } ) . FirstOrDefault ( ) ;
//if (await IsFeeLockedAsync(first.BusinessId, first.BusinessType))
// return DataResult.FailedWithDesc(nameof(MultiLanguageConst.FeeLocked));
var order = await tenantDb . Queryable < SeaExport > ( ) . Where ( x = > x . Id = = first . BusinessId ) . Select ( x = > new
if ( api . BaseUri = = null )
{
x . TEU ,
x . KGS , //毛重
x . PKGS , //件数
x . CBM //尺码
} ) . FirstAsync ( ) ;
if ( order = = null )
return ;
//获取订单箱型箱量
string bsNo = first . BusinessId . ToString ( ) ;
var ctns = await tenantDb . Queryable < OpCtn > ( ) . Where ( y = > y . BSNO = = bsNo ) . Select ( x = > new
{
x . CtnCode ,
x . CtnNum
} ) . ToListAsync ( ) ;
items = items . FindAll ( x = > x . Amount ! = 0 ) ;
foreach ( var item in items )
{
if ( item . Quantity = = 0 )
{
//逐个判定处理标准
switch ( item . Unit )
{
case "HOUR" : //小时
goto case "P" ;
case "GE" : //个
goto case "P" ;
case "DAY" : //天
goto case "P" ;
//case "XX": //箱型
// goto default;
case "JJZL" : //计价重量
goto case "Z" ;
var baseUrl = configuration [ "AutoFeeTemplate:BaseUrl" ] ;
if ( string . IsNullOrEmpty ( baseUrl ) )
throw new ApplicationException ( "未配置自动费用模板请求基础URL" ) ;
case "ZJ" : //总价
goto case "P" ;
case "JZ" : //净重
item . Quantity = 0 ;
break ;
case "TEU" : //TEU
item . Quantity = order . TEU ;
break ;
case "JF" : //计费吨
item . Quantity = order . KGS . GetValueOrDefault ( ) / 1000 > order . CBM . GetValueOrDefault ( ) ? order . KGS . GetValueOrDefault ( ) : order . CBM . GetValueOrDefault ( ) ;
break ;
case "J" : //件数
item . Quantity = order . PKGS . GetValueOrDefault ( ) ;
break ;
case "C" : //尺码
item . Quantity = order . CBM . GetValueOrDefault ( ) ;
break ;
case "Z" : //重量
item . Quantity = order . KGS . GetValueOrDefault ( ) ;
break ;
case "P" : //单票
item . Quantity = 1 ;
break ;
default : //查找箱型标准
var ctn = ctns . Find ( x = > x . CtnCode = = item . Unit ) ;
item . Quantity = ctn = = null ? 0 : ctn . CtnNum . GetValueOrDefault ( ) ;
break ;
}
api . BaseUri = new Uri ( baseUrl , UriKind . Absolute ) ;
}
//计算税费
item . SetTax ( ) ;
}
//若计价货币单位不等于本位币则尝试获取最新汇率
await FetchExchangeRateAsync ( tenantDb , items ) ;
//写入费用
await tenantDb . Insertable ( items ) . ExecuteCommandAsync ( ) ;
}
static async Task FetchExchangeRateAsync ( SqlSugarClient tenantDb , List < FeeRecord > items )
{
var exRecords = items . Where ( x = > x . Currency ! = x . LocalCurrency & & x . ExchangeRate = = null ) ;
if ( exRecords . Any ( ) )
{
var exchanges = exRecords . GroupBy ( x = > new
{
x . Currency ,
x . LocalCurrency ,
x . FeeType ,
} ) . Select ( x = > new ExchangeRate
api . DefaultHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , token ) ;
var response = await api . SendRequestAsync ( HttpMethod . Post , configuration [ "AutoFeeTemplate:GenerateFeesUrl" ] ! , new
{
CurrencyFrom = x . Key . Currency ,
CurrencyTo = x . Key . LocalCurrency ,
FeeType = x . Key . FeeType
etd = DateTime . Now . Date
} ) ;
List < ExchangeRate > exchangeRates = [ ] ;
foreach ( var item in exchanges )
{
var result = await GetExchangeRateAsync ( tenantDb , item ) ;
if ( result . Succeeded & & result . Data ! = null )
exchangeRates . Add ( result . Data ) ;
}
foreach ( var item in items )
{
item . ExchangeRate = exchangeRates . Find ( x = > x . CurrencyFrom = = item . Currency & &
x . CurrencyTo = = item . LocalCurrency & & x . FeeType = = item . FeeType ) ? . Rate ;
}
}
}
static async Task < DataResult < ExchangeRate > > GetExchangeRateAsync ( SqlSugarClient tenantDb , ExchangeRate exchange )
{
if ( exchange . CurrencyFrom = = exchange . CurrencyTo )
{
exchange . ReverseRate = exchange . Rate = 1 m ;
return DataResult < ExchangeRate > . Success ( exchange ) ;
}
//获取本位币,默认=人民币
string localCurrency = FeeCurrency . RMB_CODE ;
if ( string . IsNullOrWhiteSpace ( exchange . CurrencyTo ) )
{
exchange . CurrencyTo = localCurrency ;
}
//需要做二次转换
if ( exchange . CurrencyFrom ! = localCurrency & & exchange . CurrencyTo ! = localCurrency )
{
//获取中间汇率
var middleRate = await GetLocalRateAsync ( tenantDb , exchange . CurrencyFrom , localCurrency , exchange . FeeType ) ;
if ( middleRate = = null )
return DataResult < ExchangeRate > . FailedData ( exchange , message : $"{MultiLanguageConst.FeeCurrencyNotFound}: {exchange.CurrencyFrom}" ) ;
var rate = await GetLocalRateAsync ( tenantDb , exchange . CurrencyTo , localCurrency , exchange . FeeType ) ;
exchange . Rate = Math . Round ( 1 / ( rate ? ? 1 m ) * middleRate . GetValueOrDefault ( ) , 4 , MidpointRounding . AwayFromZero ) ;
exchange . ReverseRate = Math . Round ( 1 / exchange . Rate , 4 , MidpointRounding . AwayFromZero ) ;
}
else
{
string currency = exchange . CurrencyFrom = = FeeCurrency . RMB_CODE ? exchange . CurrencyTo : exchange . CurrencyFrom ;
var rate = await GetLocalRateAsync ( tenantDb , currency , localCurrency , exchange . FeeType ) ;
if ( currency = = exchange . CurrencyFrom )
{
exchange . Rate = rate ? ? 1 m ;
exchange . ReverseRate = Math . Round ( 1 / exchange . Rate , 4 , MidpointRounding . AwayFromZero ) ;
}
else
{
exchange . ReverseRate = rate ? ? 1 m ;
exchange . Rate = Math . Round ( 1 / exchange . ReverseRate , 4 , MidpointRounding . AwayFromZero ) ;
}
}
return DataResult < ExchangeRate > . Success ( exchange ) ;
}
static async Task < decimal? > GetLocalRateAsync ( SqlSugarClient tenantDb , string currency , string localCurrency , FeeType ? type )
{
var entity = await tenantDb . Queryable < FeeCurrency > ( ) . Where ( x = > x . CodeName = = currency ) . Includes ( x = > x . Exchanges ) . FirstAsync ( ) ;
if ( entity = = null )
return null ;
var rate = entity . DefaultRate ;
DateTime dtNow = DateTime . Now ;
if ( type . HasValue & & entity . Exchanges ! = null & & entity . Exchanges . Count > 0 )
{
//取当前时间范围内的最新一条,优先获取本位币
var item = entity . Exchanges . FindAll ( x = > x . Status = = StatusEnum . Enable & & x . LocalCurrency = = localCurrency & &
x . StartDate > = dtNow & & x . EndDate < = dtNow ) . OrderByDescending ( x = > x . CreateTime ) . FirstOrDefault ( ) ;
item ? ? = entity . Exchanges . FindAll ( x = > x . Status = = StatusEnum . Enable & &
x . StartDate > = dtNow & & x . EndDate < = dtNow ) . OrderByDescending ( x = > x . CreateTime ) . FirstOrDefault ( ) ;
if ( item ! = null )
rate = type . Value = = FeeType . Receivable ? item . DRValue : item . CRValue ;
}
return rate ;
}
static async Task < List < FeeRecord > ? > CreateFeesIfMatchAsync ( SqlSugarClient tenantDb , SeaExportRes order , FeeCustTemplate template )
{
if ( ! string . IsNullOrEmpty ( template . POLCode ) & & template . POLCode ! = order . LoadPortCode )
return null ;
if ( ! string . IsNullOrEmpty ( template . PODCode ) & & template . PODCode ! = order . DischargePortCode )
return null ;
if ( ! string . IsNullOrEmpty ( template . MBLFrtCode ) & & template . MBLFrtCode ! = order . MBLFrtCode )
return null ;
if ( template . LaneId . HasValue & & template . LaneId ! = order . LaneId )
return null ;
if ( template . CarrierId . HasValue & & template . CarrierId ! = order . CarrierId )
return null ;
if ( template . SourceId . HasValue & & template . SourceId ! = order . SourceId )
return null ;
if ( template . ForwarderId . HasValue & & template . ForwarderId ! = order . ForwarderId )
return null ;
if ( ! string . IsNullOrEmpty ( template . Condition ) ) //设置了自定义匹配条件
{
var conditionModel = JsonConvert . DeserializeObject < ConditionContent > ( template . Condition ) ;
if ( ! IsMatch ( order , conditionModel ) )
return null ;
}
if ( await tenantDb . Queryable < FeeCustTemplateRecord > ( ) . AnyAsync ( x = > x . BusinessId = = order . Id & & x . BusinessType = = BusinessType . OceanShippingExport & &
x . FeeType = = template . FeeType & & x . FeeCategoryId = = template . FeeCategoryId ) )
return null ;
var details = await tenantDb . Queryable < FeeCustTemplateDetail > ( ) . Where ( y = > y . TemplateId = = template . Id )
. Select ( x = > new FeeRecord
{
BusinessId = order . Id ,
BusinessType = BusinessType . OceanShippingExport ,
FeeType = template . FeeType ,
FeeId = x . FeeId ,
FeeCode = x . FeeCode ,
FeeName = x . FeeName ,
CustomerId = x . CustomerId ,
CustomerName = x . CustomerName ,
CustomerType = x . CustomerType ,
Unit = x . Unit ,
UnitPrice = SqlFunc . IsNull ( x . UnitPrice . Value , 0 ) ,
//Quantity = x.IsCtn ? 1 : 0,
Currency = x . Currency ,
ExchangeRate = x . ExchangeRate = = null ? 1 : x . ExchangeRate ,
TaxRate = SqlFunc . IsNull ( x . TaxRate . Value , 0 ) ,
AccTaxRate = SqlFunc . IsNull ( x . AccTaxRate . Value , 0 ) ,
Tax = SqlFunc . IsNull ( x . Tax . Value , 0 ) ,
TaxUnitPrice = SqlFunc . IsNull ( x . TaxUnitPrice . Value , 0 ) ,
IsInvoice = x . IsInvoice ,
IsAdvancedPay = x . IsAdvancedPay ,
LocalCurrency = FeeCurrency . RMB_CODE ,
Remark = template . FeeCategoryName ,
TemplateId = x . TemplateId ,
InputMethod = InputMethod . Automatic
} ) . ToListAsync ( ) ;
foreach ( var detail in details )
{
if ( detail . CustomerId = = 0 )
{
switch ( detail . CustomerType )
{
case "controller" :
detail . CustomerId = order . CustomerId ;
detail . CustomerName = order . CustomerName ;
break ;
case "yard" :
detail . CustomerId = order . YardId ;
detail . CustomerName = order . Yard ;
break ;
case "custom" :
detail . CustomerId = order . CustomserId ;
detail . CustomerName = order . Customser ;
break ;
case "contract" :
detail . CustomerId = order . ContractClientId ;
detail . CustomerName = order . ContractClientName ;
break ;
case "shipagency" :
detail . CustomerId = order . ShipAgencyId ;
detail . CustomerName = order . ShipAgency ;
break ;
case "shipper" :
detail . CustomerId = order . ShipperId . GetValueOrDefault ( ) ;
detail . CustomerName = order . Shipper ;
break ;
case "truck" :
detail . CustomerId = order . TruckerId ;
detail . CustomerName = order . Trucker ;
break ;
case "booking" :
detail . CustomerId = order . ForwarderId ;
detail . CustomerName = order . Forwarder ;
break ;
case "carrier" :
detail . CustomerId = order . CarrierId ;
detail . CustomerName = order . Carrier ;
break ;
case "wareHouse" :
detail . CustomerId = order . WareHouseId ;
detail . CustomerName = order . WareHouse ;
break ;
case "shippercn" :
detail . CustomerId = order . ShipperCnId . GetValueOrDefault ( ) ;
detail . CustomerName = order . ShipperCn ;
break ;
case "agent" :
detail . CustomerId = order . AgentId . GetValueOrDefault ( ) ;
detail . CustomerName = order . Agent ;
break ;
}
}
}
return details ;
}
static bool IsMatch ( object source , ConditionContent condition )
{
if ( source = = null | | condition = = null )
return false ;
TaskFlowDataContext dataContext = new ( ( TaskFlowDataNameConst . Business , source ) ) ;
if ( string . IsNullOrEmpty ( condition . SourceName ) )
condition . SourceName = TaskFlowDataNameConst . Business ;
return ConditionHelper . IsPass ( condition , dataContext ) ;
if ( ! response . IsSuccessStatusCode )
throw new ApplicationException ( "自动费用模板生成费用失败,详情查看日志" ) ;
}
}
}