diff --git a/ds-wms-service/DS.Module.HangfireModule/HangfireModuleInstall.cs b/ds-wms-service/DS.Module.HangfireModule/HangfireModuleInstall.cs index bd955aeb..c0d3ac0f 100644 --- a/ds-wms-service/DS.Module.HangfireModule/HangfireModuleInstall.cs +++ b/ds-wms-service/DS.Module.HangfireModule/HangfireModuleInstall.cs @@ -76,4 +76,33 @@ public static class HangfireModuleInstall options.Queues = ["fee"]; }); } + + public static void AddHangfireOPInstall(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) //设置Hangfire 存储的数据兼容性级别为 Version_170 + .UseSimpleAssemblyNameTypeSerializer()//使用简单的程序集名称类型序列化器。这是一种用于序列化和反序列化 Hangfire 任务数据的方法 + .UseRecommendedSerializerSettings()//使用推荐的序列化器设置。这将使用推荐的序列化器选项进行配置 + .UseStorage(new MySqlStorage( + AppSetting.app(new string[] { "HangfireSettings", "DbString" }), + new MySqlStorageOptions + { + TransactionIsolationLevel = IsolationLevel.ReadCommitted,// 事务隔离级别。默认是读取已提交。 + QueuePollInterval = TimeSpan.FromSeconds(15), //- 作业队列轮询间隔。默认值为15秒。 + JobExpirationCheckInterval = TimeSpan.FromHours(1), //- 作业到期检查间隔(管理过期记录)。默认值为1小时。 + CountersAggregateInterval = TimeSpan.FromMinutes(5), //- 聚合计数器的间隔。默认为5分钟。 + PrepareSchemaIfNecessary = true, //- 如果设置为true,则创建数据库表。默认是true。 + DashboardJobListLimit = 50000,//- 仪表板作业列表限制。默认值为50000。 + TransactionTimeout = TimeSpan.FromMinutes(1), //- 交易超时。默认为1分钟。 + TablesPrefix = "Hangfire" + })).UseHangfireHttpJob()); + services.AddHangfireServer(options => + { + //options.WorkerCount = AppSetting.app(new string[] { "HangfireSettings", "WorkerCount" }).ToInt(); + //options.ServerName = AppSetting.app(new string[] { "HangfireSettings", "ServerName" }); + options.Queues = ["op"]; + }); + } } \ No newline at end of file diff --git a/ds-wms-service/DS.WMS.Core/Fee/Method/FeeRecordService.cs b/ds-wms-service/DS.WMS.Core/Fee/Method/FeeRecordService.cs index 8b0f7225..65e4053d 100644 --- a/ds-wms-service/DS.WMS.Core/Fee/Method/FeeRecordService.cs +++ b/ds-wms-service/DS.WMS.Core/Fee/Method/FeeRecordService.cs @@ -254,7 +254,7 @@ namespace DS.WMS.Core.Fee.Method { if (fe.FeeStatus != FeeStatus.Entering && fe.FeeStatus != FeeStatus.RejectSubmission) { - sb.AppendFormat(MultiLanguageConst.FeeRecordStatus, fe.FeeName); + sb.AppendFormat(MultiLanguageConst.GetDescription(MultiLanguageConst.FeeRecordStatus), fe.FeeName); continue; } } diff --git a/ds-wms-service/DS.WMS.Core/HangfireJob/Dtos/WSLModel.cs b/ds-wms-service/DS.WMS.Core/HangfireJob/Dtos/WSLModel.cs new file mode 100644 index 00000000..8547a1c2 --- /dev/null +++ b/ds-wms-service/DS.WMS.Core/HangfireJob/Dtos/WSLModel.cs @@ -0,0 +1,32 @@ +namespace DS.WMS.Core.HangfireJob.Dtos +{ + public class WSLModel + { + public string Date { get; set; } + + public string Month { get; set; } + + public IEnumerable? List { get; set; } + } + + public class WSLItem + { + public long CustomerId { get; set; } + + public string? CustomerName { get; set; } + + public int? YesterdayTeu { get; set; } + + public int? TodayTeu { get; set; } + + public int? TodayTeuCTNPickup { get; set; } + + public int? IncreasedTeu => TodayTeu - YesterdayTeu; + + public string? Date { get; set; } + + public int? NextMonthTEU { get; set; } + + public int? LastMonthTEU { get; set; } + } +} diff --git a/ds-wms-service/DS.WMS.Core/HangfireJob/Interface/IWSLReportJobService.cs b/ds-wms-service/DS.WMS.Core/HangfireJob/Interface/IWSLReportJobService.cs new file mode 100644 index 00000000..ce100133 --- /dev/null +++ b/ds-wms-service/DS.WMS.Core/HangfireJob/Interface/IWSLReportJobService.cs @@ -0,0 +1,17 @@ +using Hangfire; + +namespace DS.WMS.Core.HangfireJob.Interface +{ + /// + /// WSL报表发送 + /// + public interface IWSLReportJobService + { + /// + /// 生成报表 + /// + /// + [Queue("op")] + Task GeneratReportAsync(); + } +} diff --git a/ds-wms-service/DS.WMS.Core/HangfireJob/Method/JobMiddleware.cs b/ds-wms-service/DS.WMS.Core/HangfireJob/Method/JobMiddleware.cs index f1ad6940..4b713cd0 100644 --- a/ds-wms-service/DS.WMS.Core/HangfireJob/Method/JobMiddleware.cs +++ b/ds-wms-service/DS.WMS.Core/HangfireJob/Method/JobMiddleware.cs @@ -1,4 +1,4 @@ -using DS.WMS.Core.HangfireJob.Interface; +using System.Linq.Expressions; using Hangfire; using Hangfire.Dashboard.BasicAuthorization; using Microsoft.AspNetCore.Builder; @@ -21,7 +21,7 @@ namespace DS.WMS.Core.HangfireJob.Method // 将 Hangfire 仪表板添加到应用程序的请求处理管道中 app.UseHangfireDashboard("/hangfire", new DashboardOptions { - Authorization = new[] {new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions + Authorization = [new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions { RequireSsl = false, SslRedirect = false, @@ -34,13 +34,22 @@ namespace DS.WMS.Core.HangfireJob.Method PasswordClear = "ds2024" } } - })} + })] }); - RecurringJob.AddOrUpdate(nameof(IFeeCustTemplateJobService), - s => s.GenerateFeesAsync(), Cron.Daily(23, 30)); - return app; } + + /// + /// 注册定时任务 + /// + /// + /// + /// CRON表达式 + /// 任务ID + public static void RegisterJob(Expression> methodCall, string cron, string? jobId = null) + { + RecurringJob.AddOrUpdate(jobId ?? typeof(T).FullName, methodCall, cron); + } } } diff --git a/ds-wms-service/DS.WMS.Core/HangfireJob/Method/WSLReportJobService.cs b/ds-wms-service/DS.WMS.Core/HangfireJob/Method/WSLReportJobService.cs index 1e2278d7..66f33939 100644 --- a/ds-wms-service/DS.WMS.Core/HangfireJob/Method/WSLReportJobService.cs +++ b/ds-wms-service/DS.WMS.Core/HangfireJob/Method/WSLReportJobService.cs @@ -1,8 +1,10 @@ using DS.Module.Core; -using DS.WMS.Core.Sys.Entity; +using DS.WMS.Core.HangfireJob.Dtos; +using DS.WMS.Core.HangfireJob.Interface; +using DS.WMS.Core.Op.Entity; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using MiniExcelLibs; using SqlSugar; namespace DS.WMS.Core.HangfireJob.Method @@ -10,11 +12,11 @@ namespace DS.WMS.Core.HangfireJob.Method /// /// WSL报表服务 /// - public class WSLReportJobService + public class WSLReportJobService : IWSLReportJobService { static readonly ApiFox api; ISqlSugarClient? db; - IConfiguration configuration; + Microsoft.Extensions.Configuration.IConfiguration config; IWebHostEnvironment hostEnvironment; static WSLReportJobService() @@ -29,7 +31,7 @@ namespace DS.WMS.Core.HangfireJob.Method public WSLReportJobService(IServiceProvider serviceProvider) { db = serviceProvider.GetRequiredService(); - configuration = serviceProvider.GetRequiredService(); + config = serviceProvider.GetRequiredService(); hostEnvironment = serviceProvider.GetRequiredService(); } @@ -37,36 +39,106 @@ namespace DS.WMS.Core.HangfireJob.Method /// 生成报表 /// /// - public async Task GeneratReportAsync() + public async Task GeneratReportAsync() { + string path = Path.Combine(hostEnvironment.WebRootPath, "templates", "WSL.xlsx"); + FileInfo templateFile = new FileInfo(path); + if (!templateFile.Exists) + throw new ApplicationException("未能在下列路径找到模板文件:" + path); + db.QueryFilter.Clear(); var dbLinks = await db.Queryable().ToListAsync(); SqlSugarClient? tenantDb = null; + var today = DateTime.Now; + var today0 = new DateTime(today.Year, today.Month, today.Day); + var yesterday0 = today0.AddDays(-1).Date; + var yesterday = new DateTime(yesterday0.Year, yesterday0.Month, yesterday0.Day, 23, 59, 59); + var lastMonth = today.AddMonths(-1); + var nextMonth = today.AddMonths(1); + var startDate = new DateTime(lastMonth.Year, lastMonth.Month, lastMonth.Day); + var endDate = new DateTime(nextMonth.Year, nextMonth.Month, nextMonth.Day, 23, 59, 59); try { foreach (var dbLink in dbLinks) { - var adminUser = await db.Queryable() - .Where(x => x.TenantId == dbLink.TenantId && x.Status == 0 && x.UserType == 1) - .OrderByDescending(x => x.CreateTime) + if (config["TaskMail:DefaultSetting:Tenant"] != dbLink.TenantId.ToString()) + continue; + + tenantDb = new SqlSugarClient(new ConnectionConfig + { + ConfigId = dbLink.Id, + ConnectionString = dbLink.Connection, + DbType = dbLink.DbType, + IsAutoCloseConnection = true + }); + + WSLModel model = new() + { + Date = today.ToString("yyyy.MM.dd"), + Month = today.ToString("yyyy.MM") + }; + + var list = await tenantDb.Queryable().Where(x => x.SourceCode == "FOB-WSL" && SqlFunc.Between(x.ETD, startDate, endDate)) .Select(x => new { x.Id, - x.UserName, - x.Password, - x.DefaultOrgId, - x.DefaultOrgName, - x.TenantId, - x.TenantName - }).FirstAsync(); - - if (adminUser == null) + x.CustomerId, + x.CustomerName, + x.ETD, + x.TEU + }).ToListAsync(); + + var ids = list.Select(x => x.Id.ToString()); + var ctnList = await tenantDb.Queryable().Where(x => ids.Contains(x.BSNO) && + !SqlFunc.IsNullOrEmpty(x.CntrNo) && !SqlFunc.IsNullOrEmpty(x.SealNo)) + .Select(x => new + { + x.BSNO, + x.TEU + }).ToListAsync(); + + model.List = list.GroupBy(x => new { x.CustomerId, x.CustomerName }).Select(x => new WSLItem { - Console.WriteLine($"未能获取租户系统管理员,租户ID:{dbLink.TenantId}"); - continue; + CustomerId = x.Key.CustomerId, + CustomerName = x.Key.CustomerName, + Date = model.Date, + YesterdayTeu = x.Where(x => x.ETD >= yesterday0 && x.ETD <= yesterday).Sum(x => x.TEU), + TodayTeu = x.Where(x => x.ETD >= today0 && x.ETD <= today).Sum(x => x.TEU), + NextMonthTEU = x.Where(x => x.ETD.Value.Year == nextMonth.Year && x.ETD.Value.Month == nextMonth.Month).Sum(x => x.TEU), + LastMonthTEU = x.Where(x => x.ETD.Value.Year == lastMonth.Year && x.ETD.Value.Month == lastMonth.Month).Sum(x => x.TEU), + }).ToList(); + + foreach (var item in model.List) + { + var ids2 = list.Where(x => x.CustomerId == item.CustomerId).Select(x => x.Id.ToString()); + item.TodayTeuCTNPickup = ctnList.Where(x => ids2.Contains(x.BSNO)).Sum(x => x.TEU); } + MemoryStream ms = new MemoryStream(); + await MiniExcel.SaveAsByTemplateAsync(ms, path, model); + string base64Str = Convert.ToBase64String(ms.ToArray()); + var attaches = new List + { + new() { AttachName = "WSL Volume Daily Increase Report.xlsx", AttachContent = base64Str} + }; + + dynamic[] mailParams = [new + { + SendTo = config["TaskMail:DefaultSetting:Receivers"], + Title = "WSL Volume Daily Increase Report", + Body = "Dear WSL Team" + Environment.NewLine + "Pls kindly check the daily report for your member's nomination booking:", + //ShowName = "", + Account = config["TaskMail:DefaultSetting:Account"], + Password = config["TaskMail:DefaultSetting:Password"], + Server = config["TaskMail:DefaultSetting:Host"], + Port = config["TaskMail:DefaultSetting:Port"], + UseSSL = config["TaskMail:DefaultSetting:UseSSL"], + Attaches = attaches + }]; + var mailResult = await api.SendRequestAsync(HttpMethod.Post, config["TaskMail:MailApiUrl"], mailParams); + if (!mailResult.IsSuccessStatusCode) + throw new ApplicationException("发送邮件失败"); } } finally @@ -74,5 +146,29 @@ namespace DS.WMS.Core.HangfireJob.Method tenantDb?.Dispose(); } } + + class DefaultSetting + { + public long Tenant { get; set; } + + public string? Account { get; set; } + + public string? Password { get; set; } + + public string? Host { get; set; } + + public int? Port { get; set; } + + public bool UseSSL { get; set; } + + public string? Receivers { get; set; } + } + + class Attachment + { + public string? AttachName { get; set; } + + public string? AttachContent { get; set; } + } } } diff --git a/ds-wms-service/DS.WMS.Core/Info/Entity/InfoClientStakeholder.cs b/ds-wms-service/DS.WMS.Core/Info/Entity/InfoClientStakeholder.cs index 38dd6cb5..91de5966 100644 --- a/ds-wms-service/DS.WMS.Core/Info/Entity/InfoClientStakeholder.cs +++ b/ds-wms-service/DS.WMS.Core/Info/Entity/InfoClientStakeholder.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Masuit.Tools.Systems; using SqlSugar; namespace DS.WMS.Core.Info.Entity @@ -21,6 +22,12 @@ namespace DS.WMS.Core.Info.Entity [SugarColumn(ColumnDescription = "客户ID", IsNullable = false)] public long ClientId { get; set; } + /// + /// 客户简称 + /// + [SugarColumn(IsIgnore = true)] + public string? ClientShortName { get; set; } + /// /// 干系人ID /// @@ -51,6 +58,12 @@ namespace DS.WMS.Core.Info.Entity [SugarColumn(ColumnDescription = "干系人状态", IsNullable = false)] public StakeholderStatus Status { get; set; } + /// + /// 干系人状态文本 + /// + [SugarColumn(IsIgnore = true)] + public string StatusText => Status.GetDescription(); + /// /// 备注 /// diff --git a/ds-wms-service/DS.WMS.Core/Info/Method/ClientStakeholderService.cs b/ds-wms-service/DS.WMS.Core/Info/Method/ClientStakeholderService.cs index ffafae2b..35bf0bea 100644 --- a/ds-wms-service/DS.WMS.Core/Info/Method/ClientStakeholderService.cs +++ b/ds-wms-service/DS.WMS.Core/Info/Method/ClientStakeholderService.cs @@ -43,12 +43,16 @@ namespace DS.WMS.Core.Info.Method /// public async Task SubmitAuditAsync(IdModel idModel) { - var list = await TenantDb.Queryable().Where(x => idModel.Ids.Contains(x.Id)).Select(x => new - { - x.Id, - x.Status, - x.CreateByName, - }).ToListAsync(); + var list = await TenantDb.Queryable() + .InnerJoin((x, y) => x.ClientId == y.Id) + .Where((x, y) => idModel.Ids.Contains(x.Id)).Select((x, y) => new + { + x.Id, + x.Status, + x.CreateBy, + x.CreateByName, + CustShortName = y.ShortName + }).ToListAsync(); if (list.Exists(x => x.Status == StakeholderStatus.Pending)) return DataResult.FailedWithDesc(nameof(MultiLanguageConst.ItemsAreAuditing)); @@ -59,7 +63,7 @@ namespace DS.WMS.Core.Info.Method { BusinessId = x.Id, TaskTypeName = CLIENT_STAKEHOLDER_TASK.ToString(), - TaskTitle = $"【{CLIENT_STAKEHOLDER_TASK.GetDescription()}】{x.CreateByName}" + TaskTitle = $"【{CLIENT_STAKEHOLDER_TASK.GetDescription()}】{x.CustShortName} {x.CreateByName}" }); await TenantDb.Ado.BeginTranAsync(); @@ -244,6 +248,22 @@ namespace DS.WMS.Core.Info.Method .Where(whereList) .ToQueryPageAsync(request.PageCondition); + if (result.Data?.Count > 0) + { + var ids = result.Data.Select(x => x.ClientId).Distinct(); + var clients = await TenantDb.Queryable().Where(x => ids.Contains(x.Id)) + .Select(x => new + { + x.Id, + x.ShortName + }).ToListAsync(); + + foreach (var item in result.Data) + { + item.ClientShortName = clients.Find(x => x.Id == item.ClientId)?.ShortName; + } + } + return result; } diff --git a/ds-wms-service/DS.WMS.FeeApi/Program.cs b/ds-wms-service/DS.WMS.FeeApi/Program.cs index 74e7ed76..f084b8e6 100644 --- a/ds-wms-service/DS.WMS.FeeApi/Program.cs +++ b/ds-wms-service/DS.WMS.FeeApi/Program.cs @@ -15,6 +15,7 @@ using NLog.Web; using DS.WMS.Core.HangfireJob.Method; using DS.Module.HangfireModule; using Hangfire; +using DS.WMS.Core.HangfireJob.Interface; var builder = WebApplication.CreateBuilder(args); var environment = builder.Environment.EnvironmentName; @@ -57,5 +58,6 @@ app.UsePublicMiddlewares(); app.UseHangfireServer(); app.UseJobMiddlewares(); +JobMiddleware.RegisterJob(j => j.GenerateFeesAsync(), Cron.Daily(23, 30)); app.Run(); \ No newline at end of file diff --git a/ds-wms-service/DS.WMS.OpApi/Program.cs b/ds-wms-service/DS.WMS.OpApi/Program.cs index 62ba33d4..2b194bb4 100644 --- a/ds-wms-service/DS.WMS.OpApi/Program.cs +++ b/ds-wms-service/DS.WMS.OpApi/Program.cs @@ -15,6 +15,8 @@ using DS.Module.SqlSugar; using DS.Module.Swagger; using DS.Module.UserModule; using DS.WMS.Core; +using DS.WMS.Core.HangfireJob.Interface; +using DS.WMS.Core.HangfireJob.Method; using Hangfire; using NLog.Web; using Swashbuckle.AspNetCore.SwaggerUI; @@ -48,7 +50,8 @@ builder.Services.AddMultiLanguageInstall();// builder.Services.AddDjyModuleInstall();//Djy builder.Services.AddRuleEngineModuleInstall();//DjyУ -builder.Services.AddHangfireModuleInstall();//Hangfire +//builder.Services.AddHangfireModuleInstall();//Hangfire +builder.Services.AddHangfireOPInstall();//Hangfire builder.Services.AddMQModuleInstall();//MQ // builder.Services.AddEndpointsApiExplorer(); @@ -105,4 +108,7 @@ app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); //await ServiceLocator.Instance.GetService().LoadCommonCache(); +app.UseHangfireServer(); +JobMiddleware.RegisterJob(j => j.GeneratReportAsync(), Cron.Daily(23, 59)); + app.Run(); \ No newline at end of file diff --git a/ds-wms-service/DS.WMS.OpApi/appsettings.json b/ds-wms-service/DS.WMS.OpApi/appsettings.json index cfa05c8b..d031cf89 100644 --- a/ds-wms-service/DS.WMS.OpApi/appsettings.json +++ b/ds-wms-service/DS.WMS.OpApi/appsettings.json @@ -111,7 +111,16 @@ "JsonPrint": "/printApi/OpenPrint/GetOpenJsonPrintInfoAsync", "JsonPrintByCode": "/printApi/OpenPrint/GetOpenJsonPrintInfoByTemplateCode", "SQLPrint": "/printApi/OpenPrint/GetOpenSqlPrintInfo", - "MailApiUrl": "http://47.104.73.97:8801/mail/send" + "MailApiUrl": "http://47.104.73.97:8801/mail/send", + "DefaultSetting": { + "Tenant": 1750335377144680448, + "Account": "lyle@yyts.fun", + "Password": "15275215387jZ", + "Host": "smtp.mxhichina.com", + "Port": 465, + "UseSSL": true, + "Receivers": "daisusu@dongshengsoft.com" + } }, "FeeService": { "BaseUrl": "http://118.190.144.189:3008", diff --git a/ds-wms-service/DS.WMS.OpApi/wwwroot/templates/WSL.xlsx b/ds-wms-service/DS.WMS.OpApi/wwwroot/templates/WSL.xlsx new file mode 100644 index 00000000..d55087e4 Binary files /dev/null and b/ds-wms-service/DS.WMS.OpApi/wwwroot/templates/WSL.xlsx differ