< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.TransactionJobManagementController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/TransactionJobManagementController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 162
Coverable lines: 162
Total lines: 320
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 32
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetSchedules()0%620%
GetSchedule()0%620%
CreateSchedule()0%620%
UpdateSchedule()0%2040%
DeleteSchedule()100%210%
ActivateSchedule()100%210%
DeactivateSchedule()100%210%
TriggerSchedule()0%156120%
TriggerManual()0%2040%
GetExecutions()0%2040%
GetExecutionDetails()0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/TransactionJobManagementController.cs

#LineLine coverage
 1using Hangfire;
 2using Hangfire.Common;
 3using Hangfire.States;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Mvc;
 6using Microsoft.EntityFrameworkCore;
 7using OzonDomains.Models;
 8using OzonRepositories.Context;
 9using Services.BackgroundJobsServices;
 10using Services.HangfireService;
 11
 12namespace OzonOrdersWeb.Areas.Studio2.Controllers;
 13
 14[Authorize(Roles = "Admin")]
 15[Area("Studio2")]
 16[Route("api/transaction-jobs")]
 17[ApiController]
 18public class TransactionJobManagementController : ControllerBase
 19{
 20    private readonly IBackgroundJobScheduleService _scheduleService;
 21    private readonly IBackgroundJobLoggingService _loggingService;
 22    private readonly IAutomatedTransactionProcessingService _processingService;
 23    private readonly IBackgroundJobClient _backgroundJobClient;
 24    private readonly ApplicationDbContext _context;
 25
 026    public TransactionJobManagementController(
 027        IBackgroundJobScheduleService scheduleService,
 028        IBackgroundJobLoggingService loggingService,
 029        IAutomatedTransactionProcessingService processingService,
 030        IBackgroundJobClient backgroundJobClient,
 031        ApplicationDbContext context)
 32    {
 033        _scheduleService = scheduleService;
 034        _loggingService = loggingService;
 035        _processingService = processingService;
 036        _backgroundJobClient = backgroundJobClient;
 037        _context = context;
 038    }
 39
 40    // GET: api/transaction-jobs/schedules
 41    [HttpGet("schedules")]
 42    public async Task<IActionResult> GetSchedules([FromQuery] bool activeOnly = false)
 43    {
 044        var schedules = activeOnly
 045            ? await _scheduleService.GetActiveSchedulesAsync()
 046            : await _scheduleService.GetAllSchedulesAsync();
 47
 048        return Ok(schedules);
 049    }
 50
 51    // GET: api/background-jobs/schedules/{id}
 52    [HttpGet("schedules/{id}")]
 53    public async Task<IActionResult> GetSchedule(int id)
 54    {
 055        var schedule = await _scheduleService.GetScheduleByIdAsync(id);
 056        if (schedule == null)
 057            return NotFound(new { error = "Schedule not found" });
 58
 059        return Ok(schedule);
 060    }
 61
 62    // POST: api/background-jobs/schedules
 63    [HttpPost("schedules")]
 64    public async Task<IActionResult> CreateSchedule([FromBody] BackgroundJobSchedule schedule)
 65    {
 066        if (!ModelState.IsValid)
 067            return BadRequest(ModelState);
 68
 69        try
 70        {
 071            var id = await _scheduleService.CreateScheduleAsync(schedule);
 072            var created = await _scheduleService.GetScheduleByIdAsync(id);
 073            return CreatedAtAction(nameof(GetSchedule), new { id }, created);
 74        }
 075        catch (Exception ex)
 76        {
 077            return BadRequest(new { error = ex.Message });
 78        }
 079    }
 80
 81    // PUT: api/background-jobs/schedules/{id}
 82    [HttpPut("schedules/{id}")]
 83    public async Task<IActionResult> UpdateSchedule(int id, [FromBody] BackgroundJobSchedule schedule)
 84    {
 085        if (id != schedule.Id)
 086            return BadRequest(new { error = "ID mismatch" });
 87
 088        if (!ModelState.IsValid)
 089            return BadRequest(ModelState);
 90
 91        try
 92        {
 093            await _scheduleService.UpdateScheduleAsync(schedule);
 094            return NoContent();
 95        }
 096        catch (InvalidOperationException ex)
 97        {
 098            return NotFound(new { error = ex.Message });
 99        }
 0100        catch (Exception ex)
 101        {
 0102            return BadRequest(new { error = ex.Message });
 103        }
 0104    }
 105
 106    // DELETE: api/background-jobs/schedules/{id}
 107    [HttpDelete("schedules/{id}")]
 108    public async Task<IActionResult> DeleteSchedule(int id)
 109    {
 110        try
 111        {
 0112            await _scheduleService.DeleteScheduleAsync(id);
 0113            return NoContent();
 114        }
 0115        catch (InvalidOperationException ex)
 116        {
 0117            return NotFound(new { error = ex.Message });
 118        }
 0119        catch (Exception ex)
 120        {
 0121            return BadRequest(new { error = ex.Message });
 122        }
 0123    }
 124
 125    // POST: api/background-jobs/schedules/{id}/activate
 126    [HttpPost("schedules/{id}/activate")]
 127    public async Task<IActionResult> ActivateSchedule(int id)
 128    {
 129        try
 130        {
 0131            await _scheduleService.ActivateScheduleAsync(id);
 0132            return Ok(new { message = "Schedule activated successfully" });
 133        }
 0134        catch (InvalidOperationException ex)
 135        {
 0136            return NotFound(new { error = ex.Message });
 137        }
 0138        catch (Exception ex)
 139        {
 0140            return BadRequest(new { error = ex.Message });
 141        }
 0142    }
 143
 144    // POST: api/background-jobs/schedules/{id}/deactivate
 145    [HttpPost("schedules/{id}/deactivate")]
 146    public async Task<IActionResult> DeactivateSchedule(int id)
 147    {
 148        try
 149        {
 0150            await _scheduleService.DeactivateScheduleAsync(id);
 0151            return Ok(new { message = "Schedule deactivated successfully" });
 152        }
 0153        catch (InvalidOperationException ex)
 154        {
 0155            return NotFound(new { error = ex.Message });
 156        }
 0157        catch (Exception ex)
 158        {
 0159            return BadRequest(new { error = ex.Message });
 160        }
 0161    }
 162
 163    // POST: api/background-jobs/trigger/{scheduleId}
 164    [HttpPost("trigger/{scheduleId}")]
 165    public async Task<IActionResult> TriggerSchedule(int scheduleId, [FromQuery] string? jobId = null)
 166    {
 0167        var schedule = await _scheduleService.GetScheduleByIdAsync(scheduleId);
 0168        if (schedule == null)
 0169            return NotFound(new { error = "Schedule not found" });
 170
 0171        if (!schedule.OzonClientId.HasValue)
 0172            return BadRequest(new { error = "Schedule has no Ozon Client configured" });
 173
 174        try
 175        {
 0176            var signalRJobId = string.IsNullOrWhiteSpace(jobId)
 0177                ? $"manual-job-{scheduleId}-{Guid.NewGuid():N}"[..24]
 0178                : jobId.Trim();
 179
 0180            var daysBack = ReportDeliveryDateFilter.TransactionJobDaysBack;
 0181            var hangfireJob = schedule.JobType switch
 0182            {
 0183                "CustomerReturn" => Job.FromExpression<IAutomatedTransactionProcessingService>(service =>
 0184                    service.ProcessCustomerReturnManualAsync(
 0185                        schedule.OzonClientId.Value,
 0186                        daysBack,
 0187                        signalRJobId,
 0188                        CancellationToken.None)),
 0189                _ => Job.FromExpression<IAutomatedTransactionProcessingService>(service =>
 0190                    service.ProcessShippedToCommissionerManualAsync(
 0191                        schedule.OzonClientId.Value,
 0192                        daysBack,
 0193                        signalRJobId,
 0194                        CancellationToken.None))
 0195            };
 0196            var hangfireJobId = _backgroundJobClient.Create(hangfireJob, new EnqueuedState(HangfireQueueNames.Main));
 197
 0198            return Ok(new { message = "Job triggered successfully", jobId = signalRJobId, hangfireJobId });
 199        }
 0200        catch (Exception ex)
 201        {
 0202            return BadRequest(new { error = ex.InnerException?.Message ?? ex.Message });
 203        }
 0204    }
 205
 206    // POST: api/background-jobs/trigger-manual
 207    [HttpPost("trigger-manual")]
 208    public async Task<IActionResult> TriggerManual([FromBody] ManualTriggerRequest request)
 209    {
 0210        await Task.CompletedTask;
 0211        if (!ModelState.IsValid)
 0212            return BadRequest(ModelState);
 213
 214        try
 215        {
 216            // Generate unique job ID for SignalR tracking
 0217            var jobId = $"manual-job-{Guid.NewGuid().ToString().Substring(0, 8)}";
 218
 0219            var daysBack = ReportDeliveryDateFilter.TransactionJobDaysBack;
 0220            var hangfireJob = string.Equals(request.JobType, "CustomerReturn", StringComparison.OrdinalIgnoreCase)
 0221                ? Job.FromExpression<IAutomatedTransactionProcessingService>(service =>
 0222                    service.ProcessCustomerReturnManualAsync(
 0223                        request.OzonClientId,
 0224                        daysBack,
 0225                        jobId,
 0226                        CancellationToken.None))
 0227                : Job.FromExpression<IAutomatedTransactionProcessingService>(service =>
 0228                    service.ProcessShippedToCommissionerManualAsync(
 0229                        request.OzonClientId,
 0230                        daysBack,
 0231                        jobId,
 0232                        CancellationToken.None));
 0233            _backgroundJobClient.Create(hangfireJob, new EnqueuedState(HangfireQueueNames.Main));
 234
 0235            return Ok(new { message = "Job triggered successfully", jobId });
 236        }
 0237        catch (Exception ex)
 238        {
 0239            return BadRequest(new { error = ex.Message });
 240        }
 0241    }
 242
 243    // GET: api/background-jobs/executions
 244    [HttpGet("executions")]
 245    public async Task<IActionResult> GetExecutions(
 246        [FromQuery] string? status = null,
 247        [FromQuery] string? triggerType = null,
 248        [FromQuery] int page = 1,
 249        [FromQuery] int pageSize = 20)
 250    {
 0251        var query = _context.BackgroundJobExecutions
 0252            .Include(e => e.Schedule)
 0253            .AsQueryable();
 254
 0255        if (!string.IsNullOrWhiteSpace(status))
 0256            query = query.Where(e => e.Status == status);
 257
 0258        if (!string.IsNullOrWhiteSpace(triggerType))
 0259            query = query.Where(e => e.TriggerType == triggerType);
 260
 0261        var total = await query.CountAsync();
 0262        var executions = await query
 0263            .OrderByDescending(e => e.StartedAt)
 0264            .Skip((page - 1) * pageSize)
 0265            .Take(pageSize)
 0266            .ToListAsync();
 267
 0268        return Ok(new
 0269        {
 0270            total,
 0271            page,
 0272            pageSize,
 0273            items = executions
 0274        });
 0275    }
 276
 277    // GET: api/background-jobs/executions/{id}
 278    [HttpGet("executions/{id}")]
 279    public async Task<IActionResult> GetExecutionDetails(long id)
 280    {
 0281        var execution = await _loggingService.GetExecutionAsync(id);
 0282        if (execution == null)
 0283            return NotFound(new { error = "Execution not found" });
 284
 0285        var steps = await _loggingService.GetExecutionStepsAsync(id);
 286
 0287        var reportLoads = await _context.BackgroundReportLoads
 0288            .Include(r => r.OzonClient)
 0289            .Where(r => r.ExecutionId == id)
 0290            .ToListAsync();
 291
 0292        var orderMatches = await _context.BackgroundOrderMatches
 0293            .Include(m => m.Order)
 0294            .ThenInclude(o => o!.AppStatus)
 0295            .Where(m => m.ExecutionId == id)
 0296            .ToListAsync();
 297
 0298        var transactionResults = await _context.BackgroundTransactionResults
 0299            .Include(r => r.Order)
 0300            .Include(r => r.Transaction)
 0301            .Where(r => r.ExecutionId == id)
 0302            .ToListAsync();
 303
 0304        return Ok(new
 0305        {
 0306            execution,
 0307            steps,
 0308            reportLoads,
 0309            orderMatches,
 0310            transactionResults
 0311        });
 0312    }
 313}
 314
 315public class ManualTriggerRequest
 316{
 317    public int OzonClientId { get; set; }
 318    public int DaysBack { get; set; } = 1;
 319    public string JobType { get; set; } = "ShippedToCommissioner";
 320}