using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.JobCard; using MMDAL.DTO.JobCard; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace MMBLL.JobCard { public class JobCardBLL : IJobCardBLL { private readonly IJobCardDAL _dal; private readonly AutoNumber _autoNumber; private readonly BaseEntityAppService _entityApp; private readonly ILogger _logger; public JobCardBLL( IJobCardDAL dal, AutoNumber autoNumber, BaseEntityAppService entityApp, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _entityApp = entityApp; _logger = logger; } // ── Get ─────────────────────────────────────────────────────────────── public async Task GetJobCard(int jobCardId, LoginDTO login, CancellationToken ct) { var header = await _dal.GetJobCard(jobCardId, login, ct).ConfigureAwait(false); if (header is null) return JsonConvert.SerializeObject(null); header.Details = (await _dal.GetJobCardDetails(jobCardId, login, ct).ConfigureAwait(false)).ToList(); return JsonConvert.SerializeObject(header); } public async Task GetJobCardList(int ouId, int firstNumber, int maxResult, LoginDTO login, CancellationToken ct) { var (items, total) = await _dal.GetJobCardList(ouId, firstNumber, maxResult, login, ct).ConfigureAwait(false); return JsonConvert.SerializeObject(new { Items = items, TotalCount = total }); } public async Task GetJobCardsForOperator(int operatorId, LoginDTO login, CancellationToken ct) { var cards = await _dal.GetJobCardsForOperator(operatorId, login, ct).ConfigureAwait(false); return JsonConvert.SerializeObject(cards); } // ── Save ────────────────────────────────────────────────────────────── public async Task SaveJobCard(JobCardSaveDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-jobcard", new { dto.JobCardId }); if (dto.OUId <= 0) throw new ArgumentException("OUId is required."); if (dto.PeriodId <= 0) throw new ArgumentException("PeriodId is required."); if (dto.ShiftId <= 0) throw new ArgumentException("ShiftId is required."); bool isNew = dto.JobCardId == 0; if (isNew) { var hNum = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.JOBCARD, login).ConfigureAwait(false); dto.JobCardId = hNum.StartNumber; } // Pre-assign detail IDs before transaction if (dto.Details.Count > 0) { var dNum = await _autoNumber.GetNumberAsync(dto.Details.Count, AUTONUMBERCONSTANT.JOBCARDDETAIL, login).ConfigureAwait(false); int nextId = dNum.StartNumber; for (short i = 0; i < dto.Details.Count; i++) { dto.Details[i].JobCardDetailId = nextId++; dto.Details[i].JobCardId = dto.JobCardId; dto.Details[i].SlNo = (short)(i + 1); dto.Details[i].TenantId = login.ClientId; } } foreach (var m in dto.Materials) { m.JobCardId = dto.JobCardId; m.TenantId = login.ClientId; } foreach (var p in dto.Processes) { p.JobCardId = dto.JobCardId; p.TenantId = login.ClientId; } dto.TenantId = login.ClientId; GB5Trace.Step("save-jobcard", new { dto.JobCardId, isNew }); await _entityApp.ExecuteSaveAsync( EntityConstant.OBJECTJOBCARD, EventTypeConstant.SAVEJOBCARDEVENTTYPEID, dto, login, async tx => { await _dal.SaveJobCard(dto, login, tx, ct).ConfigureAwait(false); if (dto.Details.Count > 0) await _dal.BulkSaveJobCardDetails(dto.Details, login, tx).ConfigureAwait(false); if (dto.Materials.Count > 0) await _dal.BulkSaveJobCardMaterials(dto.Materials, login, tx).ConfigureAwait(false); if (dto.Processes.Count > 0) await _dal.BulkSaveJobCardProcesses(dto.Processes, login, tx).ConfigureAwait(false); return dto.JobCardId; }).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.SAVEJOBCARDEVENTTYPEID }); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.JobCardId}" : SuccessResponse.UpdateSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("save-jobcard-failed", ex); _logger.LogError(ex, "SaveJobCard failed for JobCardId {JobCardId}", dto.JobCardId); throw; } } // ── Complete ────────────────────────────────────────────────────────── public async Task CompleteJobCard(int jobCardId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("complete-jobcard", new { jobCardId }); var stub = new JobCardSaveDTO { JobCardId = jobCardId, TenantId = login.ClientId }; await _entityApp.ExecuteSaveAsync( EntityConstant.OBJECTJOBCARD, EventTypeConstant.SAVEJOBCARDEVENTTYPEID, stub, login, async tx => { await _dal.CompleteJobCard(jobCardId, login, tx, ct).ConfigureAwait(false); return jobCardId; }).ConfigureAwait(false); return SuccessResponse.UpdateSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("complete-jobcard-failed", ex); _logger.LogError(ex, "CompleteJobCard failed for JobCardId {JobCardId}", jobCardId); throw; } } // ── Delete ──────────────────────────────────────────────────────────── public async Task DeleteJobCard(int jobCardId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-jobcard", new { jobCardId }); var stub = new JobCardSaveDTO { JobCardId = jobCardId, TenantId = login.ClientId }; await _entityApp.ExecuteSaveAsync( EntityConstant.OBJECTJOBCARD, EventTypeConstant.DELETEJOBCARDEVENTTYPEID, stub, login, async tx => { await _dal.SoftDeleteJobCard(jobCardId, login, tx, ct).ConfigureAwait(false); return jobCardId; }).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeId = EventTypeConstant.DELETEJOBCARDEVENTTYPEID }); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-jobcard-failed", ex); _logger.LogError(ex, "DeleteJobCard failed for JobCardId {JobCardId}", jobCardId); throw; } } } }