using Microsoft.Extensions.Logging; using Myshipping.Application.Entity; using Myshipping.Core.Service; using Myshipping.Core; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using Furion; using Mapster; using Microsoft.AspNetCore.Mvc; using Furion.DependencyInjection; using Furion.DynamicApiController; using System.Text.RegularExpressions; using Myshipping.Application.Helper; using Myshipping.Core.Const; using Yitter.IdGenerator; namespace Myshipping.Application.Service.BookingOrder { /// /// 马士基 SPOT /// [ApiDescriptionSettings("Application", Name = "BookingMSKSPOTAPI", Order = 10)] public class BookingMSKSPOTAPIService: IBookingMSKSPOTAPIService, IDynamicApiController, ITransient { private readonly SqlSugarRepository _bookingDeliveryRecordRep; private readonly SqlSugarRepository _bookingDeliveryRecordCtnRep; private readonly ISysCacheService _cache; private readonly IDjyWebsiteAccountConfigService _webAccountConfig; private readonly ILogger _logger; private readonly ISysDataUserMenu _sysDataUserMenuService; const string CONST_MSK_API_COMMODITY_URL = "MSKApiCommodity"; const string CONST_MSK_API_BOOKING_URL = "MSKApiBooking"; const string CONST_MSK_SPOT_API_QUERY_SCHEDULE_RATE_URL = "MSKSPOTScheduleRate"; const string CONST_MSK_SPOT_API_LOCATION_URL = "MSKSPOTApilocation"; public BookingMSKSPOTAPIService(ILogger logger, ISysCacheService cache, IDjyWebsiteAccountConfigService webAccountConfig, SqlSugarRepository bookingDeliveryRecordRep, ISysDataUserMenu sysDataUserMenuService, SqlSugarRepository bookingDeliveryRecordCtnRep) { _logger = logger; _cache = cache; _webAccountConfig = webAccountConfig; _bookingDeliveryRecordRep = bookingDeliveryRecordRep; _bookingDeliveryRecordCtnRep = bookingDeliveryRecordCtnRep; _sysDataUserMenuService = sysDataUserMenuService; } #region 检索始发地、目的港口信息 /// /// 检索始发地、目的港口信息 /// /// 请求详情 /// 返回检索结果 [HttpPost("/BookingMSKSPOTAPI/SearchLocations")] public async Task> SearchLocations(QuerySPOTLocationsDto model) { List list = new List(); /* MSKApiCommodity */ try { if (string.IsNullOrWhiteSpace(model.cityName)) throw Oops.Oh("港口或城市名称不能为空"); if (string.IsNullOrWhiteSpace(model.carrierCode)) throw Oops.Oh("服务船公司不能为空"); if (string.IsNullOrWhiteSpace(model.carrierId)) throw Oops.Oh("船公司代码不能为空"); if (model.cityName.Length < 3) throw Oops.Oh("港口或城市名称至少输入3个以上字符"); string queryUrl = string.Empty; if (model.carrierId.Equals("MSK", StringComparison.OrdinalIgnoreCase)) { queryUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == CONST_MSK_SPOT_API_LOCATION_URL)?.Value; } else { throw Oops.Oh($"当前船公司 {model.carrierId} 未配置相应的请求接口"); } if (string.IsNullOrWhiteSpace(queryUrl)) throw Oops.Oh("未配置商品请求接口地址,请联系管理员"); var webAccountConfig = _webAccountConfig .GetAccountConfig("MSKSpotApi", UserManager.UserId).GetAwaiter().GetResult(); if (webAccountConfig == null) throw Oops.Oh("未配检索始发地、目的港口,请先配置个人账户 类型-MSKApi"); MSKAPISPOTSearchLocationDto queryInfo = new MSKAPISPOTSearchLocationDto { userKey = App.Configuration["MSKAPIDjyUserKey"], userSecret = App.Configuration["MSKAPIDjyUserSecret"], operatingEnvironment = "PRODUCTION",//App.Configuration["MSKAPIOPEnvironment"], spot_api_key = webAccountConfig.Account, brandScac = model.carrierCode, originPrefix = model.cityName }; MSKAPISPOTSearchLocationResultDto resultInfo = null; var rlt = await queryUrl.SetBody(queryInfo) .PostAsStringAsync(); if (!string.IsNullOrWhiteSpace(rlt)) { try { resultInfo = JSON.Deserialize(rlt); } catch (Exception ex) { _logger.LogInformation($"请求MSK API检索始发地、目的港口异常,原因:{ex.Message}"); throw Oops.Bah($"请求MSK API检索始发地、目的港口异常,原因:{ex.Message}"); } } if (resultInfo != null && resultInfo.code == 200 && resultInfo.data != null && resultInfo.data.Count > 0) { list = resultInfo.data; } } catch (Exception ex) { _logger.LogError($"检索始发地、目的港口异常,req={JSON.Serialize(model)} 原因:{ex.Message}"); throw Oops.Bah($"检索始发地、目的港口失败,{ex.Message}"); } return list; } #endregion #region 检索海运船期详情 /// /// 检索海运船期详情 /// /// 请求船期详情 /// 返回船期结果 [HttpPost("/BookingMSKSPOTAPI/SearchShipSailingSchedule")] public async Task> SearchShipSailingSchedule(QueryMSKSPOTShipSailingScheduleDto model) { List list = new List(); /* MSKApiSailingSchedulePoint2Point */ try { if (string.IsNullOrWhiteSpace(model.originGeoId)) throw Oops.Oh($"起运地不能为空"); if (string.IsNullOrWhiteSpace(model.destinationGeoId)) throw Oops.Oh($"目的地不能为空"); if (string.IsNullOrWhiteSpace(model.brandScac)) throw Oops.Oh($"服务船公司不能为空"); if (string.IsNullOrWhiteSpace(model.carrierId)) throw Oops.Oh("船公司代码不能为空"); if (string.IsNullOrWhiteSpace(model.departureDate)) throw Oops.Oh($"预计离港日期不能为空"); DateTime etd = DateTime.MinValue; if (!DateTime.TryParse(model.departureDate, out etd)) throw Oops.Oh($"预计离港日期格式错误"); if (string.IsNullOrWhiteSpace(model.containerType)) throw Oops.Oh($"箱型不能为空"); string queryUrl = string.Empty; if (model.carrierId.Equals("MSK", StringComparison.OrdinalIgnoreCase)) { queryUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == CONST_MSK_SPOT_API_QUERY_SCHEDULE_RATE_URL)?.Value; } else { throw Oops.Oh($"当前船公司 {model.carrierId} 未配置相应的请求接口"); } if (string.IsNullOrWhiteSpace(queryUrl)) throw Oops.Oh("未配置查询船期请求接口地址,请联系管理员"); var webAccountConfig = _webAccountConfig .GetAccountConfig("MSKSpotApi", UserManager.UserId).GetAwaiter().GetResult(); if (webAccountConfig == null) throw Oops.Oh("未配置个人账户,请先配置个人账户 类型-MSKApi"); MSKAPISPOTSearchScheduleRateDto queryInfo = new MSKAPISPOTSearchScheduleRateDto { userKey = App.Configuration["MSKAPIDjyUserKey"], userSecret = App.Configuration["MSKAPIDjyUserSecret"], operatingEnvironment = "PRODUCTION",//App.Configuration["MSKAPIOPEnvironment"], spot_api_key = webAccountConfig.Account, originServiceMode = model.originServiceMode, destinationServiceMode = model.destinationServiceMode, originGeoId = model.originGeoId, destinationGeoId = model.destinationGeoId, brandScac = model.brandScac, departureDate = etd.ToString("yyyy-MM-dd"), }; //有时候船期需要带上箱型检索 if (!string.IsNullOrWhiteSpace(model.containerType)) { //这里需要翻译一下箱型 var ctnCodeMappingList = _cache.GetAllMappingCtn().GetAwaiter().GetResult().ToList(); if (ctnCodeMappingList.Count > 0) ctnCodeMappingList = ctnCodeMappingList.Where(x => x.CarrierCode == "MSK" && x.Module == "BOOK_MSK_SPOT_API").ToList(); var ctnMapping = ctnCodeMappingList.FirstOrDefault(t => t.Code.Equals(model.containerType)); if (ctnMapping == null) { queryInfo.containerType = model.containerType; } else { queryInfo.containerType = ctnMapping.MapCode; } } if (model.containerCount.HasValue) { queryInfo.containerCount = model.containerCount.Value; } if (model.containerWeight.HasValue) { queryInfo.containerWeight = model.containerWeight.Value; } MSKAPISPOTSearchScheduleRateResultDto resultInfo = null; var jsonBody = Newtonsoft.Json.JsonConvert.SerializeObject(queryInfo); var rlt = await queryUrl.SetBody(jsonBody) .PostAsStringAsync(); _logger.LogInformation($"请求MSK SPOT API查询船期请求,{jsonBody}"); if (!string.IsNullOrWhiteSpace(rlt)) { try { _logger.LogInformation($"请求MSK SPOT API查询船期结果,{rlt}"); resultInfo = JSON.Deserialize(rlt); } catch (Exception ex) { _logger.LogInformation($"请求MSK SPOT API查询船期异常,原因:{ex.Message}"); throw Oops.Bah($"请求MSK API查询船期异常,原因:{ex.Message}"); } } if (resultInfo != null && resultInfo.code == 200 && resultInfo.data != null && resultInfo.data.Count > 0) { list = resultInfo.data; foreach (var scheduleModel in list) { await CacheShipSailingSchedule(scheduleModel); } } else { throw Oops.Bah($"检索海运船期详情失败,{resultInfo.msg}"); } } catch (Exception ex) { _logger.LogError($"检索海运船期详情异常,req={JSON.Serialize(model)} 原因:{ex.Message}"); throw Oops.Bah($"检索海运船期详情失败,{ex.Message}"); } return list; } #endregion #region 缓存船期数据 /// /// 缓存船期数据 /// /// 船期查询结果明细 /// 船期类型 MSKSPOT-马士基即期 /// 返回主键ID private async Task CacheShipSailingSchedule(MSKAPISPOTSearchScheduleRateResultDataDto model,string busiType = "MSKSPOT") { /* 1、按照船期明细缓存,并把ID作为缓存的KEY 2、对数据进行JSON串行化的文本提取MD5 3、写入缓存,并返回ID */ var newModel = model.Adapt(); newModel.priceID = null; var json = Newtonsoft.Json.JsonConvert.SerializeObject(newModel); model.PId = YitIdHelper.NextId(); string md5 = json.ToMd5(); model.MD5 = md5; var shareKey = model.PId.ToString(); DateTime nowDate = DateTime.Now; DateTime expireDateTime = DateTime.Now.AddHours(1); var expireTimeSpan = expireDateTime.Subtract(nowDate).Duration(); if (!_cache.Exists(shareKey)) { await _cache.SetTimeoutAsync(shareKey, Newtonsoft.Json.JsonConvert.SerializeObject(model), expireTimeSpan); } return model.PId; } #endregion #region 发送马士基订舱请求 /// /// 发送马士基订舱请求 /// /// 请求订舱详情 /// [HttpPost("/BookingMSKSPOTAPI/SendMSKBooking")] public async Task SendMSKBooking(MSKBookingDto model) { MSKBookingResultDto result = await InnerSendMSKBooking(model, 0); return result; } #endregion #region 发送马士基订舱请求(内部方法) /// /// 发送马士基订舱请求(内部方法) /// /// 请求订舱详情 /// 当前马士基订舱主键 /// 是否默认保存 /// private async Task InnerSendMSKBooking(MSKBookingDto model, long currId, bool isDefaultSave = true) { MSKBookingResultDto result = new MSKBookingResultDto(); /* MSKApiBooking */ try { if (string.IsNullOrWhiteSpace(model.carrierCode)) throw Oops.Oh($"服务船公司不能为空"); if (string.IsNullOrWhiteSpace(model.carrierId)) throw Oops.Oh("船公司代码不能为空"); string sendUrl = string.Empty; if (model.carrierId.Equals("MSK", StringComparison.OrdinalIgnoreCase)) { sendUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == CONST_MSK_API_BOOKING_URL)?.Value; } else { throw Oops.Oh($"当前船公司 {model.carrierId} 未配置相应的请求接口"); } if (string.IsNullOrWhiteSpace(sendUrl)) throw Oops.Oh("未配置发送订舱请求接口地址,请联系管理员"); var webAccountConfig = _webAccountConfig .GetAccountConfig("MSKApi", UserManager.UserId).GetAwaiter().GetResult(); if (webAccountConfig == null) throw Oops.Oh("未配置个人账户,请先配置个人账户 类型-MSKApi"); //这里是校验必填项 ValidateMSKAPIData(model); DateTime nowDate = DateTime.Now; var recordInfo = model.Adapt(); var recordCtnList = model.ctns.Adapt>(); var ctnCodeMappingList = _cache.GetAllMappingCtn().GetAwaiter().GetResult().ToList(); if (ctnCodeMappingList.Count > 0) ctnCodeMappingList = ctnCodeMappingList.Where(x => x.CarrierCode == "MSK" && x.Module == "BOOK_MSK_API").ToList(); MSKAPIBookingDto bookingDto = new MSKAPIBookingDto { userKey = App.Configuration["MSKAPIDjyUserKey"], userSecret = App.Configuration["MSKAPIDjyUserSecret"], operatingEnvironment = App.Configuration["MSKAPIOPEnvironment"], mskAppKey = webAccountConfig.Account, mskAppSecret = webAccountConfig.Password, bookingBody = new MSKAPIBookingBodyDto() }; bookingDto.bookingBody.references = new MSKAPIBookingReferenceDto { priceReference = model.priceReference, productCode = model.productCode, sender = model.sender, bookingOfficeUNLocationCode = "CNTAO" }; bookingDto.bookingBody.mandatoryParties = new MSKAPIBookingMandatoryParties { bookedByCompanyName = model.bookedByCompanyName, bookedByMaerskPartyCode = model.bookedByMaerskPartyCode, bookedByPartyContact = new MSKAPIBookingMandatoryPartyContact { name = model.bookedByCompanyContactName, email = model.bookedByCompanyContactEmail }, priceOwnerCompanyName = model.priceOwnerCompanyName, priceOwnerMaerskPartyCode = model.priceOwnerMaerskPartyCode, priceOwnerPartyContact = new MSKAPIBookingMandatoryPartyContact { name = model.priceOwnerContactName, email = model.priceOwnerContactEmail }, }; //排查怀疑这里是导致BC没有合约号胡的问题,开启这个值看看 //if (model.isBookingPartOwnPrice) //{ // bookingDto.bookingBody.mandatoryParties.isPriceOwnerFromServiceContractAccepted = true; //} bookingDto.bookingBody.transport = new MSKAPIBookingTransport { carrierCode = model.carrierCode, earliestDepartureDate = model.earliestDepartureDate.Value.ToString("yyyy-MM-ddTHH:mm:ss"), exportServiceMode = model.exportServiceMode, importServiceMode = model.importServiceMode, routeDetails = new MSKAPIBookingRouteDetails { placeOfReceipt = new MSKAPIBookingRouteDetailsBase { UNLocationCode = model.userPlaceOfReceiptUnLocCode //: model.placeOfReceiptUnLocCode, //cityName = model.placeOfReceiptCityName, }, placeOfDelivery = new MSKAPIBookingRouteDetailsBase { UNLocationCode = model.userPlaceOfDeliveryUnLocCode //: model.placeOfDeliveryUnLocCode, //cityName = model.placeOfDeliveryCityName, }, selectedRoute = new MSKAPIBookingRoute { bookingSchedules = new List { new MSKAPIBookingSchedules { originDepartureDateTimeLocal = model.originDepartureDateTimeLocal.Value.ToString("yyyy-MM-ddTHH:mm:ss"), destinationArrivalDateTimeLocal = model.destinationArrivalDateTimeLocal.Value.ToString("yyyy-MM-ddTHH:mm:ss"), transportMode = new MSKAPIBookingTransportMode { vessel = new MSKAPIBookingTransportModeVessel { name = model.vesselName, maerskVesselCode = model.carrierVesselCode, }, exportVoyageNumber = model.exportVoyageNumber, }, startLocation = new MSKAPIBookingRouteDetailsBase { //cityName = model.placeOfReceiptCityName, UNLocationCode = model.userPlaceOfReceiptUnLocCode //: model.placeOfReceiptUnLocCode }, endLocation = new MSKAPIBookingRouteDetailsBase { //cityName = model.placeOfDeliveryCityName, UNLocationCode = model.userPlaceOfDeliveryUnLocCode //: model.placeOfDeliveryUnLocCode }, transportModeCode = model.transportMode } } }, } }; //if(!model.isSendNoSchedule) //{ // bookingDto.bookingBody.transport.routeDetails.selectedRoute.routeScheduleId = model.carrierProductId; //} bookingDto.bookingBody.cargo = new MSKAPIBookingCargo { commodityCode = model.commodityCode, commodityCodeType = "MaerskCode", cargoType = model.cargoType, totalCargoWeight = model.totalCargoWeight }; //是否冷冻处理 if (model.isReefer) { bookingDto.bookingBody.cargo.reeferSettings = new MSKAPIBookingCargoReeferSettings(); bookingDto.bookingBody.cargo.reeferSettings.temperatureDetails = model.temperature; bookingDto.bookingBody.cargo.reeferSettings.temperatureMeasureUnit = "C"; bookingDto.bookingBody.cargo.reeferSettings.noOfProbes = model.noOfProbes; //每小时需要的通风量,单位为立方米(0-285) bookingDto.bookingBody.cargo.reeferSettings.ventilation = model.ventilation; bookingDto.bookingBody.cargo.reeferSettings.humidity = model.humidity; } bookingDto.bookingBody.equipmentAndHaulage = new List(); if (model.ctns != null && model.ctns.Count > 0) { model.ctns.ForEach(ctn => { var ctnMapping = ctnCodeMappingList.FirstOrDefault(t => t.Code.Equals(ctn.ctnCode)); if (ctnMapping == null) throw Oops.Oh($"未配置相应的箱型对应{ctn.ctnName},请联系管理员"); MSKAPIBookingEquipmentAndHaulage haulage = new MSKAPIBookingEquipmentAndHaulage { equipmentDetails = new MSKAPIBookingEquipmentAndHaulageItem(), stuffingDetails = new List() }; if (model.isShipperOwned) haulage.equipmentDetails.isShipperOwned = model.isShipperOwned; if (model.isImportReturned) haulage.equipmentDetails.isImportReturned = model.isImportReturned; haulage.equipmentDetails.ISOEquipmentCode = ctnMapping.MapCode; haulage.equipmentDetails.equipmentQuantity = ctn.ctnNum; haulage.stuffingDetails.Add(new MSKAPIBookingStuffingdetails { stuffingValue = (int)ctn.ctnSufferWeight.Value, stuffingMeasurementType = "WEIGHT", stuffingMeasurementUnit = "KGS" }); bookingDto.bookingBody.equipmentAndHaulage.Add(haulage); }); } long id = 0; if (isDefaultSave) { id = InnerSave(model, isSendApi: true).GetAwaiter().GetResult(); } else { id = currId; } result.id = id; MSKAPIBookingResultDto resultInfo = null; _logger.LogInformation($"开始请求MSK API订舱,JSON={JSON.Serialize(bookingDto)}"); var jsonBody = Newtonsoft.Json.JsonConvert.SerializeObject(bookingDto); var rlt = await sendUrl.SetBody(jsonBody) .PostAsStringAsync(); _logger.LogInformation($"开始请求MSK API订舱,返回结果 JSON={JSON.Serialize(rlt)}"); if (!string.IsNullOrWhiteSpace(rlt)) { try { resultInfo = JSON.Deserialize(rlt); } catch (Exception ex) { _logger.LogInformation($"请求MSK API订舱异常,原因:{ex.Message}"); throw Oops.Bah($"请求MSK API订舱异常,原因:{ex.Message}"); } } MSKAPIBookingResultDataDto resultData = null; if (resultInfo.code == 200) { resultData = JSON.Deserialize(JSON.Serialize(resultInfo.data)); } var entity = _bookingDeliveryRecordRep .FirstOrDefault(a => a.Id == id); if (resultInfo != null && resultInfo.code == 200 && resultData != null) { entity.REQUEST_ACKNOWLEDGEMENT_ID = resultData.requestAcknowledgementId; entity.BOOKING_REFERENCE = resultData.bookingReference; entity.STATUS = "SUCC"; entity.STATUS_NAME = "发送成功"; await _bookingDeliveryRecordRep.AsUpdateable(entity).UpdateColumns(x => new { x.REQUEST_ACKNOWLEDGEMENT_ID, x.BOOKING_REFERENCE, x.STATUS, x.STATUS_NAME }).ExecuteCommandAsync(); } else { entity.STATUS = "FAILURE"; entity.STATUS_NAME = "发送失败"; entity.NOTES = resultInfo.msg.Length > 500 ? resultInfo.msg.Substring(0, 500) : resultInfo.msg; await _bookingDeliveryRecordRep.AsUpdateable(entity).UpdateColumns(x => new { x.STATUS, x.STATUS_NAME, x.NOTES }).ExecuteCommandAsync(); throw Oops.Bah(resultInfo.msg); } result.succ = true; } catch (Exception ex) { _logger.LogError($"MSK API订舱异常,req={JSON.Serialize(model)} 原因:{ex.Message}"); //throw Oops.Bah($"MSK API订舱失败,{ex.Message}"); result.succ = false; result.msg = $"MSK API订舱失败,{ex.Message}"; throw Oops.Bah($"MSK API订舱失败,{ex.Message}"); } return result; } #endregion #region 校验马士基API订舱必填项 /// /// 校验马士基API订舱必填项 /// /// private void ValidateMSKAPIData(MSKBookingDto model) { /* 马士基API订舱内容校验 1、合约号必填 2、请求类别必填 3、服务船公司必填 4、订舱公司名称、代码、联系人、邮箱必填 5、持约方公司名称、代码、联系、邮箱必填 6、始发地城市名称、UNLOC、国家代码、服务模式 7、目的地城市名称、UNLOC、国家代码、服务模式 8、船名、航次、ETD、ETA 9、商品名称、代码必填 10、总重必填 11、货物标志必填 12、如果选择了冷冻处理,至少要填个温度 13、箱型、箱量、重量必填 14、总重=箱量*单箱重量 15、预计离港日期必填 */ if (string.IsNullOrWhiteSpace(model.priceReference)) throw Oops.Bah($"订舱合同惟一ID必填"); if (!Regex.IsMatch(model.priceReference, "[a-zA-Z0-9_/,-]{1,50}")) throw Oops.Bah($"订舱合同惟一ID格式错误 [a-zA-Z0-9_/,-]{{1,50}}"); if (string.IsNullOrWhiteSpace(model.sender)) throw Oops.Bah($"请求类别必填"); if (string.IsNullOrWhiteSpace(model.carrierCode)) throw Oops.Bah($"服务船公司必填"); if (string.IsNullOrWhiteSpace(model.bookedByCompanyName)) throw Oops.Bah($"订舱公司名称必填"); if (string.IsNullOrWhiteSpace(model.bookedByMaerskPartyCode)) throw Oops.Bah($"订舱方ID必填"); if (string.IsNullOrWhiteSpace(model.bookedByCompanyContactName)) throw Oops.Bah($"订舱方公司联系人必填"); if (string.IsNullOrWhiteSpace(model.bookedByCompanyContactEmail)) throw Oops.Bah($"订舱方公司邮箱必填"); if (string.IsNullOrWhiteSpace(model.priceOwnerContactName)) throw Oops.Bah($"持约方公司名称必填"); if (string.IsNullOrWhiteSpace(model.priceOwnerMaerskPartyCode)) throw Oops.Bah($"持约方ID必填"); if (string.IsNullOrWhiteSpace(model.priceOwnerContactName)) throw Oops.Bah($"持约方公司联系人必填"); if (string.IsNullOrWhiteSpace(model.priceOwnerContactEmail)) throw Oops.Bah($"持约方公司邮箱必填"); if (string.IsNullOrWhiteSpace(model.placeOfReceiptCityName)) throw Oops.Bah($"始发地城市名称必填"); if (string.IsNullOrWhiteSpace(model.placeOfReceiptUnLocCode)) throw Oops.Bah($"始发地UN CODE必填"); if (string.IsNullOrWhiteSpace(model.placeOfReceiptCountryCode)) throw Oops.Bah($"始发地国家代码必填"); if (string.IsNullOrWhiteSpace(model.exportServiceMode)) throw Oops.Bah($"始发地服务模式必填"); if (string.IsNullOrWhiteSpace(model.placeOfDeliveryCityName)) throw Oops.Bah($"目的地城市名称必填"); if (string.IsNullOrWhiteSpace(model.placeOfDeliveryUnLocCode)) throw Oops.Bah($"目的地UN CODE必填"); if (string.IsNullOrWhiteSpace(model.placeOfDeliveryCountryCode)) throw Oops.Bah($"目的地国家代码必填"); if (string.IsNullOrWhiteSpace(model.importServiceMode)) throw Oops.Bah($"目的地服务模式必填"); if (string.IsNullOrWhiteSpace(model.vesselName)) throw Oops.Bah($"船名必填,请确认正确选择了船期"); if (string.IsNullOrWhiteSpace(model.exportVoyageNumber)) throw Oops.Bah($"航次号必填,请确认正确选择了船期"); if (!model.originDepartureDateTimeLocal.HasValue) throw Oops.Bah($"ETD必填,请确认正确选择了船期"); if (!model.destinationArrivalDateTimeLocal.HasValue) throw Oops.Bah($"ETA必填,请确认正确选择了船期"); if (string.IsNullOrWhiteSpace(model.commodityCode)) throw Oops.Bah($"商品代码必填,请确认正确选择了商品"); if (string.IsNullOrWhiteSpace(model.commodityName)) throw Oops.Bah($"商品名称必填,请确认正确选择了商品"); if (!model.totalCargoWeight.HasValue || model.totalCargoWeight.Value < 1) throw Oops.Bah($"总重必填"); if (string.IsNullOrWhiteSpace(model.cargoType)) throw Oops.Bah($"货物标志必填"); if (!model.earliestDepartureDate.HasValue) throw Oops.Bah($"预计离港日期必填"); if (string.IsNullOrWhiteSpace(model.userPlaceOfReceiptUnLocCode)) throw Oops.Bah($"始发地必填"); if (string.IsNullOrWhiteSpace(model.userPlaceOfDeliveryUnLocCode)) throw Oops.Bah($"目的地必填"); //if (!model.isSendNoSchedule && string.IsNullOrWhiteSpace(model.carrierProductId)) // throw Oops.Bah($"船期信息不能为空,请查询船期信息"); if (model.isReefer) { if (!model.temperature.HasValue) throw Oops.Bah($"选择了冷冻处理,温度必填"); } if (model.ctns.Count == 0) throw Oops.Bah($"箱型箱量信息必填"); if (model.ctns.Any(a => string.IsNullOrWhiteSpace(a.ctnCode))) throw Oops.Bah($"箱型不能为空"); if (model.ctns.Any(a => !a.ctnNum.HasValue || a.ctnNum.Value < 1)) throw Oops.Bah($"箱量不能为空,并且不能小于1"); if (model.ctns.Any(a => !a.ctnSufferWeight.HasValue || a.ctnSufferWeight.Value < 1)) throw Oops.Bah($"箱内重量不能为空,并且不能小于1"); if (model.totalCargoWeight.Value != model.ctns.Sum(b => b.ctnNum.Value * b.ctnSufferWeight.Value)) { throw Oops.Bah($"箱内重量合计不等于总重,请修改"); } } #endregion #region 检索商品名称 /// /// 检索商品名称 /// /// 请求详情 /// 返回检索结果 [HttpPost("/BookingMSKSPOTAPI/SearchCommodities")] public async Task> SearchCommodities(QueryCommoditiesDto model) { List list = new List(); /* MSKApiCommodity */ try { if (string.IsNullOrWhiteSpace(model.commodityName)) throw Oops.Oh("商品名称不能为空"); if (string.IsNullOrWhiteSpace(model.carrierId)) throw Oops.Oh("船公司代码不能为空"); if (model.commodityName.Length < 3) throw Oops.Oh("商品名称至少输入3个以上字符"); string queryUrl = string.Empty; if (model.carrierId.Equals("MSK", StringComparison.OrdinalIgnoreCase)) { queryUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == CONST_MSK_API_COMMODITY_URL)?.Value; } else { throw Oops.Oh($"当前船公司 {model.carrierId} 未配置相应的请求接口"); } if (string.IsNullOrWhiteSpace(queryUrl)) throw Oops.Oh("未配置商品请求接口地址,请联系管理员"); var webAccountConfig = _webAccountConfig .GetAccountConfig("MSKApi", UserManager.UserId).GetAwaiter().GetResult(); if (webAccountConfig == null) throw Oops.Oh("未配置个人账户,请先配置个人账户 类型-MSKApi"); MSKAPISearchCommodityDto queryInfo = new MSKAPISearchCommodityDto { userKey = App.Configuration["MSKAPIDjyUserKey"], userSecret = App.Configuration["MSKAPIDjyUserSecret"], operatingEnvironment = App.Configuration["MSKAPIOPEnvironment"], commodityName = model.commodityName, mskAppKey = webAccountConfig.Account }; MSKAPISearchCommodityResultDto resultInfo = null; var rlt = await queryUrl.SetBody(queryInfo) .PostAsStringAsync(); if (!string.IsNullOrWhiteSpace(rlt)) { try { resultInfo = JSON.Deserialize(rlt); } catch (Exception ex) { _logger.LogInformation($"请求MSK API检索商品异常,原因:{ex.Message}"); throw Oops.Bah($"请求MSK API检索商品异常,原因:{ex.Message}"); } } if (resultInfo != null && resultInfo.code == 200 && resultInfo.data != null && resultInfo.data.Count > 0) { list = resultInfo.data.Adapt>(); } } catch (Exception ex) { _logger.LogError($"检索商品名称异常,req={JSON.Serialize(model)} 原因:{ex.Message}"); throw Oops.Bah($"检索商品名称失败,{ex.Message}"); } return list; } #endregion #region 获取马士基API订舱详情 /// /// 获取马士基API订舱详情 /// /// 马士基API订舱ID /// 返回详情 [HttpGet("/BookingMSKSPOTAPI/GetInfo")] public async Task GetInfo(long id) { MSKBookingDto model = null; var entity = await _bookingDeliveryRecordRep.AsQueryable() .FirstAsync(a => a.Id == id); if (entity == null) throw Oops.Oh($"获取马士基API订舱详情失败,不存在或已作废"); model = entity.Adapt(); var ctnList = _bookingDeliveryRecordCtnRep.AsQueryable() .Where(a => a.RECORD_ID == id && a.IsDeleted == false).ToList(); if (ctnList.Count > 0) { model.ctns = ctnList.Select(a => new MSKBookingCtnInfo { id = a.Id, ctnCode = a.CTN_CODE, carrierCtnCode = a.CARRIER_CTN_CODE, ctnNum = a.CTN_NUM, ctnName = a.CTN_NAME, ctnSufferWeight = a.CTN_SUFFER_WEIGHT, stuffingMeasurementType = a.STUFFING_MEASUREMENT_TYPE, stuffingMeasurementUnit = a.STUFFING_MEASUREMENT_UNIT }).ToList(); } return model; } #endregion #region 保存 /// /// 保存 /// /// 请求订舱详情 /// 返回ID [HttpPost("/BookingMSKSPOTAPI/Save")] public async Task Save([FromBody] MSKBookingDto model) { return await InnerSave(model); } #endregion #region 保存内部方法 /// /// 保存内部方法 /// /// API订舱详情 /// 是否发送API,true-发送需要校验状态;false-不校验状态 /// private async Task InnerSave(MSKBookingDto model, bool isSendApi = false) { DateTime nowDate = DateTime.Now; string ctnStat = string.Empty; _logger.LogInformation($"获取请求马士基API订舱报文,JSON={JSON.Serialize(model)}"); if (model.ctns != null && model.ctns.Count > 0) { ctnStat = string.Join(",", model.ctns.GroupBy(a => a.ctnName) .Select(a => $"{a.Key}*{a.ToList().Sum(b => b.ctnNum.HasValue ? b.ctnNum.Value : 0)}").ToArray()); } if (model.id.HasValue && model.id.Value > 0) { var oldInfo = _bookingDeliveryRecordRep.AsQueryable() .First(a => a.Id == model.id); if (oldInfo == null) throw Oops.Oh("订舱数据不存在或已作废"); if (oldInfo.STATUS == "SUCC") { if (isSendApi) throw Oops.Oh("订舱数据已发送成功,不能重复发送"); throw Oops.Oh("订舱数据已发送成功,不能修改"); } BookingDeliveryRecord entity = model.Adapt(); entity.Id = model.id.Value; entity.UpdatedTime = nowDate; entity.UpdatedUserId = UserManager.UserId; entity.UpdatedUserName = UserManager.Name; entity.CTN_STAT = ctnStat; await _bookingDeliveryRecordRep.AsUpdateable(entity).IgnoreColumns(x => new { x.CreatedTime, x.CreatedUserId, x.CreatedUserName, x.BOOKING_REFERENCE, x.BookingId, x.REQUEST_ACKNOWLEDGEMENT_ID, x.STATUS, x.TenantId, x.IS_RECV_BC, x.LST_RECV_BC_DATE, x.IS_RECV_BK_CANCEL, x.LST_RECV_BK_CANCEL_DATE }).ExecuteCommandAsync(); if (model.ctns != null && model.ctns.Count > 0) { var ctnEntityList = _bookingDeliveryRecordCtnRep.AsQueryable() .Where(a => a.RECORD_ID == entity.Id && a.IsDeleted == false).ToList(); model.ctns.ForEach(ctn => { if (ctn.id.HasValue && ctn.id.Value > 0) { if (ctnEntityList.Any(x => x.Id == ctn.id.Value)) { ctnEntityList.Remove(ctnEntityList.FirstOrDefault(x => x.Id == ctn.id.Value)); } //更新 var ctnEntity = ctn.Adapt(); ctnEntity.Id = ctn.id.Value; ctnEntity.RECORD_ID = entity.Id; ctnEntity.UpdatedTime = nowDate; ctnEntity.UpdatedUserId = UserManager.UserId; ctnEntity.UpdatedUserName = UserManager.Name; _bookingDeliveryRecordCtnRep.AsUpdateable(ctnEntity).IgnoreColumns(x => new { x.CreatedUserId, x.CreatedUserName, x.CreatedTime, x.TenantId, x.STUFFING_MEASUREMENT_TYPE, x.STUFFING_MEASUREMENT_UNIT, x.CTN_SUFFER_WEIGHT }).ExecuteCommand(); } else { var ctnEntity = new BookingDeliveryRecordCtn { CTN_CODE = ctn.ctnCode, CTN_NAME = ctn.ctnName, CTN_NUM = ctn.ctnNum.Value, CTN_SUFFER_WEIGHT = (int)ctn.ctnSufferWeight.Value, }; ctnEntity.RECORD_ID = entity.Id; ctnEntity.CreatedTime = nowDate; ctnEntity.UpdatedTime = nowDate; ctnEntity.CreatedUserId = UserManager.UserId; ctnEntity.CreatedUserName = UserManager.Name; ctnEntity.STUFFING_MEASUREMENT_TYPE = "WEIGHT"; ctnEntity.STUFFING_MEASUREMENT_UNIT = "KGS"; _bookingDeliveryRecordCtnRep.Insert(ctnEntity); } }); if (ctnEntityList.Count > 0) { ctnEntityList.ForEach(async ctn => { ctn.IsDeleted = true; ctn.UpdatedTime = nowDate; ctn.UpdatedUserId = UserManager.UserId; ctn.UpdatedUserName = UserManager.Name; await _bookingDeliveryRecordCtnRep.AsUpdateable(ctn).UpdateColumns(x => new { x.IsDeleted, x.UpdatedTime, x.UpdatedUserId, x.UpdatedUserName }).ExecuteCommandAsync(); }); } } return model.id.Value; } else { BookingDeliveryRecord entity = model.Adapt(); entity.CreatedTime = nowDate; entity.UpdatedTime = nowDate; entity.CreatedUserId = UserManager.UserId; entity.CreatedUserName = UserManager.Name; entity.STATUS = "TEMP"; entity.STATUS_NAME = "暂存"; entity.CTN_STAT = ctnStat; await _bookingDeliveryRecordRep.InsertAsync(entity); if (model.ctns != null && model.ctns.Count > 0) { model.ctns.ForEach(ctn => { var ctnEntity = new BookingDeliveryRecordCtn { CTN_CODE = ctn.ctnCode, CTN_NAME = ctn.ctnName, CTN_NUM = ctn.ctnNum.Value, CTN_SUFFER_WEIGHT = (int)ctn.ctnSufferWeight.Value, }; ctnEntity.RECORD_ID = entity.Id; ctnEntity.CreatedTime = nowDate; ctnEntity.UpdatedTime = nowDate; ctnEntity.CreatedUserId = UserManager.UserId; ctnEntity.CreatedUserName = UserManager.Name; ctnEntity.STUFFING_MEASUREMENT_TYPE = "WEIGHT"; ctnEntity.STUFFING_MEASUREMENT_UNIT = "KGS"; _bookingDeliveryRecordCtnRep.Insert(ctnEntity); }); } return entity.Id; } } #endregion #region 删除 /// /// 删除 /// /// 请求订舱ID /// [HttpGet("/BookingMSKSPOTAPI/Delete")] public async Task Delete(long id) { var info = _bookingDeliveryRecordRep.AsQueryable().First(a => a.Id == id); if (info == null) throw Oops.Bah($"删除失败,业务信息不存在或已作废"); info.IsDeleted = true; info.UpdatedTime = DateTime.Now; info.UpdatedUserId = UserManager.UserId; info.UpdatedUserName = UserManager.Name; await _bookingDeliveryRecordRep.AsUpdateable(info).UpdateColumns(x => new { x.IsDeleted, x.UpdatedTime, x.UpdatedUserId, x.UpdatedUserName }).ExecuteCommandAsync(); } #endregion #region 批量发送 /// /// 批量发送 /// /// 马士基API订舱ID组 /// 返回执行结果消息 [HttpPost("/BookingMSKSPOTAPI/BatchSend")] public async Task BatchSend([FromBody] long[] ids) { var list = await _bookingDeliveryRecordRep.AsQueryable() .Where(a => ids.Contains(a.Id)).ToListAsync(); if (list.Count != ids.Length) throw Oops.Oh("订舱数据获取失败,请确认选中的记录是否存在"); List msgList = new List(); int totalNum = list.Count; int succNum = 0; int failNum = 0; if (list.Count > 1) { list.ForEach(entity => { MSKBookingResultDto result = null; try { var model = GetInfo(entity.Id).GetAwaiter().GetResult(); result = InnerSendMSKBooking(model, entity.Id, false).GetAwaiter().GetResult(); succNum++; } catch (Exception ex) { msgList.Add(ex.Message); failNum++; } }); msgList.Add($"共计{totalNum} 条 成功{succNum}条 失败:{failNum}"); } else { MSKBookingResultDto result = null; try { var entity = list.FirstOrDefault(); var model = GetInfo(entity.Id).GetAwaiter().GetResult(); result = InnerSendMSKBooking(model, entity.Id, false).GetAwaiter().GetResult(); if (result.succ) return string.Empty; } catch (Exception ex) { msgList.Add(ex.Message); } } return string.Join("#", msgList.ToArray()); } #endregion #region 批量复制 /// /// 批量复制 /// /// 马士基API批量复制指定数据 /// 返回执行结果消息 [HttpPost("/BookingMSKSPOTAPI/BatchCopy")] public async Task BatchCopy(MSKAPIBookingCopyDto model) { if (model.copyNum < 1) throw Oops.Oh($"复制数量不能小于1"); if (model.copyNum > 1000) throw Oops.Oh($"复制数量不能大于1000"); if (string.IsNullOrWhiteSpace(model.opType)) throw Oops.Oh($"请求的操作类型不能为空"); var entity = await _bookingDeliveryRecordRep.AsQueryable() .FirstAsync(a => a.Id == model.originalId); if (entity == null) throw Oops.Oh($"获取马士基API订舱详情失败,不存在或已作废"); var ctnList = _bookingDeliveryRecordCtnRep.AsQueryable() .Where(a => a.RECORD_ID == model.originalId && a.IsDeleted == false).ToList(); var numArg = "".PadLeft(model.copyNum, '0').Select((a, idx) => idx + 1).ToList(); DateTime nowDate = DateTime.Now; _logger.LogInformation("获取到批量复制功能"); //如果是指定的编辑信息需要先把编辑数据同步到实体类,再进行复制 if (model.opType == "copy_edit") { string ctnStat = string.Empty; if (model.bookingDto.ctns != null && model.bookingDto.ctns.Count > 0) { ctnStat = string.Join(",", model.bookingDto.ctns.GroupBy(a => a.ctnName) .Select(a => $"{a.Key}*{a.ToList().Sum(b => b.ctnNum.HasValue ? b.ctnNum.Value : 0)}").ToArray()); ctnList = new List(); model.bookingDto.ctns.ForEach(ctn => { var ctnEntity = new BookingDeliveryRecordCtn { CTN_CODE = ctn.ctnCode, CTN_NAME = ctn.ctnName, CTN_NUM = ctn.ctnNum.Value, CTN_SUFFER_WEIGHT = (int)ctn.ctnSufferWeight.Value, }; ctnEntity.CreatedTime = nowDate; ctnEntity.UpdatedTime = nowDate; ctnEntity.CreatedUserId = UserManager.UserId; ctnEntity.CreatedUserName = UserManager.Name; ctnEntity.STUFFING_MEASUREMENT_TYPE = "WEIGHT"; ctnEntity.STUFFING_MEASUREMENT_UNIT = "KGS"; ctnList.Add(ctnEntity); }); } entity = model.bookingDto.Adapt(); entity.CreatedTime = nowDate; entity.UpdatedTime = nowDate; entity.CreatedUserId = UserManager.UserId; entity.CreatedUserName = UserManager.Name; entity.STATUS = "TEMP"; entity.STATUS_NAME = "暂存"; entity.CTN_STAT = ctnStat; } int totalNum = 0; foreach (var a in numArg) { var copyEntity = entity.Adapt(); copyEntity.Id = 0; copyEntity.CreatedTime = nowDate; copyEntity.CreatedUserId = UserManager.UserId; copyEntity.CreatedUserName = UserManager.Name; copyEntity.UpdatedTime = nowDate; copyEntity.STATUS = "TEMP"; copyEntity.STATUS_NAME = "暂存"; copyEntity.IS_RECV_BC = false; copyEntity.LST_RECV_BC_DATE = null; copyEntity.IS_RECV_BK_CANCEL = false; copyEntity.LST_RECV_BK_CANCEL_DATE = null; copyEntity.REQUEST_ACKNOWLEDGEMENT_ID = null; copyEntity.BOOKING_REFERENCE = null; copyEntity.NOTES = $"COPY NO.{a} BY={model.originalId}"; await _bookingDeliveryRecordRep.InsertAsync(copyEntity); totalNum++; if (ctnList.Count > 0) { ctnList.ForEach(async ctn => { var ctnEntity = ctn.Adapt(); ctnEntity.Id = 0; ctnEntity.RECORD_ID = copyEntity.Id; ctnEntity.CreatedTime = nowDate; ctnEntity.UpdatedTime = nowDate; ctnEntity.CreatedUserId = UserManager.UserId; ctnEntity.CreatedUserName = UserManager.Name; await _bookingDeliveryRecordCtnRep.InsertAsync(ctnEntity); }); } } return JSON.Serialize(new { Total = totalNum }); } #endregion #region 同步BC状态(BC,Cancellation) /// /// 同步BC状态(BC,Cancellation) /// /// 提单号 /// 租户ID /// 操作类型 BC-同步BC状态 Cancellation-同步取消状态 /// 返回回执 [HttpGet("/BookingMSKSPOTAPI/SyncBCInfo")] public async Task SyncBCInfo(string mblno, long tenantId, string opTypeName = "BookingConfirmation") { MSKBookingResultDto result = new MSKBookingResultDto(); try { var model = await _bookingDeliveryRecordRep.AsQueryable().Filter(null, true) .FirstAsync(a => a.BOOKING_REFERENCE == mblno && a.IsDeleted == false && a.TenantId == tenantId); if (model == null) throw Oops.Oh("订舱数据不存在或已作废"); if (opTypeName == "BookingConfirmation") { model.IS_RECV_BC = true; model.LST_RECV_BC_DATE = DateTime.Now; model.UpdatedTime = model.LST_RECV_BC_DATE; model.UpdatedUserId = UserManager.UserId; model.UpdatedUserName = UserManager.Name; await _bookingDeliveryRecordRep.AsUpdateable(model).UpdateColumns(x => new { x.IS_RECV_BC, x.LST_RECV_BC_DATE, x.UpdatedTime, x.UpdatedUserId, x.UpdatedUserName }).ExecuteCommandAsync(); } else if (opTypeName == "Cancellation") { model.IS_RECV_BK_CANCEL = true; model.LST_RECV_BK_CANCEL_DATE = DateTime.Now; model.UpdatedTime = model.LST_RECV_BC_DATE; model.UpdatedUserId = UserManager.UserId; model.UpdatedUserName = UserManager.Name; await _bookingDeliveryRecordRep.AsUpdateable(model).UpdateColumns(x => new { x.IS_RECV_BK_CANCEL, x.LST_RECV_BK_CANCEL_DATE, x.UpdatedTime, x.UpdatedUserId, x.UpdatedUserName }).ExecuteCommandAsync(); } result.succ = true; } catch (Exception ex) { result.succ = false; result.msg = $"同步BC状态异常,原因:{ex.Message}"; } return result; } #endregion #region 批量复制前调取校验预警 /// /// 批量复制前调取校验预警 /// /// 马士基API批量复制指定数据 /// 返回提示信息 [HttpPost("/BookingMSKSPOTAPI/CheckWarningBatchCopy")] public async Task CheckWarningBatchCopy(MSKAPIBookingCopyDto model) { /* 主要校验内容 1、如果待复制的单子里还有“托运人自己的集装箱”或“进口退货集装箱或者其他三角集运”需要做提醒是否继续复制。 2、如果是opType=copy_edit-复制使用提交的编辑信息,需要判断原始货物标志变化是否继续。 3、如果是冷冻处理的提示是否继续。 */ var entity = await _bookingDeliveryRecordRep.AsQueryable() .FirstAsync(a => a.Id == model.originalId); if (entity != null) { List msgList = new List(); if (entity.IS_SHIPPER_OWNED || (model.bookingDto != null && model.bookingDto.isShipperOwned)) { msgList.Add("含有指定托运人自己的集装箱"); } if (entity.IS_IMPORT_RETURNED || (model.bookingDto != null && model.bookingDto.isImportReturned)) { msgList.Add("含有指定进口退货集装箱或者其他三角集运"); } if (entity.IS_REEFER || (model.bookingDto != null && model.bookingDto.isReefer)) { msgList.Add("含有是冷冻处理"); } if (model.bookingDto != null && model.bookingDto.cargoType != entity.CARGO_TYPE) { msgList.Add("货物标志与被复制货物标志不一致"); } if (msgList.Count > 0) throw Oops.Oh(string.Join(",", msgList.ToArray())); } else { return string.Empty; } return "校验成功"; } #endregion #region 批量发送API前调取校验预警 /// /// 批量发送API前调取校验预警 /// /// 马士基API订舱ID组 /// 返回提示信息 [HttpPost("/BookingMSKSPOTAPI/CheckWarningBatchSend")] public async Task CheckWarningBatchSend([FromBody] long[] ids) { var list = await _bookingDeliveryRecordRep.AsQueryable().Where(a => ids.Contains(a.Id)).ToListAsync(); if (list.Count > 0) { List msgList = new List(); if (list.Any(a => a.IS_SHIPPER_OWNED)) { msgList.Add("含有指定托运人自己的集装箱"); } if (list.Any(a => a.IS_IMPORT_RETURNED)) { msgList.Add("含有指定进口退货集装箱或者其他三角集运"); } if (list.Any(a => a.IS_REEFER)) { msgList.Add("含有是冷冻处理"); } if (msgList.Count > 0) throw Oops.Oh(string.Join("#", msgList.ToArray())); } return "校验成功"; } #endregion } }