diff --git a/AzureDataModel/AzureDbContext.cs b/AzureDataModel/AzureDbContext.cs deleted file mode 100644 index e44c194..0000000 --- a/AzureDataModel/AzureDbContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using TaskScheduler = AzureDataModel.Entities.TaskScheduler; - -namespace AzureDataModel; - -public class AzureDbContext : DbContext -{ - public AzureDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - var configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") - .Build(); - - var connectionString = configuration.GetConnectionString("AzureConnection"); - optionsBuilder.UseAzureSql(connectionString, options => options.CommandTimeout(300)); - } - - public DbSet TaskSchedulers { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasKey(e => e.Id); - - modelBuilder.Entity() - .Property(e => e.RowPointer) - .HasDefaultValueSql("newid()"); - - modelBuilder.Entity() - .Property(e => e.CreateDate) - .HasDefaultValueSql("getdate()"); - } -} \ No newline at end of file diff --git a/AzureDataModel/Services/ITaskSchedulerService.cs b/AzureDataModel/Services/ITaskSchedulerService.cs deleted file mode 100644 index 5eb356f..0000000 --- a/AzureDataModel/Services/ITaskSchedulerService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using AzureDataModel.Dtos; - -namespace AzureDataModel.Services; - -public interface ITaskSchedulerService -{ - Task> GetTaskSchedulers(); -} \ No newline at end of file diff --git a/AzureDataModel/appsettings_Azure.json b/AzureDataModel/appsettings_Azure.json deleted file mode 100644 index c10bc55..0000000 --- a/AzureDataModel/appsettings_Azure.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ConnectionStrings": { - "AzureConnection": "Server=tcp:fakrosno-hangfire.database.windows.net,1433;Initial Catalog=FaKrosnoHangfire;Persist Security Info=False;User ID=fakrosno_hangfire@trentblaugranagmail.onmicrosoft.com;Password=juhrot-zigQuz-6jydpu;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Authentication=\"Active Directory Password\";" - } -} \ No newline at end of file diff --git a/FA_WEB.sln b/FA_WEB.sln index 81ea7c8..acc6422 100644 --- a/FA_WEB.sln +++ b/FA_WEB.sln @@ -13,9 +13,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FaKrosnoEfDataModel", "FaKr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SytelineSaAppEfDataModel", "SytelineSaAppEfDataModel\SytelineSaAppEfDataModel.csproj", "{02B7F642-62C1-4BA2-87B0-0B9214319095}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HangfireApi", "HangfireApi\HangfireApi.csproj", "{52106FF2-E714-4FFB-B383-38B1E782B352}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureDataModel", "AzureDataModel\AzureDataModel.csproj", "{5971D564-5108-4777-8273-DDE90BF39879}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrdersManagementDataModel", "OrdersManagementDataModel\OrdersManagementDataModel.csproj", "{5971D564-5108-4777-8273-DDE90BF39879}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,10 +37,6 @@ Global {02B7F642-62C1-4BA2-87B0-0B9214319095}.Debug|Any CPU.Build.0 = Debug|Any CPU {02B7F642-62C1-4BA2-87B0-0B9214319095}.Release|Any CPU.ActiveCfg = Release|Any CPU {02B7F642-62C1-4BA2-87B0-0B9214319095}.Release|Any CPU.Build.0 = Release|Any CPU - {52106FF2-E714-4FFB-B383-38B1E782B352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52106FF2-E714-4FFB-B383-38B1E782B352}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52106FF2-E714-4FFB-B383-38B1E782B352}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52106FF2-E714-4FFB-B383-38B1E782B352}.Release|Any CPU.Build.0 = Release|Any CPU {5971D564-5108-4777-8273-DDE90BF39879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5971D564-5108-4777-8273-DDE90BF39879}.Debug|Any CPU.Build.0 = Debug|Any CPU {5971D564-5108-4777-8273-DDE90BF39879}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/HangfireApi/Controllers/HangfireJobsController.cs b/FaKrosnoApi/Controllers/HangfireJobsController.cs similarity index 59% rename from HangfireApi/Controllers/HangfireJobsController.cs rename to FaKrosnoApi/Controllers/HangfireJobsController.cs index ca19e30..2edbcae 100644 --- a/HangfireApi/Controllers/HangfireJobsController.cs +++ b/FaKrosnoApi/Controllers/HangfireJobsController.cs @@ -1,21 +1,21 @@ using System.Diagnostics; -using AzureDataModel.Dtos; -using AzureDataModel.Services; +using FaKrosnoApi.Models; using Hangfire; using Hangfire.Storage; -using HangfireApi.Dtos; using Microsoft.AspNetCore.Mvc; +using OrdersManagementDataModel.Dtos; +using OrdersManagementDataModel.Services; -namespace HangfireApi.Controllers; +namespace FaKrosnoApi.Controllers; [ApiController] [Route("api/[controller]")] -public class HangfireJobsController(JobStorage jobStorage, IRecurringJobManager recurringJobManager, ITaskSchedulerService service) : ControllerBase +public class HangfireJobsController(JobStorage jobStorage, IRecurringJobManager recurringJobManager, ITaskSchedulerService service) : Controller { [HttpGet("GetJobsToRun")] - public async Task>> GetJobsToRun() + public async Task>> GetJobsToRun() { - IList jobsToRun = new List(); + IList jobsToRun = new List(); using (IStorageConnection? connection = jobStorage.GetConnection()) { @@ -28,7 +28,7 @@ public class HangfireJobsController(JobStorage jobStorage, IRecurringJobManager if (taskScheduler != null) { - jobsToRun.Add(new JobDto(recurringJob.Id, recurringJob.Cron, taskScheduler.Path, + jobsToRun.Add(new JobModel(recurringJob.Id, recurringJob.Cron, taskScheduler.Path, recurringJob.LastExecution, recurringJob.NextExecution, recurringJob.Job)); } } @@ -58,6 +58,49 @@ public class HangfireJobsController(JobStorage jobStorage, IRecurringJobManager return Ok("Zadania zostały zaplanowane do uruchamiania zgodnie z ich CRON."); } + + [HttpPost("AddTask")] + public async Task AddTask([FromBody] TaskSchedulerDto taskSchedulerDto) + { + var taskScheduler = new OrdersManagementDataModel.Entities.TaskScheduler + { + Name = taskSchedulerDto.Name, + Path = taskSchedulerDto.Path, + CronOptions = taskSchedulerDto.CronOptions, + CreateDate = DateTime.UtcNow + }; + + int result = await service.AddTaskScheduler(taskSchedulerDto); + + if (result == 0) + { + return BadRequest("Nie udało się dodać zadania."); + } + + recurringJobManager.AddOrUpdate(taskScheduler.Name, () => RunConsoleApplication(taskScheduler.Path), + taskScheduler.CronOptions, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + return Ok("Zadanie zostało dodane."); + } + + [HttpGet("GetTasks")] + public async Task>> GetTasks() + { + var tasks = await service.GetTaskSchedulers(); + + // foreach (TaskSchedulerDto taskSchedulerDto in tasks) + // { + // taskSchedulerDto.JobDetails = GetJob(taskSchedulerDto.Name); + // } + + return Ok(tasks); + } + + private JobData? GetJob(string jobId) + { + using IStorageConnection? connection = jobStorage.GetConnection(); + return connection.GetJobData(jobId); + } private void RunConsoleApplication(string pathToApp) { diff --git a/FaKrosnoApi/Controllers/ScheduleJobController.cs b/FaKrosnoApi/Controllers/ScheduleJobController.cs new file mode 100644 index 0000000..ae259c2 --- /dev/null +++ b/FaKrosnoApi/Controllers/ScheduleJobController.cs @@ -0,0 +1,17 @@ +using FaKrosnoApi.Services; +using Microsoft.AspNetCore.Mvc; + +namespace FaKrosnoApi.Controllers; + +[ApiController] +[Route("[controller]")] +public class ScheduleJobController(IScheduleJobService scheduledJob) : ControllerBase +{ + [HttpPost("start")] + public IActionResult StartJob() + { + scheduledJob.ExecuteAsync(); + return Ok("Job started"); + } +} + \ No newline at end of file diff --git a/FaKrosnoApi/FaKrosnoApi.csproj b/FaKrosnoApi/FaKrosnoApi.csproj index 8f460db..418c3ca 100644 --- a/FaKrosnoApi/FaKrosnoApi.csproj +++ b/FaKrosnoApi/FaKrosnoApi.csproj @@ -7,8 +7,10 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,6 +22,7 @@ + diff --git a/FaKrosnoApi/Models/EmailSettingsModel.cs b/FaKrosnoApi/Models/EmailSettingsModel.cs new file mode 100644 index 0000000..9378b5e --- /dev/null +++ b/FaKrosnoApi/Models/EmailSettingsModel.cs @@ -0,0 +1,11 @@ +namespace FaKrosnoApi.Models; + +public class EmailSettingsModel +{ + public string SmtpServer { get; set; } + public int Port { get; set; } + public string SenderEmail { get; set; } + public string SenderPassword { get; set; } + public string RecipientEmail { get; set; } + public bool UseSsl { get; set; } +} \ No newline at end of file diff --git a/HangfireApi/Dtos/JobDto.cs b/FaKrosnoApi/Models/JobModel.cs similarity index 90% rename from HangfireApi/Dtos/JobDto.cs rename to FaKrosnoApi/Models/JobModel.cs index 3e52d88..f799f51 100644 --- a/HangfireApi/Dtos/JobDto.cs +++ b/FaKrosnoApi/Models/JobModel.cs @@ -1,8 +1,8 @@ using Hangfire.Common; -namespace HangfireApi.Dtos; +namespace FaKrosnoApi.Models; -public class JobDto( +public class JobModel( string jobId, string cron, string path, diff --git a/FaKrosnoApi/Models/JobSettingsModel.cs b/FaKrosnoApi/Models/JobSettingsModel.cs new file mode 100644 index 0000000..36e95ba --- /dev/null +++ b/FaKrosnoApi/Models/JobSettingsModel.cs @@ -0,0 +1,6 @@ +namespace FaKrosnoApi.Models; + +public class JobSettingsModel +{ + public int QueryIntervalMinutes { get; set; } +} \ No newline at end of file diff --git a/FaKrosnoApi/Program.cs b/FaKrosnoApi/Program.cs index 1cec651..e8c5feb 100644 --- a/FaKrosnoApi/Program.cs +++ b/FaKrosnoApi/Program.cs @@ -1,35 +1,60 @@ using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.Text; +using FaKrosnoApi.Models; +using FaKrosnoApi.Services; using FaKrosnoEfDataModel; using FaKrosnoEfDataModel.Services; +using Hangfire; +using Hangfire.SqlServer; using Microsoft.AspNetCore.Authentication.JwtBearer; +using OrdersManagementDataModel; +using OrdersManagementDataModel.Services; using SytelineSaAppEfDataModel; using SytelineSaAppEfDataModel.Services; using FaKrosnoMappingProfile = FaKrosnoEfDataModel.MappingProfile; using SytelineSaAppMappingProfile = SytelineSaAppEfDataModel.MappingProfile; +using OrdersManagementMappingProfile = OrdersManagementDataModel.MappingProfile; var builder = WebApplication.CreateBuilder(args); -var configuration = builder.Configuration; -// Add services to the container. + builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("FaKrosnoConnection"))); builder.Services.AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("SytelineSaAppConnection"))); +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("OrdersManagementConnection"))); + +builder.Services.Configure(builder.Configuration.GetSection("EmailSettings")); +builder.Services.Configure(builder.Configuration.GetSection("JobSettings")); builder.WebHost.UseUrls("http://*:5555"); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(config => { config.Title = "FaKrosnoApi"; config.Version = "v1"; }); +builder.Services.AddHangfire(config => config + .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(builder.Configuration.GetConnectionString("OrdersManagementConnection"), new SqlServerStorageOptions + { + CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), + SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), + QueuePollInterval = TimeSpan.Zero, + UseRecommendedIsolationLevel = true, + DisableGlobalLocks = true + })); +builder.Services.AddHangfireServer(); + // Configure AutoMapper -builder.Services.AddAutoMapper(typeof(FaKrosnoMappingProfile), typeof(SytelineSaAppMappingProfile)); +builder.Services.AddAutoMapper(typeof(FaKrosnoMappingProfile), typeof(SytelineSaAppMappingProfile), + typeof(OrdersManagementMappingProfile)); // Configure JWT Authentication builder.Services.AddAuthentication(options => @@ -55,15 +80,15 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddHostedService(); var app = builder.Build(); -// Configure the HTTP request pipeline. -//if (app.Environment.IsDevelopment()) -//{ - app.UseOpenApi(); // Serwuje dokument OpenAPI - app.UseSwaggerUi(); // Dodaje interfejs u�ytkownika Swagger -//} +app.UseOpenApi(); +app.UseSwaggerUi(); app.UseHttpsRedirection(); @@ -72,4 +97,13 @@ app.UseAuthorization(); app.MapControllers(); +app.UseHangfireDashboard(); + +// var scopeFactory = app.Services.GetRequiredService(); +// using (var scope = scopeFactory.CreateScope()) +// { +// var scheduledJob = scope.ServiceProvider.GetRequiredService(); +// scheduledJob.Start(); +// } + app.Run(); \ No newline at end of file diff --git a/FaKrosnoApi/Services/EmailService.cs b/FaKrosnoApi/Services/EmailService.cs new file mode 100644 index 0000000..ffbca0d --- /dev/null +++ b/FaKrosnoApi/Services/EmailService.cs @@ -0,0 +1,30 @@ +using System.Net; +using System.Net.Mail; +using FaKrosnoApi.Models; +using Microsoft.Extensions.Options; + +namespace FaKrosnoApi.Services; + +public class EmailService(IOptions emailSettings) : IEmailService +{ + private readonly EmailSettingsModel _emailSettings = emailSettings.Value; + + public void SendEmail(string subject, string body) + { + using var smtpClient = new SmtpClient(_emailSettings.SmtpServer, _emailSettings.Port); + smtpClient.EnableSsl = true; + smtpClient.UseDefaultCredentials = false; + smtpClient.Credentials = new NetworkCredential(_emailSettings.SenderEmail, _emailSettings.SenderPassword); + + var mailMessage = new MailMessage + { + From = new MailAddress(_emailSettings.SenderEmail), + Subject = subject, + Body = body + }; + + mailMessage.To.Add(_emailSettings.RecipientEmail); + + smtpClient.Send(mailMessage); + } +} \ No newline at end of file diff --git a/FaKrosnoApi/Services/IEmailService.cs b/FaKrosnoApi/Services/IEmailService.cs new file mode 100644 index 0000000..ab40272 --- /dev/null +++ b/FaKrosnoApi/Services/IEmailService.cs @@ -0,0 +1,6 @@ +namespace FaKrosnoApi.Services; + +public interface IEmailService +{ + void SendEmail(string subject, string body); +} \ No newline at end of file diff --git a/FaKrosnoApi/Services/IScheduleJobService.cs b/FaKrosnoApi/Services/IScheduleJobService.cs new file mode 100644 index 0000000..1ac70c9 --- /dev/null +++ b/FaKrosnoApi/Services/IScheduleJobService.cs @@ -0,0 +1,6 @@ +namespace FaKrosnoApi.Services; + +public interface IScheduleJobService +{ + Task ExecuteAsync(); +} \ No newline at end of file diff --git a/FaKrosnoApi/Services/ScheduleJobService.cs b/FaKrosnoApi/Services/ScheduleJobService.cs new file mode 100644 index 0000000..1f841f6 --- /dev/null +++ b/FaKrosnoApi/Services/ScheduleJobService.cs @@ -0,0 +1,29 @@ +using System.Text; +using SytelineSaAppEfDataModel.Dtos; +using SytelineSaAppEfDataModel.Services; + +namespace FaKrosnoApi.Services; + +public class ScheduleJobService(IEmailService emailService, IServiceScopeFactory scopeFactory) : IScheduleJobService +{ + public async Task ExecuteAsync() + { + using var scope = scopeFactory.CreateScope(); + IEdiCustomerOrderService ediCustomerOrderService = scope.ServiceProvider.GetRequiredService(); + IEnumerable missingOrders = + (await ediCustomerOrderService.FindMissingOrders(new DateTime(2025, 2, 5))).ToList(); + + if (missingOrders.Any()) + { + StringBuilder result = new StringBuilder(); + result.AppendLine("Znaleziono brakujące zamówienia w bazie 'edi_co':"); + + foreach (EdiCustomerOrderTranslateDto missingOrder in missingOrders) + { + result.AppendLine($"- {missingOrder.EdiCoCoNum}"); + } + + emailService.SendEmail("Znaleziono brakujące zamówienia!", result.ToString()); + } + } +} \ No newline at end of file diff --git a/FaKrosnoApi/Services/TimedHostedService.cs b/FaKrosnoApi/Services/TimedHostedService.cs new file mode 100644 index 0000000..9b8601a --- /dev/null +++ b/FaKrosnoApi/Services/TimedHostedService.cs @@ -0,0 +1,32 @@ +using SytelineSaAppEfDataModel.Services; + +namespace FaKrosnoApi.Services; + +public class TimedHostedService(IServiceScopeFactory scopeFactory) : IHostedService, IDisposable +{ + private Timer? _timer; + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(30)); + return Task.CompletedTask; + } + + private void DoWork(object? state) + { + using var scope = scopeFactory.CreateScope(); + var scheduledJob = scope.ServiceProvider.GetRequiredService(); + scheduledJob.ExecuteAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } +} \ No newline at end of file diff --git a/FaKrosnoApi/appsettings.json b/FaKrosnoApi/appsettings.json index 10d8d8d..9035997 100644 --- a/FaKrosnoApi/appsettings.json +++ b/FaKrosnoApi/appsettings.json @@ -1,7 +1,8 @@ { "ConnectionStrings": { "FaKrosnoConnection": "Server=192.168.0.7;Database=fakrosnotest;User Id=sa;Password=Tetum#2021!;TrustServerCertificate=true", - "SytelineSaAppConnection": "Server=192.168.0.7;Database=SL_PRODTEST_SA_APP;User Id=sa;Password=Tetum#2021!;TrustServerCertificate=true" + "SytelineSaAppConnection": "Server=192.168.0.7;Database=SL_PROD_SA_APP;User Id=sa;Password=Tetum#2021!;TrustServerCertificate=true", + "OrdersManagementConnection": "Server=192.168.0.7;Database=OrdersManagement;User Id=sa;Password=Tetum#2021!;TrustServerCertificate=true" }, "Logging": { "LogLevel": { @@ -16,5 +17,16 @@ }, "Host": { "Urls": "http://0.0.0.0:5555" + }, + "EmailSettings": { + "SmtpServer": "poczta.fakrosno.pl", + "Port": 587, + "SenderEmail": "edi@fakrosno.pl", + "SenderPassword": "F@Krosno2014", + "RecipientEmail": "piotr.kus@fakrosno.pl", + "UseSsl": false + }, + "JobSettings": { + "QueryIntervalMinutes": 30 } } diff --git a/FaKrosnoEfDataModel/Queries/ScheduleOrderQueries.cs b/FaKrosnoEfDataModel/Queries/ScheduleOrderQueries.cs index 1e699eb..85044f2 100644 --- a/FaKrosnoEfDataModel/Queries/ScheduleOrderQueries.cs +++ b/FaKrosnoEfDataModel/Queries/ScheduleOrderQueries.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using FaKrosnoEfDataModel.Entities; +using FaKrosnoEfDataModel.Entities; using Microsoft.EntityFrameworkCore; namespace FaKrosnoEfDataModel.Queries diff --git a/HangfireApi/HangfireApi.csproj b/HangfireApi/HangfireApi.csproj deleted file mode 100644 index 867b55b..0000000 --- a/HangfireApi/HangfireApi.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - diff --git a/HangfireApi/HangfireApi.http b/HangfireApi/HangfireApi.http deleted file mode 100644 index ea1a2ba..0000000 --- a/HangfireApi/HangfireApi.http +++ /dev/null @@ -1,6 +0,0 @@ -@HangfireApi_HostAddress = http://localhost:5237 - -GET {{HangfireApi_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/HangfireApi/Program.cs b/HangfireApi/Program.cs deleted file mode 100644 index 0b7a1b6..0000000 --- a/HangfireApi/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Hangfire; -using Hangfire.SqlServer; -using HangfireApi.Controllers; - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddHangfire(configuration => configuration - .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) - .UseSimpleAssemblyNameTypeSerializer() - .UseRecommendedSerializerSettings() - .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions - { - CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), - SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), - QueuePollInterval = TimeSpan.Zero, - UseRecommendedIsolationLevel = true, - DisableGlobalLocks = true - })); - -builder.Services.AddControllers(); -builder.Services.AddHangfireServer(); -builder.Services.AddAuthorization(); - -builder.Services.AddSingleton(provider => - provider.GetRequiredService().GetService(typeof(JobStorage)) as JobStorage ?? - throw new InvalidOperationException()); - -var app = builder.Build(); - -if (app.Environment.IsDevelopment()) -{ - app.UseDeveloperExceptionPage(); -} - -app.UseRouting(); -app.UseAuthorization(); - -app.MapControllers(); -app.MapHangfireDashboard(); - -await ((IApplicationBuilder)app).ApplicationServices.GetRequiredService().RunJobs(); - -await app.RunAsync(); \ No newline at end of file diff --git a/HangfireApi/Properties/launchSettings.json b/HangfireApi/Properties/launchSettings.json deleted file mode 100644 index 76fdf15..0000000 --- a/HangfireApi/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:24492", - "sslPort": 44367 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5237", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7273;http://localhost:5237", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/HangfireApi/appsettings.json b/HangfireApi/appsettings.json deleted file mode 100644 index 9d5551f..0000000 --- a/HangfireApi/appsettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ConnectionStrings": { - "HangfireConnection": "Server=tcp:fakrosno-hangfire.database.windows.net,1433;Initial Catalog=FaKrosnoHangfire;Persist Security Info=False;User ID=fakrosno_hangfire@trentblaugranagmail.onmicrosoft.com;Password=juhrot-zigQuz-6jydpu;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Authentication=\"Active Directory Password\";" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/OrdersManagement/Components/Pages/Admin/Scheduler.razor b/OrdersManagement/Components/Pages/Admin/Scheduler.razor new file mode 100644 index 0000000..4f81a5b --- /dev/null +++ b/OrdersManagement/Components/Pages/Admin/Scheduler.razor @@ -0,0 +1,51 @@ +@page "/Admin/Scheduler" +@using OrdersManagementDataModel.Dtos +@using Syncfusion.Blazor.Grids +@using Syncfusion.Blazor.Inputs +@using Syncfusion.Blazor.Buttons +@inject HangfireService HangfireService + +

Zarządzanie Zadaniami

+ +
+ + + + +
+ + + + + + + @* *@ + @* *@ + + + +@code { + private List Tasks { get; set; } = new(); + private TaskSchedulerDto NewTask { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + await LoadTasks(); + } + + private async Task AddTask() + { + var response = await HangfireService.AddTaskSchedulerAsync(NewTask); + if (response == 1) + { + NewTask = new TaskSchedulerDto(); // Reset form + await LoadTasks(); // Refresh list + } + } + + private async Task LoadTasks() + { + Tasks = (await HangfireService.GetTaskSchedulersAsync() ?? Array.Empty()).ToList(); + } + +} \ No newline at end of file diff --git a/OrdersManagement/OrdersManagement.csproj b/OrdersManagement/OrdersManagement.csproj index c76f6c2..234e103 100644 --- a/OrdersManagement/OrdersManagement.csproj +++ b/OrdersManagement/OrdersManagement.csproj @@ -19,6 +19,7 @@ + diff --git a/OrdersManagement/Program.cs b/OrdersManagement/Program.cs index 60ef0bd..3c4ee61 100644 --- a/OrdersManagement/Program.cs +++ b/OrdersManagement/Program.cs @@ -8,8 +8,8 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddSyncfusionBlazor(); builder.Services.AddBlazorBootstrap(); -string apiUrl = builder.Configuration["ApiUrl"] ?? "http://localhost:5555"; // Ustawienie na adres twojego API -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiUrl) }); +string faKrosnoApiUrl = builder.Configuration["FaKrosnoApiUrl"] ?? "http://localhost:5555"; // Ustawienie na adres twojego API +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(faKrosnoApiUrl) }); // Add services to the container. builder.Services.AddRazorComponents() @@ -19,6 +19,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); diff --git a/OrdersManagement/Services/HangfireService.cs b/OrdersManagement/Services/HangfireService.cs new file mode 100644 index 0000000..a36eddc --- /dev/null +++ b/OrdersManagement/Services/HangfireService.cs @@ -0,0 +1,22 @@ +using OrdersManagementDataModel.Dtos; + +namespace OrdersManagement.Services; + +public class HangfireService(HttpClient httpClient) +{ + public async Task?> GetTaskSchedulersAsync() + { + return await httpClient.GetFromJsonAsync>("api/HangfireJobs/GetTasks"); + } + + public async Task GetTaskSchedulerAsync(Guid id) + { + return await httpClient.GetFromJsonAsync($"api/HangfireJobs/{id}"); + } + + public async Task AddTaskSchedulerAsync(TaskSchedulerDto taskSchedulerDto) + { + HttpResponseMessage responseMessage = await httpClient.PostAsJsonAsync("api/HangfireJobs/AddTask", taskSchedulerDto); + return responseMessage.IsSuccessStatusCode ? 1 : 0; + } +} \ No newline at end of file diff --git a/AzureDataModel/Dtos/TaskScheduler.cs b/OrdersManagementDataModel/Dtos/TaskSchedulerDto.cs similarity index 52% rename from AzureDataModel/Dtos/TaskScheduler.cs rename to OrdersManagementDataModel/Dtos/TaskSchedulerDto.cs index f7ec616..56ac0b8 100644 --- a/AzureDataModel/Dtos/TaskScheduler.cs +++ b/OrdersManagementDataModel/Dtos/TaskSchedulerDto.cs @@ -1,4 +1,6 @@ -namespace AzureDataModel.Dtos; +using Hangfire.Storage; + +namespace OrdersManagementDataModel.Dtos; public class TaskSchedulerDto { @@ -7,4 +9,8 @@ public class TaskSchedulerDto public string Path { get; set; } public string CronOptions { get; set; } public DateTime CreateDate { get; set; } + public DateTime ActiveFrom { get; set; } + public DateTime? ActiveUntil { get; set; } + + public JobData? JobDetails { get; set; } } \ No newline at end of file diff --git a/AzureDataModel/Entities/TaskScheduler.cs b/OrdersManagementDataModel/Entities/TaskScheduler.cs similarity index 66% rename from AzureDataModel/Entities/TaskScheduler.cs rename to OrdersManagementDataModel/Entities/TaskScheduler.cs index b1f14dd..08f9fe8 100644 --- a/AzureDataModel/Entities/TaskScheduler.cs +++ b/OrdersManagementDataModel/Entities/TaskScheduler.cs @@ -1,4 +1,4 @@ -namespace AzureDataModel.Entities; +namespace OrdersManagementDataModel.Entities; public class TaskScheduler { @@ -8,4 +8,6 @@ public class TaskScheduler public string Path { get; set; } public string CronOptions { get; set; } public DateTime CreateDate { get; set; } + public DateTime ActiveFrom { get; set; } + public DateTime? ActiveUntil { get; set; } } \ No newline at end of file diff --git a/AzureDataModel/MappingProfile.cs b/OrdersManagementDataModel/MappingProfile.cs similarity index 69% rename from AzureDataModel/MappingProfile.cs rename to OrdersManagementDataModel/MappingProfile.cs index 2c2918c..2cd2810 100644 --- a/AzureDataModel/MappingProfile.cs +++ b/OrdersManagementDataModel/MappingProfile.cs @@ -1,7 +1,7 @@ using AutoMapper; -using AzureDataModel.Dtos; +using OrdersManagementDataModel.Dtos; -namespace AzureDataModel; +namespace OrdersManagementDataModel; public class MappingProfile : Profile { diff --git a/AzureDataModel/AzureDataModel.csproj b/OrdersManagementDataModel/OrdersManagementDataModel.csproj similarity index 91% rename from AzureDataModel/AzureDataModel.csproj rename to OrdersManagementDataModel/OrdersManagementDataModel.csproj index 7a165ce..8e0703a 100644 --- a/AzureDataModel/AzureDataModel.csproj +++ b/OrdersManagementDataModel/OrdersManagementDataModel.csproj @@ -8,6 +8,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,7 +24,7 @@ - + Always diff --git a/OrdersManagementDataModel/OrdersManagementDbContext.cs b/OrdersManagementDataModel/OrdersManagementDbContext.cs new file mode 100644 index 0000000..d3c0197 --- /dev/null +++ b/OrdersManagementDataModel/OrdersManagementDbContext.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using TaskScheduler = OrdersManagementDataModel.Entities.TaskScheduler; + +namespace OrdersManagementDataModel; + +public class OrdersManagementDbContext : DbContext +{ + public OrdersManagementDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + var connectionString = configuration.GetConnectionString("OrdersManagementConnection"); + optionsBuilder.UseSqlServer(connectionString, options => options.CommandTimeout(300)); + } + + public DbSet TaskSchedulers { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.ToTable("TaskScheduler"); + entity.Property(e => e.RowPointer).HasDefaultValueSql("newid()"); + entity.Property(e => e.CreateDate).HasDefaultValueSql("getdate()"); + entity.Property(e => e.ActiveFrom).HasDefaultValueSql("getdate()"); + entity.Property(e => e.ActiveUntil).IsRequired(false); }); + } +} \ No newline at end of file diff --git a/OrdersManagementDataModel/Queries/OrdersManagementQueries.cs b/OrdersManagementDataModel/Queries/OrdersManagementQueries.cs new file mode 100644 index 0000000..66a9c87 --- /dev/null +++ b/OrdersManagementDataModel/Queries/OrdersManagementQueries.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using OrdersManagementDataModel.Dtos; + +namespace OrdersManagementDataModel.Queries; + +public static class OrdersManagementQueries +{ + public static readonly Func> GetSchedulers = + EF.CompileQuery((OrdersManagementDbContext context) => context.TaskSchedulers.Select(x => new TaskSchedulerDto + { + Id = x.Id, + Name = x.Name, + Path = x.Path, + CronOptions = x.CronOptions, + CreateDate = x.CreateDate, + ActiveFrom = x.ActiveFrom, + ActiveUntil = x.ActiveUntil + })); +} \ No newline at end of file diff --git a/OrdersManagementDataModel/Services/ITaskSchedulerService.cs b/OrdersManagementDataModel/Services/ITaskSchedulerService.cs new file mode 100644 index 0000000..5a50a64 --- /dev/null +++ b/OrdersManagementDataModel/Services/ITaskSchedulerService.cs @@ -0,0 +1,12 @@ +using OrdersManagementDataModel.Dtos; + +namespace OrdersManagementDataModel.Services; + +public interface ITaskSchedulerService +{ + Task> GetTaskSchedulers(); + Task GetTaskSchedulerByTaskName(string taskName); + Task AddTaskScheduler(TaskSchedulerDto taskSchedulerDto); + Task UpdateTaskScheduler(TaskSchedulerDto taskSchedulerDto); + Task DeleteTaskScheduler(Guid id); +} \ No newline at end of file diff --git a/AzureDataModel/Services/TaskSchedulerService.cs b/OrdersManagementDataModel/Services/TaskSchedulerService.cs similarity index 77% rename from AzureDataModel/Services/TaskSchedulerService.cs rename to OrdersManagementDataModel/Services/TaskSchedulerService.cs index 8af9b20..ffdc6e9 100644 --- a/AzureDataModel/Services/TaskSchedulerService.cs +++ b/OrdersManagementDataModel/Services/TaskSchedulerService.cs @@ -1,15 +1,19 @@ using AutoMapper; -using AzureDataModel.Dtos; using Microsoft.EntityFrameworkCore; -using TaskScheduler = AzureDataModel.Entities.TaskScheduler; +using OrdersManagementDataModel.Dtos; +using OrdersManagementDataModel.Queries; +using TaskScheduler = OrdersManagementDataModel.Entities.TaskScheduler; -namespace AzureDataModel.Services; +namespace OrdersManagementDataModel.Services; -public class TaskSchedulerService(AzureDbContext context, IMapper mapper) : ITaskSchedulerService +public class TaskSchedulerService(OrdersManagementDbContext context, IMapper mapper) : ITaskSchedulerService { public async Task> GetTaskSchedulers() { - return await context.TaskSchedulers.Select(x => mapper.Map(x)).ToListAsync(); + List taskSchedulers = + (await Task.FromResult(OrdersManagementQueries.GetSchedulers(context))).ToList(); + + return taskSchedulers; } public async Task GetTaskSchedulerById(Guid id) diff --git a/OrdersManagementDataModel/appsettings_OrdersManagement.json b/OrdersManagementDataModel/appsettings_OrdersManagement.json new file mode 100644 index 0000000..fe6d9f8 --- /dev/null +++ b/OrdersManagementDataModel/appsettings_OrdersManagement.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "OrdersManagementConnection": "\"OrdersManagementConnection\": \"Server=192.168.0.7;Database=OrdersManagement;User Id=sa;Password=Tetum#2021!;TrustServerCertificate=true\";" + } +} \ No newline at end of file diff --git a/SytelineSaAppEfDataModel/Services/EdiCustomerOrderService.cs b/SytelineSaAppEfDataModel/Services/EdiCustomerOrderService.cs index 3285649..a958d33 100644 --- a/SytelineSaAppEfDataModel/Services/EdiCustomerOrderService.cs +++ b/SytelineSaAppEfDataModel/Services/EdiCustomerOrderService.cs @@ -78,6 +78,25 @@ namespace SytelineSaAppEfDataModel.Services return ediCustomerOrder; } + + public async Task> FindMissingOrders(DateTime startDate) + { + IList ediCustomerOrderTranslates = await context.EdiCustomerOrderTranslates + .Where(x => x.CreatedDate >= startDate).ToListAsync(); + + var ediOrderNumbers = ediCustomerOrderTranslates.Select(x => x.EdiCoCoNum).ToList(); + var ediOrderNumbersFromEdiCo = (await context.EdiCustomerOrders.ToListAsync()) + .Where(x => ediOrderNumbers.Contains(x.CustomerOrderNumber)).Select(x => x.CustomerOrderNumber) + .ToList(); + + ISet missingNumbers = ediOrderNumbers.Except(ediOrderNumbersFromEdiCo).ToHashSet(); + + IEnumerable missingOrders = + (await context.EdiCustomerOrderTranslates.ToListAsync()) + .Where(x => missingNumbers.Contains(x.EdiCoCoNum)).Select(mapper.Map); + + return missingOrders; + } public async Task SendOrderToSyteline(Guid customerOrderNumber) { diff --git a/SytelineSaAppEfDataModel/Services/IEdiCustomerOrderService.cs b/SytelineSaAppEfDataModel/Services/IEdiCustomerOrderService.cs index acfcdff..1c61e65 100644 --- a/SytelineSaAppEfDataModel/Services/IEdiCustomerOrderService.cs +++ b/SytelineSaAppEfDataModel/Services/IEdiCustomerOrderService.cs @@ -12,6 +12,7 @@ namespace SytelineSaAppEfDataModel.Services Task> GetAll(); Task> GetByDate(DateTime date); Task GetByOrderNumber(Guid orderNumber); + Task> FindMissingOrders(DateTime startDate); Task SendOrderToSyteline(Guid customerOrderNumber); } }