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 { /// /// ONE API订舱 /// public static class ONESoApiHelper { public async static Task> DoPost(long custOrderId) { var repCustOrder = App.GetService>(); var repOrder = App.GetService>(); var repCtn = App.GetService>(); var repCustomer = App.GetService>(); var repContact = App.GetService>(); var repTemplate = App.GetService>(); var cache = App.GetService(); var cacheService = App.GetService(); var custOrder = await repCustOrder.AsQueryable().Filter(null, true).FirstAsync(x => x.Id == custOrderId); if (custOrder == null) { return new KeyValuePair(false, "客户订舱信息未找到"); } var sysConfigList = await cache.GetAllSysConfig(); var sCfgSpiderUrl = sysConfigList.FirstOrDefault(x => x.Code == "BookingPostApiServerAddr" && x.GroupCode == "DJY_CONST"); if (sCfgSpiderUrl == null) { return new KeyValuePair(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(false, "订舱API的KEY和密钥未配置,请联系管理员"); } if (string.IsNullOrEmpty(custOrder.CARGOID)) { return new KeyValuePair(false, "订单货物类型为空"); } if (custOrder.CARGOID is not ("S" or "R")) { return new KeyValuePair(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((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(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(false, "未找到订舱模板"); } } else { return new KeyValuePair(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(false, $"未找到收货地映射信息:{custOrder.PLACERECEIPTCODE}"); } //目的地 var mapDestination = mappingPort.FirstOrDefault(x => x.Module == "DjyCustBooking" && x.CarrierCode == CarrierCodeConst.ONE && x.Code == custOrder.DESTINATIONCODE); if (mapDestination == null) { return new KeyValuePair(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(false, $"未找到运输条款映射信息:{custOrder.SERVICE}"); } if (!Regex.IsMatch(mappService.MapCode, "^[A-Za-z]+-[A-Za-z]+$")) { return new KeyValuePair(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(); foreach (var ctn in ctns) { if (!ctn.KGS.HasValue) { return new KeyValuePair(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(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, consigneeName = custOrder.ConsigneeName, }; postModel.remarkInfo = new ONESoApiRemarkInfo() { officeName = custOrder.BookingAddr, specialInstruction = custOrder.SOREMARK }; postModel.copyNum = custOrder.CopyNum; if (!string.IsNullOrWhiteSpace(custOrder.OpMail)) { var mailStr = custOrder.OpMail.Replace(",", ","); var mailList = mailStr.Split(',').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).ToList(); postModel.emailList = mailList; } //else if (!string.IsNullOrEmpty(custContact.Email)) //{ // postModel.emailList = new List() { custContact.Email }; //} //else if (!string.IsNullOrEmpty(template.BcReceiveEmail)) //{ // postModel.emailList = new List() { template.BcReceiveEmail }; //} 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(true, "发送成功"); } else { return new KeyValuePair(false, jobjRtn.GetStringValue("msg")); } } } /// /// ONE订舱传输对象 /// public class ONESoApiModel : BookingHelperBaseModel { /// /// 箱信息数组,箱型箱量等信息 /// public List boxInfos { get; set; } /// /// 货物信息数据,品名, 重量等信息 /// public ONESoApiCargoInfo cargoInfo { get; set; } /// /// 收发通等联系信息,按照网站规则, 只提供对应的名字即可, 但是需要与网站上一致 /// public ONESoApiContactInfo contactInfo { get; set; } /// /// 约号相关信息,约号必填, namedAccount可不传, 但是必须与实际对应 /// public ONESoApiContractInfo contractInfo { get; set; } /// /// 备注信息,数据字典, 部分参数目前不涉及, 只传备注文本即可 /// public ONESoApiRemarkInfo remarkInfo { get; set; } /// /// 路线信息,港口,船期等信息 /// public ONESoApiRoute routes { get; set; } /// /// 模板名字,保存为模板时这个字段必填 /// public string saveName { get; set; } /// /// 船期数据,船期查询的结果中选择的数据集, 原样上传 /// public object shipInfo { get; set; } /// /// 复制的单数 /// public int copyNum { get; set; } /// /// 邮件联系人列表 /// public List emailList { get; set; } } /// /// ONEBOxInfo,箱相关数据 /// public class ONESoApiBoxInfo { /// /// 箱型,每个箱类型对应自己可选的箱型, 需要做映射 /// public string boxSize { get; set; } /// /// 箱类型,每个箱类型对应自己可选的箱型, 需要做映射 /// public string boxType { get; set; } /// /// 箱量,当前箱型的总箱量 /// public decimal quantity { get; set; } /// /// SOC箱量,不能大于总箱量 /// public long? socQuantity { get; set; } } /// /// 货物信息数据,品名, 重量等信息 /// /// ONECargoInfo,货物相关信息 /// public class ONESoApiCargoInfo { /// /// 品名,需和网站上一致 /// public string commodity { get; set; } /// /// 返回的日期,精确到天, 可以不传 /// public string returnDate { get; set; } /// /// 重量,重量 /// public decimal weight { get; set; } /// /// 重量单位,重量单位 /// public string weightUnit { get; set; } } /// /// 收发通等联系信息,按照网站规则, 只提供对应的名字即可, 但是需要与网站上一致 /// ONEContactInfo,联系人相关数据 /// public class ONESoApiContactInfo { /// /// 收货人,可以不传 /// public string consigneeName { get; set; } /// /// 货代名,和网站上保值一致, 且已经在账户的通讯录中 /// public string forwarderName { get; set; } /// /// 发货人名字,和网站上保值一致, 且已经在账户的通讯录中 /// public string shipperName { get; set; } } /// /// 约号相关信息,约号必填, namedAccount可不传, 但是必须与实际对应 /// ONEContractInfo,客户约号信息 /// public class ONESoApiContractInfo { /// /// 客户约号,必填,且应与网站数据一致 /// public string contractNO { get; set; } /// /// 约号对应的账户名,如果传则必须与网站一直, 不传则默认选择 Unable to find Named Account or Not Applicable /// public string namedAccount { get; set; } } /// /// 备注信息,数据字典, 部分参数目前不涉及, 只传备注文本即可 /// ONERemarkInfo,ONE订舱时备注信息 /// public class ONESoApiRemarkInfo { /// /// Booking Freight Forwarder Ref. No. /// public string bkgFFRefNo { get; set; } /// /// Booking Shipper Ref. No. /// public string bkgShRefNo { get; set; } /// /// Invoice Ref. No. /// public string invoiceRefNo { get; set; } /// /// Manual Booking Number /// public string manualBookingNo { get; set; } /// /// 订舱网点名 /// public string officeName { get; set; } /// /// S/I Freight Forwarder No. /// public string siFFNo { get; set; } /// /// S/I Shipper Ref. No. /// public string siSHRefNo { get; set; } /// /// 备注信息,备注信息, 默认为空 /// public string specialInstruction { get; set; } } /// /// 路线信息,港口,船期等信息 /// OneAutoRoutes,路线信息 /// public class ONESoApiRoute { /// /// 交货地,交货地, 与网站上应一致, 注意不要CY及DOOR信息, 运输方式有相关参数 /// public string destinationName { get; set; } /// /// 运送形态 /// public string inboundHaulage { get; set; } /// /// 时间范围,按周计算, 取值范围2, 4, 6, 8 /// public string numberOfWeeks { get; set; } /// /// 收货地,收货地, 与网站上应一致, 注意不要CY及DOOR信息, 运输方式有相关参数 /// public string originName { get; set; } /// /// 运送方式 /// public string outboundHaulage { get; set; } /// /// 查询日期,默认为两天后 /// public string searchConditionDate { get; set; } } }