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.
BookingHeChuan/Myshipping.Application/Service/ExpressDelivery/ExpressDeliveryService.cs

1068 lines
51 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.DataEncryption;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using Furion.RemoteRequest.Extensions;
using Furion.UnifyResult;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Myshipping.Application.Entity;
using Myshipping.Application.Service.ExpressDelivery.Dto;
using Myshipping.Core;
using Myshipping.Core.Attributes;
using Myshipping.Core.Service;
using Newtonsoft.Json.Linq;
using SqlSugar;
using StackExchange.Profiling.Internal;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Myshipping.Application
{
/// <summary>
/// 快递订单服务
/// </summary>
[ApiDescriptionSettings("Application", Name = "ExpressDelivery", Description = "快递订单服务", Order = 20)]
[Route("/ExpressDelivery")]
public class ExpressDeliveryService : IExpressDeliveryService, IDynamicApiController, ITransient
{
private const string STATUS_ASSOCIATION = "关联快递订单";
private const string STATUS_DISASSOCIATION = "取消关联快递订单";
private const string STATUS_DELETE = "关联快递订单已删除";
private const string STATUS_SEND = "快递已下单";
private const string STATUS_CANCEL = "快递已消单";
/// <summary>
/// 保存面单文件的文件夹名称
/// </summary>
private const string WAY_BILL_DIRECTORY = "WaybillFile";
/// <summary>
/// Redis中用于保存访问顺丰API Token的键名
/// </summary>
private const string SF_API_TOKEN_KEY = $"SfApiToken";
private readonly ILogger<ExpressDeliveryService> _logger;
private readonly ISysCacheService _cache;
private readonly SqlSugarRepository<BookingStatusLog> _bookingStatuslogRep;
private readonly SqlSugarRepository<BookingOrder> _bookingOrderRep;
private readonly SqlSugarRepository<ExpressDeliveryOrder> _orderRep;
private readonly SqlSugarRepository<ExpressDeliveryBusiness> _businessRep;
private readonly SqlSugarRepository<ExpressDeliveryStatus> _statusRep;
private readonly SqlSugarRepository<ExpressDeliveryAddress> _addressRep;
public ExpressDeliveryService(ILogger<ExpressDeliveryService> logger,
ISysCacheService cache,
SqlSugarRepository<BookingStatusLog> bookingStatuslogRep,
SqlSugarRepository<BookingOrder> bookingOrderRep,
SqlSugarRepository<ExpressDeliveryOrder> orderRep,
SqlSugarRepository<ExpressDeliveryBusiness> businessRep,
SqlSugarRepository<ExpressDeliveryStatus> statusRep,
SqlSugarRepository<ExpressDeliveryAddress> templeteRep)
{
_logger = logger;
_cache = cache;
_orderRep = orderRep;
_businessRep = businessRep;
_statusRep = statusRep;
_bookingStatuslogRep = bookingStatuslogRep;
_bookingOrderRep = bookingOrderRep;
_addressRep = templeteRep;
}
/// <summary>
/// 分页查询快递主表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpGet("Page")]
public async Task<dynamic> Page([FromQuery] ExpressDeliveryInput input)
{
var entities = await _orderRep.AsQueryable().Filter(null, true)
.Where(x => x.TenantId == UserManager.TENANT_ID && x.IsDeleted == false)
.WhereIF(!string.IsNullOrWhiteSpace(input.KDNO), u => u.KDNO.Contains(input.KDNO))
.WhereIF(!string.IsNullOrWhiteSpace(input.SJCompany), u => u.SJCompany.Contains(input.SJCompany))
.WhereIF(!string.IsNullOrWhiteSpace(input.SJPeople), u => u.SJPeople.Contains(input.SJPeople))
.WhereIF(!string.IsNullOrWhiteSpace(input.SJTel), u => u.SJTel.Contains(input.SJTel))
.WhereIF(!string.IsNullOrWhiteSpace(input.FJCompany), u => u.FJCompany.Contains(input.FJCompany))
.WhereIF(!string.IsNullOrWhiteSpace(input.FJPeople), u => u.FJPeople.Contains(input.FJPeople))
.WhereIF(!string.IsNullOrWhiteSpace(input.FJTel), u => u.FJTel.Contains(input.FJTel))
.WhereIF(!string.IsNullOrWhiteSpace(input.VESSEL), u => u.VESSEL.Contains(input.VESSEL))
.WhereIF(!string.IsNullOrWhiteSpace(input.VOYNO), u => u.VOYNO.Contains(input.VOYNO))
.WhereIF(input.BDate != null, u => u.Date >= input.BDate)
.WhereIF(input.EDate != null, u => u.Date <= input.EDate)
.OrderBy(PageInputOrder.OrderBuilder(input.SortField, input.DescSort))
.ToPagedListAsync(input.PageNo, input.PageSize);
var list = entities.Adapt<SqlSugarPagedList<ExpressDeliveryDto>>();
foreach (var item in list.Items)
{
item.Business = await _businessRep.AsQueryable().Filter(null, true).Where(x => x.PId == item.Id).ToListAsync();
}
return list;
}
/// <summary>
/// 获取详情
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
[HttpPost("Get")]
public async Task<dynamic> Get(long Id)
{
ExpressDeliveryDto ordOut = new ExpressDeliveryDto();
var main = await _orderRep.FirstOrDefaultAsync(u => u.Id == Id);
if (main != null)
{
ordOut = main.Adapt<ExpressDeliveryDto>();
var business = await _businessRep.AsQueryable().Where(x => x.PId == Id).ToListAsync();
ordOut.Business = business;
}
return ordOut;
}
/// <summary>
/// 保存并返回数据
/// </summary>
/// <returns></returns>
[HttpPost("Save")]
[JoinValidateMessge]
public async Task<dynamic> Save(ExpressDeliveryDto input)
{
var entity = input.Adapt<ExpressDeliveryOrder>();
string logGuid = Guid.NewGuid().ToString();
_logger.LogInformation($"快递订单保存操作开始,{logGuid}");
if (input.Id == 0)
{
entity.CurrentStateCode = "DJY02";
entity.CurrentStateDesc = "已保存";
_logger.LogInformation($"快递订单新增操作开始,{logGuid}");
await _orderRep.InsertAsync(entity);
if (input.Business != null && input.Business.Count > 0)
{
#region 保存关联的业务信息
_logger.LogInformation($"快递订单新增关联的业务信息,{logGuid}");
input.Business.ForEach(b => b.PId = entity.Id);
await _businessRep.InsertAsync(input.Business);
#endregion
#region 向与快递关联的订舱列表中添加动态
List<string> mblNoList = input.Business.Select(b => b.MBLNO).ToList();
await InsertNewBookingStatusWithMblnoList(mblNoList, STATUS_ASSOCIATION);
#endregion
}
// 添加快递动态
_logger.LogInformation($"快递订单新增快递动态,{logGuid}");
var status = new ExpressDeliveryStatus()
{
OrderId = entity.Id,
MailNo = "",
StatusCode = entity.CurrentStateCode,
StatusDesc = entity.CurrentStateDesc
};
await _statusRep.InsertAsync(status);
}
else
{
var oldOrder = _orderRep.FirstOrDefault(x => x.Id == input.Id);
if (oldOrder.CurrentStateCode != "DJY02")
{
throw Oops.Bah("修改失败,原因:下单过的快递单无法修改信息");
}
_logger.LogInformation($"快递订单修改操作开始,{logGuid}");
await _orderRep.AsUpdateable(entity).IgnoreColumns(it => new
{
it.CurrentStateCode,
it.CurrentStateDesc,
it.TenantId,
it.CreatedTime,
it.CreatedUserId,
it.CreatedUserName,
}).ExecuteCommandAsync();
#region 向与快递关联的订舱列表中添加动态,并新增或更新业务信息
List<ExpressDeliveryBusiness> businessList = await _businessRep.AsQueryable().Where(x => x.PId == input.Id).ToListAsync();
if (businessList.Count == 0)
{
if (input.Business?.Count != 0)
{
// 直接得到:要关联快递的提单号列表
List<string> mblNoList = input.Business.Select(b => b.MBLNO).ToList();
await InsertNewBookingStatusWithMblnoList(mblNoList, STATUS_ASSOCIATION);
// 直接得到:要新增的业务信息
input.Business.ForEach(b => b.PId = entity.Id);
await _businessRep.InsertAsync(input.Business);
}
}
else
{
if (input.Business?.Count == 0)
{
// 直接得到:要取消关联快递的提单号列表
List<string> mblNoList = businessList.Select(b => b.MBLNO).ToList();
await InsertNewBookingStatusWithMblnoList(mblNoList, STATUS_DISASSOCIATION);
// 直接得到:要删除的业务信息
await _businessRep.DeleteAsync(x => x.PId == entity.Id);
}
else
{
// 分析对比,判断哪些关联,哪些取消关联
List<string> oldMblnoList = businessList.Select(b => b.MBLNO).ToList();
List<string> newMblnoList = input.Business.Select(b => b.MBLNO).ToList();
// 新增关联的提单号列表:
List<string> addMblnoList = newMblnoList.Except(oldMblnoList).ToList();
await InsertNewBookingStatusWithMblnoList(addMblnoList, STATUS_ASSOCIATION);
// 取消关联的提单号列表
List<string> cancelMblnoList = oldMblnoList.Except(newMblnoList).ToList();
await InsertNewBookingStatusWithMblnoList(cancelMblnoList, STATUS_DISASSOCIATION);
// 先删除,再新增业务信息
await _businessRep.DeleteAsync(x => x.PId == entity.Id);
input.Business.ForEach(b => b.PId = entity.Id);
await _businessRep.InsertAsync(input.Business);
}
}
#endregion
}
return Get(entity.Id);
}
/// <summary>
/// 删除单据
/// </summary>
/// <param name="Ids"></param>
/// <returns></returns>
[SqlSugarUnitOfWork]
[HttpPost("Delete")]
public async Task Delete(string Ids)
{
var arr = Ids.Split(",");
if (arr.Length > 0)
{
foreach (var ar in arr)
{
long Id = Convert.ToInt64(ar);
ExpressDeliveryOrder order = await _orderRep.AsQueryable(e => e.Id == Id).FirstAsync() ?? throw Oops.Bah("未找到快递订单");
// 添加快递动态
var status = new ExpressDeliveryStatus()
{
OrderId = Id,
MailNo = order.KDNO,
StatusCode = "DJY05",
StatusDesc = "快递单已删除"
};
await _statusRep.InsertAsync(status);
// 更新快递订单表
await _orderRep.UpdateAsync(x => x.Id == Id, x => new ExpressDeliveryOrder
{
IsDeleted = true,
CurrentStateCode = status.StatusCode,
CurrentStateDesc = status.StatusDesc
});
// 快递关联的订舱添加动态
List<string> mblnoList = await _businessRep.AsQueryable(x => x.PId == Id).Select(b => b.MBLNO).ToListAsync();
await InsertNewBookingStatusWithMblnoList(mblnoList, STATUS_DELETE);
// 删除快递关联的业务表数据
await _businessRep.DeleteAsync(x => x.PId == Id);
}
}
else
{
throw Oops.Bah("请上传正确参数");
}
}
/// <summary>
/// 发送快递
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
[HttpPost("SendBooking")]
public async Task<dynamic> SendBooking(long Id)
{
var rt = String.Empty;
var order = _orderRep.FirstOrDefault(x => x.Id == Id);
if (order == null)
{
throw Oops.Bah("请选择正确数据!");
}
if (order.CurrentStateCode == "DJY01")
{
throw Oops.Bah("下单失败,原因:尚未保存");
}
if (order.CurrentStateCode != "DJY02")
{
throw Oops.Bah("下单失败,原因:重复下单");
}
SFSendBooking sFSend = new SFSendBooking()
{
orderId = Id.ToString(),
language = "zh_CN",
cargoDetails = new List<CargoDetailsItem>()
{
new CargoDetailsItem()
{
count = (int)order.KDNum,
name = order.GOODSNAME
}
}
};
if (order.SettleAccountsTypeCode == "2")
{
if (string.IsNullOrWhiteSpace(order.MonthlyCard))
{
throw Oops.Bah("当结费类型为月结时,月结卡号不能为空");
}
sFSend.monthlyCard = order.MonthlyCard;
}
List<ContactInfoListItem> contactList = new()
{
new ContactInfoListItem
{
address = $"{order.FJProvince}{order.FJCity}{order.FJAddress}",
contact = order.FJPeople,
contactType = 1,
country = "CN",
tel = order.FJTel,
},
new ContactInfoListItem
{
address = $"{order.SJProvince}{order.SJCity}{order.SJAddress}",
contact = order.SJPeople,
contactType = 2,
country = "CN",
tel = order.SJTel,
}
};
sFSend.contactInfoList = contactList;
(string url, Dictionary<string, object> dict) apiParam = await BuildSFApiParams("EXP_RECE_CREATE_ORDER", sFSend);
_logger.LogInformation("调用顺丰下单接口,参数:" + apiParam.dict.ToJson());
var strRtn = await apiParam.url.SetBody(apiParam.dict, "application/x-www-form-urlencoded").PostAsStringAsync();
//var strRtn = "{\"apiErrorMsg\":\"\",\"apiResponseID\":\"00018BF4CE37D63FDF38696876F68A3F\",\"apiResultCode\":\"A1000\",\"apiResultData\":\"{\\\"success\\\":true,\\\"errorCode\\\":\\\"S0000\\\",\\\"errorMsg\\\":null,\\\"msgData\\\":{\\\"orderId\\\":\\\"485172445380677\\\",\\\"originCode\\\":\\\"759\\\",\\\"destCode\\\":\\\"886\\\",\\\"filterResult\\\":2,\\\"remark\\\":\\\"\\\",\\\"url\\\":null,\\\"paymentLink\\\":null,\\\"isUpstairs\\\":null,\\\"isSpecialWarehouseService\\\":null,\\\"mappingMark\\\":null,\\\"agentMailno\\\":null,\\\"returnExtraInfoList\\\":null,\\\"waybillNoInfoList\\\":[{\\\"waybillType\\\":1,\\\"waybillNo\\\":\\\"SF7444473840720\\\",\\\"boxNo\\\":null,\\\"length\\\":null,\\\"width\\\":null,\\\"height\\\":null,\\\"weight\\\":null,\\\"volume\\\":null}],\\\"routeLabelInfo\\\":[{\\\"code\\\":\\\"1000\\\",\\\"routeLabelData\\\":{\\\"waybillNo\\\":\\\"SF7444473840720\\\",\\\"sourceTransferCode\\\":\\\"759VA\\\",\\\"sourceCityCode\\\":\\\"759\\\",\\\"sourceDeptCode\\\":\\\"759\\\",\\\"sourceTeamCode\\\":\\\"\\\",\\\"destCityCode\\\":\\\"886\\\",\\\"destDeptCode\\\":\\\"886\\\",\\\"destDeptCodeMapping\\\":\\\"\\\",\\\"destTeamCode\\\":\\\"\\\",\\\"destTeamCodeMapping\\\":\\\"\\\",\\\"destTransferCode\\\":\\\"886WA\\\",\\\"destRouteLabel\\\":\\\"886WA-886\\\",\\\"proName\\\":\\\"\\\",\\\"cargoTypeCode\\\":\\\"C201\\\",\\\"limitTypeCode\\\":\\\"T4\\\",\\\"expressTypeCode\\\":\\\"B1\\\",\\\"codingMapping\\\":\\\"WU\\\",\\\"codingMappingOut\\\":\\\"\\\",\\\"xbFlag\\\":\\\"0\\\",\\\"printFlag\\\":\\\"000000000\\\",\\\"twoDimensionCode\\\":\\\"MMM={'k1':'886WA','k2':'886','k3':'','k4':'T4','k5':'SF7444473840720','k6':'','k7':'ce37f885'}\\\",\\\"proCode\\\":\\\"特快\\\",\\\"printIcon\\\":\\\"00000000\\\",\\\"abFlag\\\":\\\"\\\",\\\"destPortCode\\\":null,\\\"destCountry\\\":null,\\\"destPostCode\\\":null,\\\"goodsValueTotal\\\":\\\"\\\",\\\"currencySymbol\\\":null,\\\"cusBatch\\\":\\\"\\\",\\\"goodsNumber\\\":null,\\\"errMsg\\\":\\\"\\\",\\\"checkCode\\\":\\\"ce37f885\\\",\\\"proIcon\\\":\\\"\\\",\\\"fileIcon\\\":\\\"\\\",\\\"fbaIcon\\\":\\\"\\\",\\\"icsmIcon\\\":\\\"\\\",\\\"destGisDeptCode\\\":\\\"886\\\",\\\"newIcon\\\":null,\\\"sendAreaCode\\\":null,\\\"destinationStationCode\\\":null,\\\"sxLabelDestCode\\\":null,\\\"sxDestTransferCode\\\":null,\\\"sxCompany\\\":null,\\\"newAbFlag\\\":null,\\\"destAddrKeyWord\\\":\\\"\\\",\\\"rongType\\\":null,\\\"waybillIconList\\\":null},\\\"message\\\":\\\"SF7444473840720:\\\"}],\\\"contactInfoList\\\":null,\\\"sendStartTm\\\":null,\\\"customerRights\\\":null,\\\"expressTypeId\\\":null}}\"}";
_logger.LogInformation("调用顺丰下单接口,返回:" + strRtn);
var jobj = strRtn.ToJObject();
if (jobj.GetStringValue("apiResultCode") == "A1000")
{
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
bool isSuccess = resultDataObj.GetBooleanValue("success");
if (isSuccess)
{
rt = "下单成功!";
var waybillNoInfoObj = resultDataObj.GetJObjectValue("msgData").GetJArrayValue("waybillNoInfoList")[0].Value<JObject>();
var waybillNo = waybillNoInfoObj.GetStringValue("waybillNo");
string statusDesc = $"{STATUS_SEND}(单号:{waybillNo}";
// 不需要等待的任务,并且不会因为异常导致当前请求终止
_ = Task.Run(async () =>
{
#region 添加快递动态
try
{
var status = new ExpressDeliveryStatus()
{
OrderId = Id,
MailNo = waybillNo,
StatusCode = "DJY03",
StatusDesc = statusDesc
};
await _statusRep.InsertAsync(status);
}
catch (Exception ex)
{
_logger.LogError(ex, "调用顺丰接口下单后,添加快递动态的过程中发生异常");
}
#endregion
#region 为与快递关联的订舱添加动态
try
{
List<string> mblnoList = await _businessRep.AsQueryable(b => b.PId == Id).Select(b => b.MBLNO).ToListAsync();
_ = InsertNewBookingStatusWithMblnoList(mblnoList, statusDesc);
}
catch (Exception ex)
{
_logger.LogError(ex, "调用顺丰接口下单后,为与快递关联的订舱添加动态的过程中发生异常");
}
#endregion
#region 维护地址簿
try
{
if (!await _addressRep.IsExistsAsync(a => a.People == order.SJPeople && a.Tel == order.SJTel))
{
await _addressRep.InsertAsync(new ExpressDeliveryAddress()
{
Address = order.SJAddress,
City = order.SJCity,
Company = order.SJCompany,
People = order.SJPeople,
Province = order.SJProvince,
ProvinceId = order.SJProvinceId,
Tel = order.SJTel,
Type = 1
});
}
if (!await _addressRep.IsExistsAsync(a => a.People == order.FJPeople && a.Tel == order.FJTel))
{
await _addressRep.InsertAsync(new ExpressDeliveryAddress()
{
Address = order.FJAddress,
City = order.FJCity,
Company = order.FJCompany,
People = order.FJPeople,
Province = order.FJProvince,
ProvinceId = order.FJProvinceId,
Tel = order.FJTel,
Type = 2
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, "调用顺丰接口下单后,维护地址簿的过程中发生异常");
}
#endregion
});
// 需要等待的任务
#region 请求顺丰获取面单下载地址存库并下载PDF保存到本地
var filePath = $"{waybillNo}_{order.Id}.pdf";
try
{
var waybillDownloadInfo = await GetSfWaybillDownloadUrl(waybillNo);
var header = new Dictionary<string, object>() { { "X-Auth-token", waybillDownloadInfo.Token } };
using var stream = await waybillDownloadInfo.Url.SetHeaders(header).GetAsStreamAsync();
var dic = Path.Combine(App.WebHostEnvironment.WebRootPath, WAY_BILL_DIRECTORY);
if (!Directory.Exists(dic))
{
Directory.CreateDirectory(dic);
}
var allPath = Path.Combine(dic, filePath);
_logger.LogInformation($"调用顺丰下单接口,面单文件保存地址:{allPath}");
using FileStream fs = new FileStream(allPath, FileMode.Create);
await stream.CopyToAsync(fs);
}
catch (Exception ex)
{
_logger.LogError(ex, "调用顺丰接口下单后,获取面单下载地址的过程中发生异常");
}
#endregion
#region 向快递订单表反写快递单号与状态
ExpressDeliveryOrder updateOrder = new ExpressDeliveryOrder()
{
Id = Id,
KDNO = waybillNo,
CurrentStateCode = "DJY03",
CurrentStateDesc = statusDesc,
WaybillFilePath = filePath
};
await _orderRep.AsUpdateable(updateOrder)
.UpdateColumns(o => new { o.KDNO, o.CurrentStateCode, o.CurrentStateDesc, o.WaybillFilePath })
.ExecuteCommandAsync();
#endregion
UnifyContext.Fill(new
{
KDNO = waybillNo,
currentStateCode = "DJY03",
currentStateDesc = statusDesc
});
}
else
{
var errorMsg = resultDataObj.GetStringValue("errorMsg");
_logger.LogInformation("调用顺丰下单接口返回异常errorMsg" + errorMsg);
throw Oops.Bah(errorMsg);
}
}
else
{
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
_logger.LogInformation("调用顺丰下单接口返回异常apiErrorMsg" + apiErrorMsg);
throw Oops.Bah(apiErrorMsg);
}
return rt;
}
/// <summary>
/// 取消快递下单
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
[HttpPost("CancelBooking")]
public async Task<string> CancelBooking(long Id)
{
var oldOrder = _orderRep.FirstOrDefault(x => x.Id == Id) ?? throw Oops.Bah("请选择正确数据!");
if (oldOrder.CurrentStateCode is "DJY01" or "DJY02")
{
throw Oops.Bah("消单失败,原因:尚未下单,无需消单");
}
if (oldOrder.CurrentStateCode is "DJY04")
{
throw Oops.Bah("消单失败,原因:已消单,无需再次消单");
}
var param = new
{
dealType = 2,
orderId = Id
};
(string url, Dictionary<string, object> dict) apiParam = await BuildSFApiParams("EXP_RECE_UPDATE_ORDER", param);
_logger.LogInformation("调用顺丰订单取消接口,参数:" + apiParam.dict.ToJson());
var strRtn = await apiParam.url.SetBody(apiParam.dict, "application/x-www-form-urlencoded").PostAsStringAsync();
//var strRtn = "{\"apiErrorMsg\":\"\",\"apiResponseID\":\"00018BF5250ADD3FEE59BC8A2F42CE3F\",\"apiResultCode\":\"A1000\",\"apiResultData\":\"{\\\"success\\\":true,\\\"errorCode\\\":\\\"S0000\\\",\\\"errorMsg\\\":null,\\\"msgData\\\":{\\\"orderId\\\":\\\"485172445380677\\\",\\\"waybillNoInfoList\\\":[{\\\"waybillType\\\":1,\\\"waybillNo\\\":\\\"SF7444473840720\\\"}],\\\"resStatus\\\":2,\\\"extraInfoList\\\":null}}\"}";
_logger.LogInformation("调用顺丰订单取消接口,返回:" + strRtn);
var jobj = strRtn.ToJObject();
if (jobj.GetStringValue("apiResultCode") == "A1000")
{
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
if (resultDataObj.GetBooleanValue("success"))
{
ExpressDeliveryOrder order = await _orderRep.AsQueryable(e => e.Id == Id).FirstAsync();
#region 添加快递动态
var status = new ExpressDeliveryStatus()
{
OrderId = Id,
MailNo = order.KDNO,
StatusCode = "DJY04",
StatusDesc = STATUS_CANCEL
};
await _statusRep.InsertAsync(status);
#endregion
#region 为与快递关联的订舱添加动态
List<string> mblnoList = await _businessRep.AsQueryable(b => b.PId == Id).Select(b => b.MBLNO).ToListAsync();
await InsertNewBookingStatusWithMblnoList(mblnoList, STATUS_CANCEL);
#endregion
#region 更新快递订单状态
ExpressDeliveryOrder updateOrder = new ExpressDeliveryOrder()
{
Id = Id,
CurrentStateCode = status.StatusCode,
CurrentStateDesc = status.StatusDesc,
};
await _orderRep.AsUpdateable(updateOrder)
.UpdateColumns(o => new { o.CurrentStateCode, o.CurrentStateDesc })
.ExecuteCommandAsync();
#endregion
return "取消成功";
}
else
{
var errorMsg = resultDataObj.GetStringValue("errorMsg");
_logger.LogInformation("顺丰取消快递下单接口返回异常errorMsg" + errorMsg);
throw Oops.Bah(errorMsg);
}
}
else
{
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
_logger.LogInformation("顺丰取消快递下单接口,返回异常:" + apiErrorMsg);
throw Oops.Bah(apiErrorMsg);
}
}
/// <summary>
/// 订单结果查询接口
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
[HttpPost("QueryKDSchedule")]
public async Task<dynamic> QueryKDSchedule(long Id)
{
var rt = String.Empty;
var json = new
{
orderId = Id.ToString()
};
(string url, Dictionary<string, object> dict) apiParam = await BuildSFApiParams("EXP_RECE_SEARCH_ORDER_RESP", json);
_logger.LogInformation("调用顺丰订单结果查询接口,参数:" + apiParam.dict.ToJson());
var strRtn = await apiParam.url.SetBody(apiParam.dict, "application/x-www-form-urlencoded").PostAsStringAsync();
_logger.LogInformation("调用顺丰订单结果查询接口,返回:" + strRtn);
var jobj = strRtn.ToJObject();
if (jobj.GetStringValue("apiResultCode") == "A1000")
{
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
if (resultDataObj.GetBooleanValue("success"))
{
rt = jobj.GetStringValue("apiResultData").ToJsonString();
}
else
{
throw Oops.Bah(resultDataObj.GetStringValue("errorMsg"));
}
}
else
{
throw Oops.Bah(jobj.GetStringValue("apiErrorMsg"));
}
return rt;
}
/// <summary>
/// 打印面单根据快递订单Id读取相应的面单文件并返回
/// </summary>
/// <param name="authorization">Jwt令牌</param>
/// <param name="orderId">快递订单Id</param>
/// <returns></returns>
[HttpGet("PrintWaybill")]
[NonUnify]
[AllowAnonymous]
public async Task<IActionResult> PrintWaybill([FromQuery][Required] string authorization, [FromQuery][Required] long orderId)
{
var validateResult = JWTEncryption.Validate(authorization);
if (!validateResult.IsValid)
{
return new BadPageResult(403) { Title = "接口鉴权未通过", Description = null, };
//return new UnauthorizedObjectResult("接口鉴权未通过");
}
var order = await _orderRep.AsQueryable().Filter(null, true).FirstAsync(x => x.Id == orderId);
if (order == null)
{
return new BadPageResult(400) { Title = "请选择正确快递单", Description = null, };
}
if (order.CurrentStateCode is "DJY01" or "DJY02")
{
return new BadPageResult(400) { Title = "请先保存并下单后,再打印面单", Description = null, };
}
if (string.IsNullOrEmpty(order.KDNO))
{
return new BadPageResult(500) { Title = "未知异常:快递单号为空", Description = null, };
}
try
{
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, WAY_BILL_DIRECTORY, order.WaybillFilePath);
if (!File.Exists(filePath))
{
_logger.LogInformation("快递面单文件不存在,尝试重新下载...");
var waybillDownloadInfo = await GetSfWaybillDownloadUrl(order.KDNO);
var header = new Dictionary<string, object>() { { "X-Auth-token", waybillDownloadInfo.Token } };
using var stream = await waybillDownloadInfo.Url.SetHeaders(header).GetAsStreamAsync();
var dic = Path.Combine(App.WebHostEnvironment.WebRootPath, WAY_BILL_DIRECTORY);
if (!Directory.Exists(dic))
{
Directory.CreateDirectory(dic);
}
_logger.LogInformation($"调用打印接口,重新下载面单文件,保存地址:{filePath}");
using FileStream fs = new FileStream(filePath, FileMode.Create);
await stream.CopyToAsync(fs);
}
var data = File.ReadAllBytes(filePath);
FileContentResult result = new FileContentResult(data, "application/pdf");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex,"打印面单的过程中发生未知异常");
return new BadPageResult(500) { Title = "打印失败", Description = ex.Message, };
}
}
/// <summary>
/// 根据提单号列表,向与之快递关联的订舱动态中,添加快递动态
/// </summary>
/// <param name="mblnoList">需要添加快递动态的提单号列表</param>
/// <param name="status">快递动态描述</param>
/// <param name="logGuid">日志标志</param>
[NonAction]
private async Task InsertNewBookingStatusWithMblnoList(List<string> mblnoList, string status, string logGuid = "")
{
if (mblnoList == null || mblnoList.Count == 0)
{
_logger.LogInformation($"快递订单关联订舱增加动态,{logGuid}mblNoList为空return");
return;
}
_logger.LogInformation($"快递订单关联订舱增加动态,{logGuid}mblNoList:{string.Join(',', mblnoList)}");
List<string> filteredList = mblnoList.Where(m => !string.IsNullOrWhiteSpace(m)).ToList(); // 如果提单号为空,则不进行处理
if (filteredList == null || filteredList.Count == 0)
{
return;
}
var bookingIdMapMblnoList = await _bookingOrderRep.AsQueryable(o => o.ParentId == 0 && filteredList.Contains(o.MBLNO))
.Select(o => new { o.Id, o.MBLNO })
.ToListAsync();
var bookingStatusLogList = new List<BookingStatusLog>();
foreach (string mblNo in filteredList)
{
var bookingOrderIdListTemp = bookingIdMapMblnoList.Where(o => o.MBLNO == mblNo);
if (bookingOrderIdListTemp == null)
{
_logger.LogInformation($"根据提单号{mblNo}查询订舱记录主键结果为空,{logGuid}");
continue;
}
else if (bookingOrderIdListTemp.Count() > 1)
{
_logger.LogInformation($"根据提单号{mblNo}查询订舱记录主键时查出多条记录ids{string.Join(',', bookingOrderIdListTemp.Select(b => b.Id).ToArray())}{logGuid}");
continue;
}
long? bookingOrderId = bookingOrderIdListTemp.First().Id;
_logger.LogInformation($"根据提单号{mblNo}查询订舱记录主键为{bookingOrderId}{logGuid}");
bookingStatusLogList.Add(new BookingStatusLog()
{
BookingId = bookingOrderId,
Status = status,
Category = "kuaidi",
OpTime = DateTime.Now,
MBLNO = mblNo
});
}
if (bookingStatusLogList.Count > 0)
{
await _bookingStatuslogRep.InsertAsync(bookingStatusLogList);
}
}
#region 顺丰相关
/// <summary>
/// 构建调用顺丰API接口的参数
/// </summary>
/// <returns></returns>
[NonAction]
private async Task<(string url, Dictionary<string, object> param)> BuildSFApiParams(string serviceCode, object msgData)
{
var cacheDictData = await _cache.GetAllDictData();
var url = cacheDictData.FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == "sf_api_url")?.Value;
if (string.IsNullOrEmpty(url))
{
throw Oops.Bah("需配置顺丰Api接口字典参数url地址sf_api_url");
}
var partnerId = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "partnerId")?.Value;
if (string.IsNullOrEmpty(partnerId))
{
throw Oops.Bah("需配置顺丰Api接口字典参数合作伙伴编码即顾客编码partnerId");
}
string accessToken;
if (_cache.Exists(SF_API_TOKEN_KEY))
{
accessToken = _cache.Get(SF_API_TOKEN_KEY);
}
else
{
var secret = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "secret")?.Value;
if (string.IsNullOrEmpty(partnerId))
{
throw Oops.Bah("需配置顺丰Api接口字典参数合作伙伴密钥即校验码secret");
}
var getTokenParam = new Dictionary<string, string>()
{
{ "partnerID",partnerId },
{ "grantType", "password"},
{ "secret", secret}
};
_logger.LogInformation("调用顺丰Token查询接口参数" + getTokenParam.ToJson());
var strRtn = await (url + "/oauth2/accessToken").SetBody(getTokenParam, "application/x-www-form-urlencoded").PostAsStringAsync();
_logger.LogInformation("调用顺丰Token查询接口返回" + strRtn);
var jobj = strRtn.ToJObject();
if (jobj.GetStringValue("apiResultCode") == "A1000")
{
accessToken = jobj.GetStringValue("accessToken");
//设置token过期时间2小时
await _cache.SetTimeoutAsync(SF_API_TOKEN_KEY, accessToken, TimeSpan.FromHours(2));
}
else
{
throw Oops.Bah("调用顺丰Token查询接口时发生异常" + jobj.GetStringValue("apiErrorMsg"));
}
}
var dict = new Dictionary<string, object>()
{
{ "partnerID", partnerId },
{ "serviceCode", serviceCode},
{ "requestID", Guid.NewGuid().ToString()},
{ "timestamp", DateTimeOffset.Now.ToUnixTimeSeconds()},
{ "accessToken", accessToken},
{ "msgData", msgData}
};
return (url + "/std/service", dict);
}
/// <summary>
/// 接收顺丰推送的快递动态
/// </summary>
[AllowAnonymous]
[NonUnify]
[HttpPost("AddSfStatus")]
public async Task<object> AddSfStatus(SFAddOrderStatus orderStatus)
{
string logGuid = Guid.NewGuid().ToString();
_logger.LogInformation($"接收到顺丰运踪动态,{logGuid},入参:{orderStatus?.ToJsonString()}");
var expressDeliveryStatusList = new List<ExpressDeliveryStatus>();
foreach (WaybillRoute item in orderStatus.Body.WaybillRoute)
{
expressDeliveryStatusList.Add(new ExpressDeliveryStatus()
{
OrderId = long.Parse(item.Orderid),
AcceptAddress = item.AcceptAddress,
AcceptTime = string.IsNullOrEmpty(item.AcceptTime) ? null : DateTime.Parse(item.AcceptTime),
ErrorCode = item.ReasonCode,
ErrorDesc = item.ReasonName,
MailNo = item.Mailno,
StatusId = item.Id,
StatusCode = item.OpCode,
StatusDesc = item.Remark
});
}
IEnumerable<IGrouping<long?, ExpressDeliveryStatus>> statusGroupList = expressDeliveryStatusList.GroupBy(e => e.OrderId);
foreach (IGrouping<long?, ExpressDeliveryStatus> item in statusGroupList)
{
long? orderId = item.Key;
ExpressDeliveryStatus lastStatus = item.MaxBy(e => e.StatusId);
ExpressDeliveryOrder order = await _orderRep.AsQueryable(e => e.Id == orderId)
.Filter(null, true)
.FirstAsync();
if (order == null)
{
_logger.LogInformation($"未找到相应的快递订单,{logGuid}Id{orderId}");
continue;
}
#region 更新快递订单当前的状态
_logger.LogInformation($"当前快递订单的状态,{logGuid}Code{order.CurrentStateCode}Desc{order.CurrentStateDesc}");
// 因为此方法为匿名方法所以需要手动设置一下租户Id
expressDeliveryStatusList.Where(s => s.OrderId == orderId).ToList().ForEach(e => e.TenantId = order.TenantId);
Lazy<IUpdateable<ExpressDeliveryOrder>> waitUpdate = new(() => _orderRep.Context.Updateable<ExpressDeliveryOrder>());
if (order.CurrentStateCode != lastStatus.StatusCode)
{
waitUpdate.Value.SetColumns(o => o.CurrentStateCode == lastStatus.StatusCode);
}
if (order.CurrentStateDesc != lastStatus.StatusDesc)
{
waitUpdate.Value.SetColumns(o => o.CurrentStateDesc == lastStatus.StatusDesc);
}
if (waitUpdate.IsValueCreated)
{
_logger.LogInformation($"快递订单的状态更新为,{logGuid}Code{lastStatus.StatusCode}Desc{lastStatus.StatusDesc}");
await waitUpdate.Value.Where(e => e.Id == orderId).ExecuteCommandAsync();
}
#endregion
#region 向与快递关联的订舱列表中添加动态(如果一票快递关联了多条订舱,则每条订舱都要添加动态)
List<string> mblNoList = await _businessRep.AsQueryable(b => b.PId == orderId && b.TenantId == order.TenantId).Filter(null, true).Select(b => b.MBLNO).ToListAsync();
List<string> filteredList = mblNoList.Where(m => !string.IsNullOrWhiteSpace(m)).ToList();
if (filteredList == null)
{
_logger.LogInformation($"根据快递订单主键查询关联业务表的提单号,查询结果为空,{logGuid}");
}
else
{
_logger.LogInformation($"根据快递订单主键查询关联业务表的提单号共查出以下记录MblNos{string.Join(',', filteredList)}{logGuid}");
var bookingIdMapMblnoList = await _bookingOrderRep.AsQueryable(o => o.ParentId == 0 && o.TenantId == order.TenantId && filteredList.Contains(o.MBLNO))
.Filter(null, true)
.Select(o => new { o.Id, o.MBLNO })
.ToListAsync();
var bookingStatusLogList = new List<BookingStatusLog>();
foreach (string mblnoItem in filteredList)
{
var bookingOrderIdListTemp = bookingIdMapMblnoList.Where(o => o.MBLNO == mblnoItem);
if (bookingOrderIdListTemp == null)
{
_logger.LogInformation($"根据提单号{mblnoItem}查询订舱记录主键结果为空,{logGuid}");
continue;
}
else if (bookingOrderIdListTemp.Count() > 1)
{
_logger.LogInformation($"根据提单号{mblnoItem}查询订舱记录主键时查出多条记录ids{string.Join(',', bookingOrderIdListTemp.Select(b => b.Id).ToArray())}{logGuid}");
continue;
}
long? bookingOrderId = bookingOrderIdListTemp.First().Id;
_logger.LogInformation($"根据提单号{mblnoItem}查询订舱记录主键为{bookingOrderId}{logGuid}");
foreach (ExpressDeliveryStatus statusItem in item)
{
bookingStatusLogList.Add(new BookingStatusLog()
{
BookingId = bookingOrderId,
Status = statusItem.StatusDesc,
Category = "kuaidi",
OpTime = DateTime.Now,
MBLNO = bookingOrderIdListTemp.First().MBLNO,
TenantId = order.TenantId
});
}
}
await _bookingStatuslogRep.InsertAsync(bookingStatusLogList);
}
#endregion
}
#region 保存快递动态
// 如果租户Id不为null或0说明有相应的快递订单这种情况再进行新增
expressDeliveryStatusList = expressDeliveryStatusList.Where(e => e.TenantId != null && e.TenantId != 0).ToList();
if (expressDeliveryStatusList != null && expressDeliveryStatusList.Count > 0)
{
await _statusRep.InsertAsync(expressDeliveryStatusList);
}
#endregion
return new { return_code = "0000", return_msg = "成功" };
}
/// <summary>
/// 请求获取顺丰面单Pdf下载地址
/// </summary>
/// <returns></returns>
[NonAction]
private async Task<(string Url, string Token)> GetSfWaybillDownloadUrl(string masterWaybillNo)
{
SFPrintWaybillDto sFSend = new SFPrintWaybillDto()
{
fileType = "pdf",
sync = true,
templateCode = "fm_150_standard_DSWYR7LUN7FB",
version = "2.0",
documents = new List<SFPrintWaybillDto.Document>() {
new SFPrintWaybillDto.Document(masterWaybillNo)
}
};
(string url, Dictionary<string, object> dict) apiParam = await BuildSFApiParams("COM_RECE_CLOUD_PRINT_WAYBILLS", sFSend);
_logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口参数" + apiParam.dict.ToJson());
var strRtn = await apiParam.url.SetBody(apiParam.dict, "application/x-www-form-urlencoded").PostAsStringAsync();
_logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口返回" + strRtn);
var jobj = strRtn.ToJObject();
if (jobj.GetStringValue("apiResultCode") == "A1000")
{
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
bool isSuccess = resultDataObj.GetBooleanValue("success");
if (isSuccess)
{
var fileObj = resultDataObj.GetJObjectValue("obj").GetJArrayValue("files")[0].Value<JObject>();
var token = fileObj.GetStringValue("token");
var url = fileObj.GetStringValue("url");
return (url, token);
}
else
{
var errorMsg = resultDataObj.GetStringValue("errorMsg");
_logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口返回异常errorMsg" + errorMsg);
return default;
}
}
else
{
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
_logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口返回异常apiErrorMsg" + apiErrorMsg);
return default;
}
}
#endregion
#region 地址簿相关
/// <summary>
/// 获取快递地址簿列表
/// </summary>
[HttpGet("GetAddressList")]
public async Task<List<ExpressDeliveryAddressDto>> GetAddressList()
{
var modelList = await _addressRep.ToListAsync(e => true, e => e.Type, OrderByType.Desc);
var result = modelList.Adapt<List<ExpressDeliveryAddressDto>>();
return result;
}
/// <summary>
/// 删除快递地址簿
/// </summary>
[HttpPost("DeleteAddress")]
public async Task DeleteAddress([Required] string ids)
{
long[] idArr = ids.Split(',').Adapt<long[]>();
await _addressRep.DeleteAsync(a => idArr.Contains(a.Id));
}
/// <summary>
/// 新增或修改地址簿
/// </summary>
[HttpPost("SaveOrUpdateAddress")]
public async Task SaveOrUpdateAddress(ExpressDeliveryAddressDto input)
{
if (input == null) return;
var inputModel = input.Adapt<ExpressDeliveryAddress>();
if (inputModel.Id == 0)
{
await _addressRep.InsertAsync(inputModel);
}
else
{
await _addressRep.AsUpdateable(inputModel)
.IgnoreColumns(a => a.TenantId)
.ExecuteCommandAsync();
}
}
#endregion
}
}