using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using MMDAL.CustomCode.MTR; using MMDAL.DTO.MTR; using static GB5Shared.GB5Constant.Constant; namespace MMBLL.MTR { public class MtrBLL : IMtrBLL { private readonly IMtrDAL _MtrDAL; private readonly AutoNumber _AutoNumber; private readonly IQueryExecutor _QueryExecutor; private readonly KeyInvalidate _KeyInvalidate; public MtrBLL( IMtrDAL mtrDAL, AutoNumber autoNumber, IQueryExecutor queryExecutor, KeyInvalidate keyInvalidate) { _MtrDAL = mtrDAL; _AutoNumber = autoNumber; _QueryExecutor = queryExecutor; _KeyInvalidate = keyInvalidate; } // ── Get single MTR — full hierarchy (all 5 sections) ───────────────── public async Task GetMtr(int mtrId, LoginDTO loginDTO, CancellationToken ct) => await _MtrDAL.GetMtr(mtrId, loginDTO, ct).ConfigureAwait(false); // ── Get picklist for dropdowns ──────────────────────────────────────── public async Task GetSelectListMtr(CriteriaDTO criteriaDTO, LoginDTO loginDTO) => await _MtrDAL.GetSelectListMtr(criteriaDTO, loginDTO).ConfigureAwait(false); // ── Get paginated list ──────────────────────────────────────────────── public async Task> GetMtrList( string? searchText, int partyId, int itemId, byte reportStatus, int pageOffset, int pageSize, LoginDTO loginDTO, CancellationToken ct) => await _MtrDAL.GetMtrList( searchText, partyId, itemId, reportStatus, pageOffset, pageSize, loginDTO, ct).ConfigureAwait(false); // ── Save (create or update) ─────────────────────────────────────────── // Auto-number allocation order mirrors the insert hierarchy: // MTR → MTRLOT → MTRSOURCE → MTRRESULT → MTRCLIENTPREF // All numbers are rolled back if any step throws. public async Task SaveMtr(MtrReportDTO mtrDTO, LoginDTO loginDTO, CancellationToken ct) { if (mtrDTO == null) throw new ArgumentNullException(nameof(mtrDTO)); ValidateMtr(mtrDTO); bool isNew = mtrDTO.MtrId == 0; int allocatedMtrId = 0; int allocatedLotStart = 0; int allocatedSrcStart = 0; var Trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { if (isNew) { AutoNumberDTO mtrAuto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.MTR, loginDTO); allocatedMtrId = mtrAuto.StartNumber; mtrDTO.MtrId = allocatedMtrId; } if (mtrDTO.Lots.Count > 0) { AutoNumberDTO lotAuto = await _AutoNumber.GetNumberAsync( mtrDTO.Lots.Count, AUTONUMBERCONSTANT.MTRLOT, loginDTO); allocatedLotStart = lotAuto.StartNumber; for (int i = 0; i < mtrDTO.Lots.Count; i++) { mtrDTO.Lots[i].MtrLotId = lotAuto.StartNumber + i; mtrDTO.Lots[i].MtrId = mtrDTO.MtrId; } } int totalSources = mtrDTO.Lots.Sum(l => l.Sources.Count); if (totalSources > 0) { AutoNumberDTO srcAuto = await _AutoNumber.GetNumberAsync( totalSources, AUTONUMBERCONSTANT.MTRSOURCE, loginDTO); allocatedSrcStart = srcAuto.StartNumber; int srcIndex = 0; foreach (var lot in mtrDTO.Lots) foreach (var src in lot.Sources) { src.MtrSourceId = srcAuto.StartNumber + srcIndex++; src.MtrLotId = lot.MtrLotId; } } _ = isNew ? await _MtrDAL.SaveMtr(mtrDTO, loginDTO, Trans, ct).ConfigureAwait(false) : await _MtrDAL.UpdateMtr(mtrDTO, loginDTO, Trans, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {mtrDTO.MtrId}" : $"{SuccessResponse.UpdateSuccessMessage} {mtrDTO.MtrId}"; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); if (isNew && allocatedMtrId > 0) await _AutoNumber.RollbackAutoNumber(AUTONUMBERCONSTANT.MTR, allocatedMtrId, loginDTO); if (allocatedLotStart > 0) await _AutoNumber.RollbackAutoNumber(AUTONUMBERCONSTANT.MTRLOT, allocatedLotStart, loginDTO); if (allocatedSrcStart > 0) await _AutoNumber.RollbackAutoNumber(AUTONUMBERCONSTANT.MTRSOURCE, allocatedSrcStart, loginDTO); throw; } } // ── Delete ──────────────────────────────────────────────────────────── public async Task DeleteMtr(int mtrId, LoginDTO loginDTO, CancellationToken ct) { var Trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { await _MtrDAL.DeleteMtr(mtrId, loginDTO, Trans, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans); return SuccessResponse.DeleteSuccessMessage; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans); throw; } } // ── Validation ──────────────────────────────────────────────────────── private static void ValidateMtr(MtrReportDTO dto) { if (string.IsNullOrWhiteSpace(dto.MtrNo)) throw new ArgumentException("MTR number is required.", nameof(dto.MtrNo)); if (dto.ItemId == 0) throw new ArgumentException("Item is required.", nameof(dto.ItemId)); if (dto.PartyId == 0) throw new ArgumentException("Party (Customer) is required.", nameof(dto.PartyId)); if (dto.MtrDate == default) throw new ArgumentException("MTR date is required.", nameof(dto.MtrDate)); if (dto.TotalQty <= 0) throw new ArgumentException("Total quantity must be greater than zero.", nameof(dto.TotalQty)); if (dto.Lots.Count > 0) { var duplicateSlNos = dto.Lots .GroupBy(l => l.SlNo) .Where(g => g.Count() > 1) .Select(g => g.Key) .ToList(); if (duplicateSlNos.Count > 0) throw new ArgumentException( $"Duplicate lot serial numbers found: {string.Join(", ", duplicateSlNos)}"); } } } }