You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

515 lines
19 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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>
/// 备注信息,数据字典, 部分参数目前不涉及, 只传备注文本即可
/// ONERemarkInfoONE订舱时备注信息
/// </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; }
}
}