using Furion;
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.FriendlyException;
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.Application.Service.ExpressDelivery.Dto.SF;
using Myshipping.Core;
using Myshipping.Core.Attributes;
using Myshipping.Core.Service;
using Newtonsoft.Json;
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
{
///
/// 快递订单服务
///
[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 = "快递已消单";
///
/// 保存面单文件的文件夹名称
///
private const string WAY_BILL_DIRECTORY = "WaybillFile";
private readonly ILogger _logger;
private readonly ISysCacheService _cache;
private readonly SqlSugarRepository _bookingStatuslogRep;
private readonly SqlSugarRepository _bookingOrderRep;
private readonly SqlSugarRepository _orderRep;
private readonly SqlSugarRepository _businessRep;
private readonly SqlSugarRepository _statusRep;
private readonly SqlSugarRepository _addressRep;
private readonly SqlSugarRepository _feeRep;
private readonly INamedServiceProvider _deliverySendServiceProvider;
public ExpressDeliveryService(ILogger logger,
ISysCacheService cache,
SqlSugarRepository bookingStatuslogRep,
SqlSugarRepository bookingOrderRep,
SqlSugarRepository orderRep,
SqlSugarRepository businessRep,
SqlSugarRepository statusRep,
SqlSugarRepository templeteRep,
INamedServiceProvider deliverySendServiceProvider,
SqlSugarRepository feeRep)
{
_logger = logger;
_cache = cache;
_orderRep = orderRep;
_businessRep = businessRep;
_statusRep = statusRep;
_bookingStatuslogRep = bookingStatuslogRep;
_bookingOrderRep = bookingOrderRep;
_addressRep = templeteRep;
_deliverySendServiceProvider = deliverySendServiceProvider;
_feeRep = feeRep;
}
///
/// 分页查询快递主表
///
///
///
[HttpGet("Page")]
public async Task> 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>();
foreach (var item in list.Items)
{
item.Business = await _businessRep.AsQueryable().Filter(null, true).Where(x => x.PId == item.Id).ToListAsync();
}
return list;
}
///
/// 获取详情
///
///
///
[HttpPost("Get")]
public async Task Get(long Id)
{
ExpressDeliveryDto ordOut = new ExpressDeliveryDto();
var main = await _orderRep.FirstOrDefaultAsync(u => u.Id == Id);
if (main != null)
{
ordOut = main.Adapt();
var business = await _businessRep.AsQueryable().Where(x => x.PId == Id).ToListAsync();
ordOut.Business = business;
var feeList = await _feeRep.AsQueryable().Where(x => x.OrderId == Id).ToListAsync();
ordOut.FeeList = feeList;
}
return ordOut;
}
///
/// 保存并返回数据
///
///
[HttpPost("Save")]
[JoinValidateMessge]
public async Task Save(ExpressDeliveryDto input)
{
var entity = input.Adapt();
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 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 businessList = await _businessRep.AsQueryable().Where(x => x.PId == input.Id).ToListAsync();
if (businessList.Count == 0)
{
if (input.Business?.Count != 0)
{
// 直接得到:要关联快递的提单号列表
List 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 mblNoList = businessList.Select(b => b.MBLNO).ToList();
await InsertNewBookingStatusWithMblnoList(mblNoList, STATUS_DISASSOCIATION);
// 直接得到:要删除的业务信息
await _businessRep.DeleteAsync(x => x.PId == entity.Id);
}
else
{
// 分析对比,判断哪些关联,哪些取消关联
List oldMblnoList = businessList.Select(b => b.MBLNO).ToList();
List newMblnoList = input.Business.Select(b => b.MBLNO).ToList();
// 新增关联的提单号列表:
List addMblnoList = newMblnoList.Except(oldMblnoList).ToList();
await InsertNewBookingStatusWithMblnoList(addMblnoList, STATUS_ASSOCIATION);
// 取消关联的提单号列表
List cancelMblnoList = oldMblnoList.Except(newMblnoList).ToList();
await InsertNewBookingStatusWithMblnoList(cancelMblnoList, STATUS_DISASSOCIATION);
if (addMblnoList.Count != 0 || cancelMblnoList.Count != 0)
{
// 先删除,再新增业务信息
await _businessRep.DeleteAsync(x => x.PId == entity.Id);
input.Business.ForEach(b => b.PId = entity.Id);
await _businessRep.InsertAsync(input.Business);
}
}
}
#endregion
if (input.IsSending)
{
await SendBooking(entity);
}
}
return await Get(entity.Id);
}
///
/// 删除单据
///
///
///
[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 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("请上传正确参数");
}
}
///
/// 发送快递
///
/// [HttpPost("SendBooking")]
[NonAction]
public async Task SendBooking(ExpressDeliveryOrder order)
{
if (order == null)
{
throw Oops.Bah("请选择正确数据!");
}
if (order.CurrentStateCode == "DJY01")
{
throw Oops.Bah("下单失败,原因:尚未保存");
}
if (order.CurrentStateCode != "DJY02")
{
throw Oops.Bah("下单失败,原因:重复下单");
}
var Id = order.Id;
IDeliverySend deliverySend = GetDeliverySend(order.KDCompany);
string waybillNo = await deliverySend.CreateOrder(order);
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 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,保存到本地,不会因异常终止当前请求
string filePath = null;
try
{
var dic = Path.Combine(App.WebHostEnvironment.WebRootPath, WAY_BILL_DIRECTORY);
if (!Directory.Exists(dic))
{
Directory.CreateDirectory(dic);
}
var fileName = $"{waybillNo}_{order.Id}.pdf";
filePath = Path.Combine(dic, fileName);
await deliverySend.DownloadWaybillFile(waybillNo, filePath);
}
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
});
return "下单成功!";
}
///
/// 取消快递下单
///
///
///
[HttpPost("CancelBooking")]
public async Task CancelBooking(long Id)
{
var order = _orderRep.FirstOrDefault(x => x.Id == Id) ?? throw Oops.Bah("请选择正确数据!");
if (order.CurrentStateCode is "DJY01" or "DJY02")
{
throw Oops.Bah("消单失败,原因:尚未下单,无需消单");
}
if (order.CurrentStateCode is "DJY04")
{
throw Oops.Bah("消单失败,原因:已消单,无需再次消单");
}
IDeliverySend deliverySend = GetDeliverySend(order.KDCompany);
await deliverySend.CancelOrder(order.Id.ToString());
#region 添加快递动态
var status = new ExpressDeliveryStatus()
{
OrderId = Id,
MailNo = order.KDNO,
StatusCode = "DJY04",
StatusDesc = STATUS_CANCEL
};
await _statusRep.InsertAsync(status);
#endregion
#region 为与快递关联的订舱添加动态
List 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 "取消成功!";
}
///
/// 打印面单(根据快递订单Id读取相应的面单文件并返回)
///
/// Jwt令牌
/// 快递订单Id
///
[HttpGet("PrintWaybill")]
[NonUnify]
[AllowAnonymous]
public async Task 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 dic = Path.Combine(App.WebHostEnvironment.WebRootPath, WAY_BILL_DIRECTORY);
var filePath = Path.Combine(dic, order.WaybillFilePath);
if (!File.Exists(filePath))
{
_logger.LogInformation("快递面单文件不存在,尝试重新下载...");
IDeliverySend deliverySend = GetDeliverySend(order.KDCompany);
await deliverySend.DownloadWaybillFile(order.KDNO, filePath);
}
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, };
}
}
///
/// 根据提单号列表,向与之快递关联的订舱动态中,添加快递动态
///
/// 需要添加快递动态的提单号列表
/// 快递动态描述
/// 日志标志
[NonAction]
private async Task InsertNewBookingStatusWithMblnoList(List 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 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();
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);
}
}
///
/// 根据不同的快递公司,解析出不同的实现类
///
///
///
private IDeliverySend GetDeliverySend(string kdCompany)
{
return _deliverySendServiceProvider.GetService(kdCompany switch
{
"sf" => nameof(SFDeliverySend),
"jd" => throw Oops.Oh("京东快递以后会对接的"),
"ems" => throw Oops.Oh("EMS快递以后会对接的"),
_ => throw Oops.Oh("未知快递公司")
});
}
#region 顺丰推送接口
///
/// 接收顺丰推送的路由动态
///
[AllowAnonymous]
[NonUnify]
[HttpPost("ReceiveSfRoute")]
public async Task