using System.Text.Json; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using WiBLL.Common; using WiDAL.CustomCode.LineStation; using WiDAL.CustomCode.WorkstationAvailability; using WiDAL.DTOs; namespace WiBLL.WorkstationAvailability; public class WorkstationAvailabilityBLL : IWorkstationAvailabilityBLL { private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web); private readonly IWorkstationAvailabilityDAL _dal; private readonly AutoNumber _autoNumber; private readonly ILineStationDAL _lineStationDal; private readonly IWiHubNotifier _hubNotifier; public WorkstationAvailabilityBLL(IWorkstationAvailabilityDAL dal, AutoNumber autoNumber, ILineStationDAL lineStationDal, IWiHubNotifier hubNotifier) { _dal = dal; _autoNumber = autoNumber; _lineStationDal = lineStationDal; _hubNotifier = hubNotifier; } // The line group is where Line Digital Twin actually listens (see IWiHubNotifier. // NotifyWorkstationOutageChangedAsync) — a workstation can be unmapped to any line // (station not yet placed on a line layout), in which case there's nothing to notify. private async Task NotifyLineIfMappedAsync(int workstationId, LoginDTO login, CancellationToken ct) { var stationsJson = await _lineStationDal.GetCurrentByWorkstationAsync(workstationId, login, ct).ConfigureAwait(false); var stations = JsonSerializer.Deserialize>(stationsJson, JsonOpts) ?? []; var productionLineId = stations.FirstOrDefault()?.ProductionLineId; if (productionLineId is not null) await _hubNotifier.NotifyWorkstationOutageChangedAsync(productionLineId.Value, workstationId, ct).ConfigureAwait(false); } public async Task GetAvailabilityAsync(int workstationId, DateTime rangeFrom, DateTime rangeTo, LoginDTO login, CancellationToken ct) => await _dal.GetAvailabilityAsync(workstationId, rangeFrom, rangeTo, login, ct).ConfigureAwait(false); public async Task ReportOutageAsync(WorkstationOutageDTO dto, LoginDTO login, CancellationToken ct) { GB5Trace.Step("validate-workstation-outage", new { dto.WorkstationId, dto.OutageType }); if (dto.WorkstationId == 0) throw new InvalidOperationException("WorkstationId is required."); if (dto.OutageType is not (0 or 1)) throw new InvalidOperationException("OutageType must be 0 (Breakdown) or 1 (Planned Maintenance)."); if (dto.StartedOn == default) dto.StartedOn = DateTime.UtcNow; var an = await _autoNumber.GetNumberAsync(1, "WORKSTATIONOUTAGE", login).ConfigureAwait(false); dto.WorkstationOutageId = an.StartNumber; GB5Trace.Step("save-workstation-outage", new { dto.WorkstationOutageId, dto.WorkstationId, isNew = true }); try { await _dal.InsertOutageAsync(dto, login, ct).ConfigureAwait(false); } catch (Exception ex) { await _autoNumber.RollbackAutoNumber("WORKSTATIONOUTAGE", dto.WorkstationOutageId, login).ConfigureAwait(false); GB5Trace.MarkFailed("save-workstation-outage-failed", ex); throw; } await NotifyLineIfMappedAsync(dto.WorkstationId, login, ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {dto.WorkstationOutageId}"; } public async Task CloseOutageAsync(int workstationOutageId, int workstationId, LoginDTO login, CancellationToken ct) { GB5Trace.Step("close-workstation-outage", new { workstationOutageId }); try { await _dal.CloseOutageAsync(workstationOutageId, DateTime.UtcNow, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("close-workstation-outage-failed", ex); throw; } await NotifyLineIfMappedAsync(workstationId, login, ct).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } }