|
|
using Furion;
|
|
|
using Furion.FriendlyException;
|
|
|
using Furion.Logging;
|
|
|
using Furion.RemoteRequest.Extensions;
|
|
|
using Myshipping.Application.EDI.Dtos;
|
|
|
using Myshipping.Application.Entity;
|
|
|
using Myshipping.Core;
|
|
|
using Myshipping.Core.Entity;
|
|
|
using Myshipping.Core.Service;
|
|
|
using Newtonsoft.Json;
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Text.RegularExpressions;
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
namespace Myshipping.Application.EDI
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// ONE API订舱
|
|
|
/// </summary>
|
|
|
public static class ONESoApiHelper
|
|
|
{
|
|
|
public async static Task<KeyValuePair<bool, string>> DoPost(long custOrderId)
|
|
|
{
|
|
|
var repCustOrder = App.GetService<SqlSugarRepository<BookingCustomerOrder>>();
|
|
|
var repOrder = App.GetService<SqlSugarRepository<BookingOrder>>();
|
|
|
var repCtn = App.GetService<SqlSugarRepository<BookingCtn>>();
|
|
|
var repCustomer = App.GetService<SqlSugarRepository<DjyCustomer>>();
|
|
|
var repContact = App.GetService<SqlSugarRepository<DjyCustomerContact>>();
|
|
|
var repTemplate = App.GetService<SqlSugarRepository<BookingSoTemplate>>();
|
|
|
var cache = App.GetService<ISysCacheService>();
|
|
|
|
|
|
var cacheService = App.GetService<ISysCacheService>();
|
|
|
|
|
|
var custOrder = await repCustOrder.AsQueryable().Filter(null, true).FirstAsync(x => x.Id == custOrderId);
|
|
|
if (custOrder == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "客户订舱信息未找到");
|
|
|
}
|
|
|
|
|
|
var sysConfigList = await cache.GetAllSysConfig();
|
|
|
var sCfgSpiderUrl = sysConfigList.FirstOrDefault(x => x.Code == "BookingPostApiServerAddr" && x.GroupCode == "DJY_CONST");
|
|
|
if (sCfgSpiderUrl == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "订舱API的URL地址未配置,请联系管理员");
|
|
|
}
|
|
|
|
|
|
var sCfgUserKey = sysConfigList.FirstOrDefault(x => x.Code == "BookingPostApiKey" && x.GroupCode == "DJY_CONST");
|
|
|
var sCfgUserSecret = sysConfigList.FirstOrDefault(x => x.Code == "BookingPostApiSecret" && x.GroupCode == "DJY_CONST");
|
|
|
if (sCfgUserKey == null || sCfgUserSecret == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "订舱API的KEY和密钥未配置,请联系管理员");
|
|
|
}
|
|
|
|
|
|
if (string.IsNullOrEmpty(custOrder.CARGOID))
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "订单货物类型为空");
|
|
|
}
|
|
|
|
|
|
if (custOrder.CARGOID is not ("S" or "R"))
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "订单货物类型需为“普通货”或“冻柜”");
|
|
|
}
|
|
|
|
|
|
BookingSoTemplate template = null;
|
|
|
DjyCustomerContact custContact = null;
|
|
|
var postModel = new ONESoApiModel
|
|
|
{
|
|
|
webAccount = custOrder.BookingAccount,
|
|
|
webPassword = custOrder.BookingPassword,
|
|
|
|
|
|
userKey = sCfgUserKey.Value,
|
|
|
userSecret = sCfgUserSecret.Value,
|
|
|
|
|
|
mark = new
|
|
|
{
|
|
|
BookingCustomerOrderId = custOrder.Id,
|
|
|
BookingNo = custOrder.BOOKINGNO,
|
|
|
BookingId = custOrder.BookingId,
|
|
|
}
|
|
|
};
|
|
|
|
|
|
//查找模板:
|
|
|
//1.根据客户订舱信息中的BookingUserId和BookingTenantId,去客户信息中根据CustSysId查找客户(公司)及联系人(员工)信息
|
|
|
//2.根据找到的客户及联系人信息,查找订舱模板
|
|
|
if (custOrder.BookingUserId > 0 && custOrder.BookingTenantId > 0)
|
|
|
{
|
|
|
custContact = await repCustomer.AsQueryable().Filter(null, true)
|
|
|
.InnerJoin<DjyCustomerContact>((cust, contact) => cust.Id == contact.CustomerId)
|
|
|
.Where((cust, contact) => cust.CustSysId == custOrder.BookingTenantId && contact.CustSysId == custOrder.BookingUserId)
|
|
|
.Select((cust, contact) => contact)
|
|
|
.SingleAsync();
|
|
|
if (custContact == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "未找到客户及联系人信息");
|
|
|
}
|
|
|
|
|
|
//根据:用户+船司+船司账号+约号,找到启用的模板
|
|
|
template = await repTemplate.AsQueryable().Filter(null, true).FirstAsync(x => x.IsDeleted == false && x.CarrierId == custOrder.CARRIERID && x.UserId == custContact.Id && x.IsEnable);
|
|
|
if (template == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "未找到订舱模板");
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, "未找到客户端公司和用户ID");
|
|
|
}
|
|
|
|
|
|
postModel.uploadType = template.Category; //DRAFT, TEMPLATE, BOOKING分别对应:草稿, 模板, 订舱
|
|
|
postModel.saveName = template.TemplateName;
|
|
|
|
|
|
var mappingCtn = await cache.GetAllMappingCtn();
|
|
|
var mappingFrt = await cache.GetAllMappingFrt();
|
|
|
var mappingPortLoad = await cache.GetAllMappingPortLoad();
|
|
|
var mappingPort = await cache.GetAllMappingPort();
|
|
|
|
|
|
//收货地
|
|
|
var mapPlaceReceipt = mappingPortLoad.FirstOrDefault(x => x.Module == "DjyCustBooking" && x.CarrierCode == CarrierCodeConst.ONE && x.Code == custOrder.PLACERECEIPTCODE);
|
|
|
if (mapPlaceReceipt == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"未找到收货地映射信息:{custOrder.PLACERECEIPTCODE}");
|
|
|
}
|
|
|
|
|
|
//目的地
|
|
|
var mapDestination = mappingPort.FirstOrDefault(x => x.Module == "DjyCustBooking" && x.CarrierCode == CarrierCodeConst.EMC && x.Code == custOrder.DESTINATIONCODE);
|
|
|
if (mapDestination == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"未找到目的地映射信息:{custOrder.DESTINATIONCODE}");
|
|
|
}
|
|
|
|
|
|
//运输条款
|
|
|
var mappingService = await cacheService.GetAllMappingService();
|
|
|
var mappService = mappingService.FirstOrDefault(x => x.Module == "DjyCustBooking" && x.CarrierCode == CarrierCodeConst.ONE && x.Code == custOrder.SERVICE);
|
|
|
if (mappService == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"未找到运输条款映射信息:{custOrder.SERVICE}");
|
|
|
}
|
|
|
|
|
|
if (!Regex.IsMatch(mappService.MapCode, "^[A-Za-z]+-[A-Za-z]+$"))
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"映射配置不正确:{mappService.MapCode}");
|
|
|
}
|
|
|
string[] arrService = null;
|
|
|
if (mappService == null)
|
|
|
{
|
|
|
arrService = custOrder.SERVICE.Split('-');
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
arrService = mappService.MapCode.Split('-');
|
|
|
}
|
|
|
|
|
|
//routes
|
|
|
postModel.routes = new ONESoApiRoute()
|
|
|
{
|
|
|
searchConditionDate = custOrder.ETD.Value.ToString("yyyy-MM-dd"),
|
|
|
originName = mapPlaceReceipt.MapCode,
|
|
|
destinationName = mapDestination.MapCode,
|
|
|
numberOfWeeks = "2",
|
|
|
outboundHaulage = arrService[0],
|
|
|
inboundHaulage = arrService[1],
|
|
|
};
|
|
|
|
|
|
// shipInfo
|
|
|
var extData = JObject.Parse(custOrder.ExtendData);
|
|
|
postModel.shipInfo = extData.GetJObjectValue("shipInfo");
|
|
|
|
|
|
//合约信息
|
|
|
postModel.contractInfo = new ONESoApiContractInfo()
|
|
|
{
|
|
|
contractNo = custOrder.CONTRACTNO,
|
|
|
namedAccount = custOrder.NamedAccount
|
|
|
};
|
|
|
|
|
|
decimal ctnAllKgs = 0;
|
|
|
|
|
|
// boxInfos
|
|
|
var ctns = await repCtn.AsQueryable().Filter(null, true).Where(x => x.BILLID == custOrder.Id).ToListAsync();
|
|
|
postModel.boxInfos = new List<ONESoApiBoxInfo>();
|
|
|
foreach (var ctn in ctns)
|
|
|
{
|
|
|
if (!ctn.KGS.HasValue)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"所有箱的毛重都不能为空");
|
|
|
}
|
|
|
ctnAllKgs += (decimal)ctn.KGS;
|
|
|
|
|
|
var mapCtn = mappingCtn.FirstOrDefault(x => x.Module == "DjyCustBooking" && x.CarrierCode == CarrierCodeConst.ONE && x.Code == ctn.CTNCODE);
|
|
|
if (mapCtn == null)
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, $"未找箱型映射信息:{ctn.CTNCODE}");
|
|
|
}
|
|
|
|
|
|
var apiBox = new ONESoApiBoxInfo()
|
|
|
{
|
|
|
boxType = custOrder.CARGOID switch
|
|
|
{
|
|
|
"S" => "DRY",
|
|
|
"R" => "REEFER",
|
|
|
_ => throw Oops.Oh("暂不支持的货物类型")
|
|
|
},
|
|
|
boxSize = mapCtn.MapCode,
|
|
|
quantity = ctn.KGS.Value,
|
|
|
};
|
|
|
postModel.boxInfos.Add(apiBox);
|
|
|
}
|
|
|
|
|
|
postModel.cargoInfo = new ONESoApiCargoInfo()
|
|
|
{
|
|
|
commodity = custOrder.DESCRIPTION,
|
|
|
weight = ctnAllKgs
|
|
|
};
|
|
|
postModel.contactInfo = new ONESoApiContactInfo()
|
|
|
{
|
|
|
shipperName = custOrder.ShipperName,
|
|
|
forwarderName = custOrder.BookingName
|
|
|
};
|
|
|
postModel.remarkInfo = new ONESoApiRemarkInfo()
|
|
|
{
|
|
|
officeName = custOrder.BookingAddr
|
|
|
};
|
|
|
postModel.copyNum = custOrder.CopyNum;
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(custOrder.OpMail))
|
|
|
{
|
|
|
var mails = custOrder.OpMail.Split(',').ToList();
|
|
|
mails.RemoveAll(x => string.IsNullOrWhiteSpace(x));
|
|
|
postModel.emailList = mails;
|
|
|
}
|
|
|
|
|
|
var apiUrl = sCfgSpiderUrl.Value;
|
|
|
if (!apiUrl.EndsWith("/"))
|
|
|
{
|
|
|
apiUrl += "/";
|
|
|
}
|
|
|
apiUrl += "v1/one/booking/auto";
|
|
|
|
|
|
var jsonStr = JsonConvert.SerializeObject(postModel, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
|
|
|
|
|
|
Log.Information($"发送API数据给爬虫,custOrder.BookingId:{custOrder.BookingId},custOrder.BOOKINGNO:{custOrder.BOOKINGNO},custOrder.BookingId:{custOrder.BookingId}({apiUrl}):{jsonStr}");
|
|
|
var rtn = await apiUrl.SetBody(jsonStr, "application/json")
|
|
|
.PostAsStringAsync();
|
|
|
|
|
|
Log.Information($"爬虫返回:{rtn}");
|
|
|
var jobjRtn = JObject.Parse(rtn);
|
|
|
if (jobjRtn.GetIntValue("code") == 200)
|
|
|
{
|
|
|
var jdata = jobjRtn.GetJObjectValue("data");
|
|
|
if (jdata != null)
|
|
|
{
|
|
|
JObject extObj = null;
|
|
|
if (!string.IsNullOrEmpty(custOrder.ExtendData))
|
|
|
{
|
|
|
extObj = JObject.Parse(custOrder.ExtendData);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
extObj = new JObject();
|
|
|
}
|
|
|
|
|
|
var bookingNoArr = jdata.GetJArrayValue("bookingNoList");
|
|
|
extObj["CustNO"] = bookingNoArr;
|
|
|
custOrder.ExtendData = extObj.ToJsonString();
|
|
|
await repCustOrder.AsUpdateable(custOrder).UpdateColumns(x => new { x.ExtendData }).ExecuteCommandAsync();
|
|
|
Log.Information($"回写CC号 {custOrder.Id} {custOrder.BOOKINGNO}");
|
|
|
}
|
|
|
|
|
|
return new KeyValuePair<bool, string>(true, "发送成功");
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return new KeyValuePair<bool, string>(false, jobjRtn.GetStringValue("msg"));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// ONE订舱传输对象
|
|
|
/// </summary>
|
|
|
public class ONESoApiModel : BookingHelperBaseModel
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 箱信息数组,箱型箱量等信息
|
|
|
/// </summary>
|
|
|
public List<ONESoApiBoxInfo> boxInfos { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 货物信息数据,品名, 重量等信息
|
|
|
/// </summary>
|
|
|
public ONESoApiCargoInfo cargoInfo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 收发通等联系信息,按照网站规则, 只提供对应的名字即可, 但是需要与网站上一致
|
|
|
/// </summary>
|
|
|
public ONESoApiContactInfo contactInfo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 约号相关信息,约号必填, namedAccount可不传, 但是必须与实际对应
|
|
|
/// </summary>
|
|
|
public ONESoApiContractInfo contractInfo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 备注信息,数据字典, 部分参数目前不涉及, 只传备注文本即可
|
|
|
/// </summary>
|
|
|
public ONESoApiRemarkInfo remarkInfo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 路线信息,港口,船期等信息
|
|
|
/// </summary>
|
|
|
public ONESoApiRoute routes { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 模板名字,保存为模板时这个字段必填
|
|
|
/// </summary>
|
|
|
public string saveName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 船期数据,船期查询的结果中选择的数据集, 原样上传
|
|
|
/// </summary>
|
|
|
public object shipInfo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 复制的单数
|
|
|
/// </summary>
|
|
|
public int copyNum { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 邮件联系人列表
|
|
|
/// </summary>
|
|
|
public List<string> emailList { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// ONEBOxInfo,箱相关数据
|
|
|
/// </summary>
|
|
|
public class ONESoApiBoxInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 箱型,每个箱类型对应自己可选的箱型, 需要做映射
|
|
|
/// </summary>
|
|
|
public string boxSize { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 箱类型,每个箱类型对应自己可选的箱型, 需要做映射
|
|
|
/// </summary>
|
|
|
public string boxType { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 箱量,当前箱型的总箱量
|
|
|
/// </summary>
|
|
|
public decimal quantity { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// SOC箱量,不能大于总箱量
|
|
|
/// </summary>
|
|
|
public long? socQuantity { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 货物信息数据,品名, 重量等信息
|
|
|
///
|
|
|
/// ONECargoInfo,货物相关信息
|
|
|
/// </summary>
|
|
|
public class ONESoApiCargoInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 品名,需和网站上一致
|
|
|
/// </summary>
|
|
|
public string commodity { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 返回的日期,精确到天, 可以不传
|
|
|
/// </summary>
|
|
|
public string returnDate { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 重量,重量
|
|
|
/// </summary>
|
|
|
public decimal weight { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 重量单位,重量单位
|
|
|
/// </summary>
|
|
|
public string weightUnit { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 收发通等联系信息,按照网站规则, 只提供对应的名字即可, 但是需要与网站上一致
|
|
|
/// ONEContactInfo,联系人相关数据
|
|
|
/// </summary>
|
|
|
public class ONESoApiContactInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 收货人,可以不传
|
|
|
/// </summary>
|
|
|
public string consigneeName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 货代名,和网站上保值一致, 且已经在账户的通讯录中
|
|
|
/// </summary>
|
|
|
public string forwarderName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 发货人名字,和网站上保值一致, 且已经在账户的通讯录中
|
|
|
/// </summary>
|
|
|
public string shipperName { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 约号相关信息,约号必填, namedAccount可不传, 但是必须与实际对应
|
|
|
/// ONEContractInfo,客户约号信息
|
|
|
/// </summary>
|
|
|
public class ONESoApiContractInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 客户约号,必填,且应与网站数据一致
|
|
|
/// </summary>
|
|
|
public string contractNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 约号对应的账户名,如果传则必须与网站一直, 不传则默认选择 Unable to find Named Account or Not Applicable
|
|
|
/// </summary>
|
|
|
public string namedAccount { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 备注信息,数据字典, 部分参数目前不涉及, 只传备注文本即可
|
|
|
/// ONERemarkInfo,ONE订舱时备注信息
|
|
|
/// </summary>
|
|
|
public class ONESoApiRemarkInfo
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Booking Freight Forwarder Ref. No.
|
|
|
/// </summary>
|
|
|
public string bkgFFRefNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Booking Shipper Ref. No.
|
|
|
/// </summary>
|
|
|
public string bkgShRefNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Invoice Ref. No.
|
|
|
/// </summary>
|
|
|
public string invoiceRefNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Manual Booking Number
|
|
|
/// </summary>
|
|
|
public string manualBookingNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 订舱网点名
|
|
|
/// </summary>
|
|
|
public string officeName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// S/I Freight Forwarder No.
|
|
|
/// </summary>
|
|
|
public string siFFNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// S/I Shipper Ref. No.
|
|
|
/// </summary>
|
|
|
public string siSHRefNo { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 备注信息,备注信息, 默认为空
|
|
|
/// </summary>
|
|
|
public string specialInstruction { get; set; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 路线信息,港口,船期等信息
|
|
|
/// OneAutoRoutes,路线信息
|
|
|
/// </summary>
|
|
|
public class ONESoApiRoute
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 交货地,交货地, 与网站上应一致, 注意不要CY及DOOR信息, 运输方式有相关参数
|
|
|
/// </summary>
|
|
|
public string destinationName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 运送形态
|
|
|
/// </summary>
|
|
|
public string inboundHaulage { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 时间范围,按周计算, 取值范围2, 4, 6, 8
|
|
|
/// </summary>
|
|
|
public string numberOfWeeks { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 收货地,收货地, 与网站上应一致, 注意不要CY及DOOR信息, 运输方式有相关参数
|
|
|
/// </summary>
|
|
|
public string originName { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 运送方式
|
|
|
/// </summary>
|
|
|
public string outboundHaulage { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
|
/// 查询日期,默认为两天后
|
|
|
/// </summary>
|
|
|
public string searchConditionDate { get; set; }
|
|
|
}
|
|
|
|
|
|
}
|