using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using MMBLL.Indent; using MMBLL.MMHead; using MMDAL.CustomCode.MRPRun; using MMDAL.DTO.Indent; using MMDAL.DTO.MRPRun; using static GB5Shared.GB5Constant.Constant; namespace MMBLL.MRPRun { public class MRPActionBLL : IMRPActionBLL { private readonly IMRPActionDAL _MRPActionDAL; private readonly AutoNumber _AutoNumber; private readonly IQueryExecutor _QueryExecutor; private readonly KeyInvalidate _KeyInvalidate; private readonly BaseEntityAppService _BaseEntityAppService; private readonly IMMHeadBLL _MMHeadBLL; private readonly IIndentBLL _IndentBLL; public MRPActionBLL( IMRPActionDAL mrpActionDAL, AutoNumber autoNumber, IQueryExecutor queryExecutor, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, IMMHeadBLL mmHeadBLL, IIndentBLL indentBLL) { _MRPActionDAL = mrpActionDAL; _AutoNumber = autoNumber; _QueryExecutor = queryExecutor; _KeyInvalidate = keyInvalidate; _BaseEntityAppService = baseEntityAppService; _MMHeadBLL = mmHeadBLL; _IndentBLL = indentBLL; } public async Task GetMRPAction( int MRPActionId, LoginDTO loginDTO, CancellationToken ct) => await _MRPActionDAL.GetMRPAction(MRPActionId, loginDTO, ct).ConfigureAwait(false); public async Task> GetMRPActionList( int MRPRunId, LoginDTO loginDTO, CancellationToken ct) => await _MRPActionDAL.GetMRPActionList(MRPRunId, loginDTO, ct).ConfigureAwait(false); public async Task> GetWorkbench( CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct) => await _MRPActionDAL.GetWorkbench(criteriaDTO, loginDTO, ct).ConfigureAwait(false); public async Task> GetWorkbenchItemWise( WorkBenchCriteriaDTO criteria, LoginDTO loginDTO, CancellationToken ct) => await _MRPActionDAL.GetWorkbenchItemWise(criteria, loginDTO, ct).ConfigureAwait(false); public async Task SaveMRPAction( MRPActionDTO dto, LoginDTO loginDTO, CancellationToken ct) { var Trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { bool isNew = dto.MRPActionId == 0; if (isNew) { var auto = await _AutoNumber.GetNumberAsync( 1, AUTONUMBERCONSTANT.MRPACTION, loginDTO).ConfigureAwait(false); dto.MRPActionId = auto.StartNumber; } await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJMRPACTION, EventTypeConstant.SAVEMRPACTIONEVENTTYPEID, dto, loginDTO, async tx => { var result = isNew ? await _MRPActionDAL.SaveMRPAction(dto, loginDTO, tx, ct).ConfigureAwait(false) : await _MRPActionDAL.UpdateMRPAction(dto, loginDTO, tx, ct).ConfigureAwait(false); return dto.MRPActionId; }, new Dictionary { { "MRPRunId", dto.MRPRunId } }, -1, -1, Trans); await _QueryExecutor.CommitAsync(Trans); var keyGen = new CacheKeyGeneration(); var cacheKey = keyGen.KeyGeneration( dto.MRPActionId, EntityConstant.OBJMRPACTION, CacheKeyLevel.NOT_REQUIRED, loginDTO); await _KeyInvalidate.AllInvalidateCache(cacheKey); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.MRPActionId}" : SuccessResponse.UpdateSuccessMessage; } catch { await _QueryExecutor.RollbackAsync(Trans); throw; } } // Release path: iterates actions, applies priority filter, then routes to PO/PR or WorkIndent. // Path A (IsAllocationBasedRelease=0, ReleaseType 1/2): builds MMHeadDTO → IMMHeadBLL.SaveMMHead // Path A (IsAllocationBasedRelease=0, ReleaseType 3): requires IIndentBLL — pending migration // Path B (IsAllocationBasedRelease=1): requires IAllocationBLL.AllocationSave — pending migration public async Task ReleaseMRPActions( ReleaseMRPActionDTO dto, LoginDTO loginDTO, CancellationToken ct) { if (dto.MrpActionIds == null || dto.MrpActionIds.Count == 0) throw new ArgumentException("No MRP Action IDs provided for release."); var actions = new List(); foreach (var id in dto.MrpActionIds) { var action = await _MRPActionDAL.GetMRPAction(id, loginDTO, ct).ConfigureAwait(false); if (action == null) continue; if (action.MRPActionPlanStatus == 2) continue; // already released // Apply MaxPriorityToRelease filter: skip low-priority actions if filter set if (dto.MaxPriorityToRelease.HasValue && action.Priority != 0 && action.Priority > dto.MaxPriorityToRelease.Value) continue; actions.Add(action); } if (actions.Count == 0) return "No eligible actions found for release."; int released = 0; foreach (var action in actions) { var releaseType = dto.ReleaseTypeOverride ?? action.MRPActionReleaseType; if (dto.IsAllocationBasedRelease == 0) { // Path A — direct release if (releaseType == 1 || releaseType == 2) { // PO / PR: delegate to MMHeadBLL var mmHead = BuildMMHeadFromAction(action, loginDTO); await _MMHeadBLL.SaveMMHead(mmHead, loginDTO).ConfigureAwait(false); var trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { action.MRPActionReleaseType = releaseType; action.MRPActionPlanStatus = 2; // Released await _MRPActionDAL.UpdateMRPActionRelease(action, loginDTO, trans, ct) .ConfigureAwait(false); await _QueryExecutor.CommitAsync(trans); } catch { await _QueryExecutor.RollbackAsync(trans); throw; } released++; } else if (releaseType == 3) { // Work Indent — generate WO via IndentBLL var indent = BuildIndentFromAction(action, dto, loginDTO); await _IndentBLL.SaveIndent(indent, loginDTO, ct).ConfigureAwait(false); var trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { action.MRPActionReleaseType = releaseType; action.MRPActionPlanStatus = 2; // Released await _MRPActionDAL.UpdateMRPActionRelease(action, loginDTO, trans, ct) .ConfigureAwait(false); await _QueryExecutor.CommitAsync(trans); } catch { await _QueryExecutor.RollbackAsync(trans); throw; } released++; } } else { // Path B — allocation-based release — requires AllocationBLL.AllocationSave throw new NotSupportedException( "Allocation-based release requires AllocationBLL.AllocationSave. " + "Complete when AllocationBLL is extended with the save pipeline."); } } return $"{released} MRP action(s) released successfully."; } // Builds a minimal MMHeadDTO from MRPAction for PO/PR release. // Full field mapping requires legacy MRPBLL.AssignMMHeadForRelease — expand as needed. private static MMDAL.DTO.MMHead.MMHeadDTO BuildMMHeadFromAction( MRPActionDTO action, LoginDTO loginDTO) { var head = new MMDAL.DTO.MMHead.MMHeadDTO { BizTransactionTypeId = action.ReleaseBizTransactionTypeId, PartyId = action.PartyId, StoreId = action.StoreId, MMHeadDate = action.MRPActionPlannedOrderDate, OUId = loginDTO.WorkOUId, }; // Add a single detail line from the action head.MMDetailArray.Add(new MMDAL.DTO.MMHead.MMDetailDTO { ItemId = action.PlanItemId, AllocationId = action.AllocationId, MMDetailTransactionQuantity = (double)action.MRPActionPlannedQuantity, }); return head; } // Builds an IndentSaveDTO from MRPAction for Work Indent release (ReleaseType=3). private static IndentSaveDTO BuildIndentFromAction( MRPActionDTO action, ReleaseMRPActionDTO releaseDto, LoginDTO loginDTO) { var detail = new IndentDetailSaveDTO { ItemId = action.PlanItemId, SkuId = action.PlanSKUId > 0 ? action.PlanSKUId : -1, PartDimId = action.PartDimId, IndentDetailQuantity = action.MRPActionPlannedQuantity, IndentDetailReleaseDate = action.MRPActionPlannedDueDate, IndentDetailPriority = action.Priority, AllocationId = action.AllocationId > 0 ? action.AllocationId : null, SalesOrderId = action.SalesOrderId > 0 ? action.SalesOrderId : null, }; return new IndentSaveDTO { BizTransactionTypeId = action.ReleaseBizTransactionTypeId, OUId = loginDTO.WorkOUId, PeriodId = releaseDto.PeriodId, IndentDate = action.MRPActionPlannedOrderDate, SourceType = 3, // MRP ExpectedCompletionDate = action.MRPActionPlannedDueDate, Details = [detail], }; } } }