using DS.Module.Core; using DS.Module.Core.Condition; using DS.Module.Core.Constants; using DS.Module.Core.Data; using DS.Module.SqlSugar; using DS.Module.UserModule; using DS.WMS.Core.Code.Dtos; using DS.WMS.Core.Map.Dtos; using DS.WMS.Core.Op.Dtos; using DS.WMS.Core.Op.Entity; using DS.WMS.Core.Op.Interface; using DS.WMS.Core.TaskPlat.Dtos; using DS.WMS.Core.TaskPlat.Entity; using DS.WMS.Core.TaskPlat.Interface; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using SqlSugar; using System.Linq.Expressions; using System.Runtime.InteropServices; using System.Text; using System.Web; namespace DS.WMS.Core.TaskPlat.Method { /// /// 任务模块业务类的基类,封装了一些常用的方法 /// public class TaskManageBaseService : ITaskManageBaseService { // 实例化时构建 protected readonly IUser user; protected readonly ILogger logger; protected readonly ISaasDbService saasDbService; protected readonly IServiceProvider serviceProvider; protected readonly IWebHostEnvironment environment; // 按需构建 private Lazy allocationService; private Lazy seaExportCommonService; public TaskManageBaseService(IUser user, ILogger logger, ISaasDbService saasDbService, IServiceProvider serviceProvider, IWebHostEnvironment environment) { this.user = user; this.logger = logger; this.saasDbService = saasDbService; this.serviceProvider = serviceProvider; this.environment = environment; allocationService = new Lazy(serviceProvider.GetRequiredService()); seaExportCommonService = new Lazy(serviceProvider.GetRequiredService()); } /// /// 更新任务主表状态 /// /// 任务主键数组 /// 需要更新状态的列 public async Task SetTaskStatus(long[] taskIds, params Expression>[] columns) { SqlSugarScopeProvider tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); //任务不考虑OrgId,这里去掉 tenantDb.QueryFilter.Clear(); var updateable = tenantDb.Updateable(); foreach (var item in columns) { updateable.SetColumns(item); } updateable.SetColumns(x => x.UpdateBy == long.Parse(user.UserId)) .SetColumns(x => x.UpdateTime == DateTime.Now) .SetColumns(x => x.UpdateUserName == user.UserName); await updateable.Where(x => taskIds.Contains(x.Id)) .ExecuteCommandAsync(); // 后面可以记录日志 } /// /// 设置任务处理人 /// /// 任务主键数组 /// 人员信息列表 public async Task SetTaskOwner(long[] taskIds, List userInfo) { SqlSugarScopeProvider tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); //任务不考虑OrgId,这里去掉 tenantDb.QueryFilter.Clear(); try { var taskList = await tenantDb.Queryable().Where(x => taskIds.Contains(x.Id)).ToListAsync(x => new { x.Id, x.STATUS, x.STATUS_NAME }); var taskIdList = taskList.Select(x => x.Id).ToList(); List allocationList = new(); foreach (var item in taskList) { allocationList.AddRange(userInfo.Select(x => new TaskBaseAllocation { TaskId = item.Id, UserId = x.RecvUserId, UserName = x.RecvUserName, Status = item.STATUS, StatusName = item.STATUS_NAME, StatusTime = DateTime.Now })); } await tenantDb.Ado.BeginTranAsync(); await tenantDb.Deleteable(x => taskIdList.Contains(x.TaskId)).ExecuteCommandAsync(); await tenantDb.Insertable(allocationList).ExecuteCommandAsync(); await tenantDb.Updateable() .SetColumns(x => x.IS_PUBLIC == 0) .Where(x => taskIdList.Contains(x.Id)) .ExecuteCommandAsync(); await tenantDb.Ado.CommitTranAsync(); } catch (Exception) { await tenantDb.Ado.RollbackTranAsync(); throw; } } #region Tools /// /// 保存文件并返回文件完整路径 /// /// 追加文件夹 /// 文件二进制流 /// 批次号 /// 无拓展名的文件名 /// 文件类型 /// 附件类型 bcfiles-BC文件 sofile-订舱附件 /// 返回文件完整路径 protected async Task SaveFile(string fileDictKey, byte[] fileBytes, string batchNo, string fileNameNoSuffix, PrintFileTypeEnum printFileType, string attachFileType = "sofiles") { var basePath = AppSetting.app(new string[] { "FileSettings", "BasePath" }); var relativePath = AppSetting.app(new string[] { "FileSettings", "RelativePath" }); if (!string.IsNullOrWhiteSpace(attachFileType)) relativePath += $"\\{attachFileType}"; if (!string.IsNullOrWhiteSpace(fileDictKey)) relativePath += $"\\{fileDictKey}"; string? dirAbs; if (string.IsNullOrEmpty(basePath)) { dirAbs = Path.Combine(environment.WebRootPath ?? "", relativePath); } else { dirAbs = Path.Combine(basePath, relativePath); } if (!Directory.Exists(dirAbs)) Directory.CreateDirectory(dirAbs); var fileType = string.Empty; if (printFileType == PrintFileTypeEnum.PDF) { fileType = ".pdf"; } else if (printFileType == PrintFileTypeEnum.XLSX) { fileType = ".xlsx"; } else if (printFileType == PrintFileTypeEnum.DOCX) { fileType = ".docx"; } else if (printFileType == PrintFileTypeEnum.XLS) { fileType = ".xls"; } else if (printFileType == PrintFileTypeEnum.DOC) { fileType = ".doc"; } string curFileName = fileNameNoSuffix; //var id = SnowFlakeSingle.Instance.NextId(); var fileSaveName = $"{curFileName}{fileType}"; string fileRelaPath = Path.Combine(relativePath, fileSaveName); string fileAbsPath = Path.Combine(dirAbs, fileSaveName); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); fileRelaPath = fileRelaPath.Replace("\\", "/"); fileAbsPath = fileAbsPath.Replace("\\", "/"); } //logger.LogInformation("批次={no} 生成文件保存路径完成 路由={filePath} 服务器系统={system}", batchNo, fileRelaPath, RuntimeInformation.OSDescription); await File.WriteAllBytesAsync(fileAbsPath, fileBytes); //string bookFilePath; //if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) //{ // bookFilePath = System.Text.RegularExpressions.Regex.Match(fileAbsPath, relativePath.Replace("/", "\\/") + ".*").Value; //} //else //{ // bookFilePath = System.Text.RegularExpressions.Regex.Match(fileAbsPath, relativePath.Replace("\\", "\\\\") + ".*").Value; //} return fileAbsPath; //return bookFilePath; } /// /// 获取文件类型 /// /// 文件完成路径 /// 返回文件类型枚举 protected PrintFileTypeEnum GetFileType(string fileName) { PrintFileTypeEnum fileType = PrintFileTypeEnum.PDF; switch (Path.GetExtension(fileName).ToLower()) { case ".pdf": fileType = PrintFileTypeEnum.PDF; break; case ".xlsx": fileType = PrintFileTypeEnum.XLSX; break; case ".docx": fileType = PrintFileTypeEnum.DOCX; break; case ".xls": fileType = PrintFileTypeEnum.XLS; break; case ".doc": fileType = PrintFileTypeEnum.DOC; break; } return fileType; } #endregion /// /// 根据任务ID获取附件信息 /// /// 任务Id /// 附件分类代码 public async Task<(string fileFullPath, string fileName)> GetTaskFileInfo(long taskId, string fileCategory) { var tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); //任务不考虑OrgId,这里去掉 tenantDb.QueryFilter.Clear(); var bcTaskInfo = await tenantDb.Queryable().Where(u => u.Id == taskId).FirstAsync(); if (bcTaskInfo == null) { throw new Exception(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.DataQueryNoData))); } TaskFileCategoryEnum fileCategoryEnum = TaskFileCategoryEnum.NONE; System.Enum.TryParse(fileCategory, out fileCategoryEnum); if (fileCategoryEnum == TaskFileCategoryEnum.NONE) { // 附件分类代码错误,请提供正确的分类代码 throw new Exception(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.TaskFileCategoryError))); } string name = fileCategoryEnum.ToString(); var fileInfo = await tenantDb.Queryable().Where(u => u.TASK_PKID == taskId && u.FILE_CATEGORY == name).OrderByDescending(x => x.Id).FirstAsync(); if (fileInfo == null) { // 附件分类代码错误,请提供正确的分类代码 throw new Exception( string.Format(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.TaskFileEmpty)), taskId) ); } var basePath = AppSetting.app(new string[] { "FileSettings", "BasePath" }); string fileFullPath; if (string.IsNullOrEmpty(basePath)) { fileFullPath = Path.Combine(environment.WebRootPath ?? "", fileInfo.FILE_PATH); } else { fileFullPath = Path.Combine(basePath, fileInfo.FILE_PATH); } if (!File.Exists(fileFullPath)) { logger.LogError("根据任务主键获取文件信息失败:文件不存在,fileFullPath={fileFullPath}", fileFullPath); //任务主键{0} 附件下载请求失败,请确认文件是否存在 throw new Exception( string.Format(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.TaskFileNotExists)), taskId) ); } var fileName = HttpUtility.UrlEncode(fileInfo.FILE_NAME, Encoding.GetEncoding("UTF-8"))!; return (fileFullPath, fileName); //return (new FileStream(fileFullPath, FileMode.Open), fileName); } /// /// 根据订单及配置,将所有或指定的公共任务匹配到个人 /// /// 任务Id列表(当传入时,则只匹配列表中指定的任务) /// 涉及当前登陆人的匹配结果 public async Task> MatchTask(List? taskIdList = null) { /* * * 测试库测试用Sql: SELECT t.SplitOrMergeFlag,t.OperatorId,t.OperatorName,t.Doc,t.DocName,t.CustomerService,t.CustomerServiceName,t.ForeignCustomerService,t.ForeignCustomerServiceName,t.Sale,t.SaleId,t. * FROM `op_sea_export` t where Deleted=0 and id In(1816649497120477184,1816779333432381440,1780891904372772864) order by id desc SELECT t.SplitOrMergeFlag,t.OperatorId,t.OperatorName,t.Doc,t.DocName,t.CustomerService,t.CustomerServiceName,t.ForeignCustomerService,t.ForeignCustomerServiceName,t.Sale,t.SaleId,t. * FROM `op_sea_export` t where Deleted=0 and id In(1813475270208917504,1813509723408961536,1816277472539447296) order by id desc */ MatchTaskResultDto result = new MatchTaskResultDto(); var tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); //任务不考虑OrgId,这里去掉 tenantDb.QueryFilter.Clear(); var time = DateTime.Now.AddMonths(-3); var taskList = await tenantDb.Queryable() .Where(x => x.IS_PUBLIC == 1 && x.STATUS == TaskStatusEnum.Create.ToString() && !string.IsNullOrEmpty(x.MBL_NO) && x.CreateTime > time) .WhereIF(taskIdList != null && taskIdList.Count > 0, x => taskIdList!.Contains(x.Id)) //.Where(x => x.Id == 1813475270208917504 || x.Id == 1816277472539447296 || x.Id == 1813509723408961536) .ToListAsync(x => new TaskBaseInfo { Id = x.Id, MBL_NO = x.MBL_NO, TASK_TYPE = x.TASK_TYPE, TASK_TYPE_NAME = x.TASK_TYPE_NAME, //CARRIER_ID = x.CARRIER_ID, }); logger.LogInformation($"MatchTask共查询出{taskList.Count}条待匹配的记录,taskList:{string.Join(',', taskList.Select(x => x.Id).Take(50))}"); var allotSetCache = await allocationService.Value.GetAllList(); if (!allotSetCache.Succeeded || allotSetCache.Data?.Any() != true) { return DataResult.Success("操作成功!", result, MultiLanguageConst.DataUpdateSuccess); } var taskTypeStrList = taskList.Select(x => x.TASK_TYPE).Distinct().ToList(); var allotSetList = allotSetCache.Data.Where(x => taskTypeStrList.Contains(x.TaskTypeCode)).ToList(); if (allotSetList.Count == 0) { return DataResult.Success("操作成功!", result, MultiLanguageConst.DataUpdateSuccess); } // 需要查询的订单的提单号的集合,用于一次性将需要查询的订单查询出来; // 后续如果需要查询舱位,可以再补充字段如List waitQuerySlotWithMblnoList = new(); HashSet waitQuerySeaExportWithMblnoList = new(); // 分配规则 Dictionary> allotTargetList = new(); foreach (var taskItem in taskList) { List taskItemAllotSetList = allotSetList.Where(x => x.TaskTypeCode == taskItem.TASK_TYPE).ToList(); foreach (var allotSetItem in taskItemAllotSetList) { // 这里先存下来,后面再统一查库,减少查询次数 // 如果配置的下面四种接收对象,则需要查询订单 if (allotSetItem.IsAllotCustomerService || allotSetItem.IsAllotSale || allotSetItem.IsAllotOperator || allotSetItem.IsAllotVouchingClerk) { waitQuerySeaExportWithMblnoList.Add(taskItem.MBL_NO!); } } } // 查出涉及到的订单 var seaExportList = await tenantDb.Queryable() .Where(x => x.ParentId == 0 && ( (x.SplitOrMergeFlag == 0 && waitQuerySeaExportWithMblnoList.Contains(x.MBLNO)) || (x.SplitOrMergeFlag == 1 && waitQuerySeaExportWithMblnoList.Contains(x.BookingNo)) )).Select().ToListAsync(); List<(TaskBaseInfo, List)> allotData = new(); foreach (var taskItem in taskList) { var recvUserList = new List(); List taskItemAllotSetList = allotSetList.Where(x => x.TaskTypeCode == taskItem.TASK_TYPE).ToList(); var taskItemAllotList = new List(); foreach (var allotSetItem in taskItemAllotSetList) { // 验证条件 if (!string.IsNullOrEmpty(allotSetItem.Condition)) { var contitionContent = JsonConvert.DeserializeObject(allotSetItem.Condition!)!; TaskFlowDataContext dataContext = new(); if (allotSetItem.IsAllotCustomerService || allotSetItem.IsAllotSale || allotSetItem.IsAllotOperator || allotSetItem.IsAllotVouchingClerk) { var seaExport = seaExportList.FirstOrDefault(x => (x.SplitOrMergeFlag == 0 && taskItem.MBL_NO == x.MBLNO) || (x.SplitOrMergeFlag == 1 && taskItem.MBL_NO == x.BookingNo)); if (seaExport == null) continue; // 这里为了效率,设置为如果订单为空,则跳过;后面如果需要判断其他表,需要把这里注释掉 dataContext.Set(TaskFlowDataNameConst.Business, seaExport); } if (!ConditionHelper.IsPass(contitionContent, dataContext)) continue; } if (allotSetItem.IsAllotCustomerService || allotSetItem.IsAllotSale || allotSetItem.IsAllotOperator || allotSetItem.IsAllotVouchingClerk) { var order = seaExportList.FirstOrDefault(x => (x.SplitOrMergeFlag == 0 && taskItem.MBL_NO == x.MBLNO) || (x.SplitOrMergeFlag == 1 && taskItem.MBL_NO == x.BookingNo)); if (order == null) { continue; } /* * 操作Operator=订单里的:OperatorId+OperatorName * 单证VouchingClerk=订单里的:Doc+DocName * 销售Sale=订单里的:SaleId+Sale * 客服CustomerService=订单里的:CustomerService+CustomerServiceName / ForeignCustomerService+ForeignCustomerServiceName */ if (allotSetItem.IsAllotCustomerService) { if (order.CustomerService != 0 && !string.IsNullOrEmpty(order.CustomerServiceName)) { recvUserList.Add(new RecvUserInfo(order.CustomerService, order.CustomerServiceName)); } if (order.ForeignCustomerService != 0 && !string.IsNullOrEmpty(order.ForeignCustomerServiceName)) { recvUserList.Add(new RecvUserInfo(order.ForeignCustomerService, order.ForeignCustomerServiceName)); } } if (allotSetItem.IsAllotOperator && order.OperatorId != 0 && !string.IsNullOrEmpty(order.OperatorName)) { recvUserList.Add(new RecvUserInfo(order.OperatorId, order.OperatorName)); } if (allotSetItem.IsAllotSale && order.SaleId != 0 && !string.IsNullOrEmpty(order.Sale)) { recvUserList.Add(new RecvUserInfo(order.SaleId, order.Sale)); } if (allotSetItem.IsAllotVouchingClerk && order.Doc != 0 && !string.IsNullOrEmpty(order.DocName)) { recvUserList.Add(new RecvUserInfo(order.Doc, order.DocName)); } } } if (recvUserList.Count != 0) { recvUserList = recvUserList.DistinctBy(x => x.RecvUserId).ToList(); allotData.Add((taskItem, recvUserList)); await SetTaskOwner([taskItem.Id], recvUserList); } } return DataResult.Success("操作成功!", result, MultiLanguageConst.DataUpdateSuccess); } /// /// 通过任务主表主键设置任务状态(任务台使用) /// /// 任务主表主键 /// 业务类型 /// 业务状态 /// 状态发生时间 /// 业务主键 public async Task SetTaskStatus(long taskBaseId, TaskBaseTypeEnum taskBaseTypeEnum, TaskStatusEnum taskStatusEnum, DateTime? statusTime, long? bsno = null) { SqlSugarScopeProvider tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); TaskBaseInfo taskInfo = await tenantDb.Queryable().ClearFilter(typeof(IOrgId)) .Where(t => t.Id == taskBaseId) .FirstAsync(); return await SetTaskStatus(taskInfo, taskStatusEnum, statusTime, bsno); } /// /// 通过任务主表对象设置任务状态() /// /// 任务主表对象 /// 业务状态 /// 状态发生时间 /// 业务主键 public async Task SetTaskStatus(TaskBaseInfo taskInfo, TaskStatusEnum taskStatusEnum, DateTime? statusTime, long? bsno = null) { if (taskInfo is null) { throw new ArgumentNullException(nameof(taskInfo)); } SqlSugarScopeProvider tenantDb = saasDbService.GetBizDbScopeById(user.TenantId); // 修改任务的状态 taskInfo.STATUS = taskStatusEnum.ToString(); taskInfo.STATUS_NAME = taskStatusEnum.EnumDescription(); taskInfo.RealUserId = long.Parse(user.UserId); taskInfo.RealUserName = user.UserName; if (taskStatusEnum == TaskStatusEnum.Complete) { taskInfo.IS_COMPLETE = 1; taskInfo.COMPLETE_DATE = statusTime; } // 如果任务状态为已完成,则查询任务完成时任务对应模块的配置中要设置的业务状态,然后进行设置 if (taskStatusEnum == TaskStatusEnum.Complete) { long? orderId = bsno; if (orderId == null) { if (long.TryParse(taskInfo.BOOK_ORDER_NO, out long temp)) { orderId = temp; } } if (orderId != null) { string? statusCode = await tenantDb.Queryable().Where(x => x.ModuleType == 2 && x.TaskType == taskInfo.TASK_TYPE).Select(x => x.BusinessStatusCode).FirstAsync(); if (!string.IsNullOrEmpty(statusCode)) { await seaExportCommonService.Value.SetGoodsStatus(statusCode, (long)orderId, tenantDb); } } } try { await tenantDb.Ado.BeginTranAsync(); await tenantDb.Updateable(taskInfo).UpdateColumns(x => new { x.UpdateBy, x.UpdateTime, x.UpdateUserName, x.COMPLETE_DATE, x.IS_COMPLETE, x.STATUS, x.STATUS_NAME, x.RealUserId, x.RealUserName }).ExecuteCommandAsync(); var taskBaseAllocationList = await tenantDb.Queryable().Where(x => x.TaskId == taskInfo.Id).ToListAsync(); if (taskBaseAllocationList.Count != 0) { taskBaseAllocationList.ForEach(x => { x.Status = taskStatusEnum.ToString(); x.StatusName = taskStatusEnum.EnumDescription(); x.StatusTime = statusTime; }); await tenantDb.CopyNew().Updateable(taskBaseAllocationList).UpdateColumns(x => new { x.UpdateBy, x.UpdateTime, x.UpdateUserName, x.Status, x.StatusName, x.StatusTime }).ExecuteCommandAsync(); } await tenantDb.Ado.CommitTranAsync(); } catch (Exception) { await tenantDb.Ado.RollbackTranAsync(); throw; } return DataResult.Successed(MultiLanguageConst.GetDescription(nameof(MultiLanguageConst.DataUpdateSuccess))); } #region 根据收货地港口英文名解析出起始港对象 /// /// 根据收货地港口英文名解析出起始港对象 /// /// 收货地港口英文名 /// 起始港缓存 /// 起始港缓存映射 /// 起始港对象 protected async Task> PlaceReceiptToPortload(string portEnName, List cachePortLoad, Func>>> cacheMapPortLoadFunc) { CodePortRes portInfo = null; if (string.IsNullOrEmpty(portEnName)) { return DataResult.FailedData(portInfo); } // 匹配方式1:精准匹配 portInfo = cachePortLoad.FirstOrDefault(x => x.PortName.Equals(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式2:起始模糊匹配 portInfo = cachePortLoad.FirstOrDefault(x => x.PortName.StartsWith(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式3:完整模糊匹配 portInfo = cachePortLoad.FirstOrDefault(x => x.PortName.Contains(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式4:精准映射匹配 var mapCachePortLoad = await cacheMapPortLoadFunc(); var map = mapCachePortLoad.Data.FirstOrDefault(x => x.Module == MappingModuleConst.RECEIPT_TO_PORTLOAD && x.MapName.Equals(portEnName, StringComparison.OrdinalIgnoreCase)); if (map != null) { portInfo = cachePortLoad.FirstOrDefault(x => x.Id == map.LinkId); if (portInfo != null) return DataResult.Success(portInfo); } return DataResult.FailedData(portInfo); } #endregion #region 根据交货地港口英文名解析出目的港对象 /// /// 根据交货地港口英文名解析出目的港对象 /// /// 交货地港口英文名 /// 目的港缓存 /// 目的港缓存映射 /// 目的港对象 protected async Task> PlaceDeliveryToPort(string portEnName, List cachePort, Func>>> cacheMapPortFunc) { CodePortRes portInfo = null; if (string.IsNullOrEmpty(portEnName)) { return DataResult.FailedData(portInfo); } // 匹配方式1:精准匹配 portInfo = cachePort.FirstOrDefault(x => x.PortName.Equals(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式2:起始模糊匹配 portInfo = cachePort.FirstOrDefault(x => x.PortName.StartsWith(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式3:完整模糊匹配 portInfo = cachePort.FirstOrDefault(x => x.PortName.Contains(portEnName, StringComparison.OrdinalIgnoreCase)); if (portInfo != null) return DataResult.Success(portInfo); // 匹配方式4:精准映射匹配 var mapCachePort = await cacheMapPortFunc(); var map = mapCachePort.Data.FirstOrDefault(x => x.Module == MappingModuleConst.DELIVERY_TO_PORT && x.MapName.Equals(portEnName, StringComparison.OrdinalIgnoreCase)); if (map != null) { portInfo = cachePort.FirstOrDefault(x => x.Id == map.LinkId); if (portInfo != null) return DataResult.Success(portInfo); } return DataResult.FailedData(portInfo); } #endregion } }