namespace EgwCoreLib.Lux.Data.Repository.Sales { public class OrderRowRepository : BaseRepository, IOrderRowRepository { #region Public Constructors public OrderRowRepository(IDbContextFactory ctxFactory) : base(ctxFactory) { } #endregion Public Constructors #region Public Methods /// public async Task AddAsync(OrderRowModel entity) { await using var dbCtx = await CreateContextAsync(); await dbCtx.DbSetOrderRow.AddAsync(entity); return await dbCtx.SaveChangesAsync() > 0; } /// public async Task DeleteAsync(OrderRowModel entity) { await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 1. Recupero il record da eliminare var dbResult = await dbCtx.DbSetOrderRow .FirstOrDefaultAsync(x => x.OrderRowID == entity.OrderRowID); if (dbResult == null) return false; // 2. Recupero i record successivi da shiftare var list2Move = await dbCtx.DbSetOrderRow .Where(x => x.OrderID == entity.OrderID && x.RowNum > dbResult.RowNum) .ToListAsync(); foreach (var item in list2Move) { item.RowNum--; dbCtx.Entry(item).State = EntityState.Modified; } // 3. Rimuovo il record dbCtx.DbSetOrderRow.Remove(dbResult); // 4. Salvo tutto bool done = await dbCtx.SaveChangesAsync() > 0; if (done) await tx.CommitAsync(); return done; } catch { await tx.RollbackAsync(); throw; } } /// public async Task> GetBomItemsAsync() { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetItem .Where(x => x.ItemType == ItemClassType.Bom || x.ItemType == ItemClassType.BomAlt) .ToListAsync(); } /// public async Task GetByIdAsync(int OrderRowId) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetOrderRow .Include(r => r.SellingItemNav) .FirstOrDefaultAsync(x => x.OrderRowID == OrderRowId); } /// public async Task> GetByParentAsync(int orderId) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetOrderRow .Where(x => x.OrderID == orderId) .Include(r => r.SellingItemNav) .AsNoTracking() .ToListAsync(); } /// public async Task> GetByStateAsync(OrderStates reqState, DateTime dtStart, DateTime dtEnd) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetOrderRow .Where(x => x.OrderRowState == reqState && x.Inserted >= dtStart && x.Inserted <= dtEnd) .Include(r => r.SellingItemNav) .AsNoTracking() .ToListAsync(); } /// public async Task> GetByStateMinAsync(OrderStates reqState, DateTime dtStart, DateTime dtEnd) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetOrderRow .Where(x => x.OrderRowState >= reqState && x.Inserted >= dtStart && x.Inserted <= dtEnd) .Include(r => r.SellingItemNav) .AsNoTracking() .ToListAsync(); } /// public async Task GetByUidAsync(string OrderRowUid) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetOrderRow .Include(r => r.SellingItemNav) .FirstOrDefaultAsync(x => x.OrderRowUID == OrderRowUid); } /// public async Task> GetItemGroupsAsync() { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetItemGroup.ToListAsync(); } /// public async Task SaveRowsAsync(List rows) { await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) dbCtx.Entry(row).State = EntityState.Modified; bool done = await dbCtx.SaveChangesAsync() > 0; if (done) await tx.CommitAsync(); return done; } catch { await tx.RollbackAsync(); throw; } } /// public async Task UpdateAsync(OrderRowModel entity) { await using var dbCtx = await CreateContextAsync(); // Recuperiamo l'entità tracciata dal context var trackedEntity = await dbCtx.DbSetOrderRow.FirstOrDefaultAsync(x => x.OrderRowID == entity.OrderRowID); if (trackedEntity != null) { // Aggiorna i valori dell'entità tracciata con quelli della nuova dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity); } else { dbCtx.DbSetOrderRow.Update(entity); } return await dbCtx.SaveChangesAsync() > 0; } public async Task SaveProdEstAsync(string uID, string prodEstim) { // Add validation for null or empty list if (string.IsNullOrWhiteSpace(uID) || string.IsNullOrWhiteSpace(prodEstim)) return false; await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // recupero offerta... var currRec = dbCtx .DbSetOrderRow .Where(x => x.OrderRowUID == uID) .FirstOrDefault(); // se trovo aggiorno if (currRec != null) { // genero WLD x controllo var currWLD = new WorkLoadDetailDTO(currRec.OrderRowUID, currRec.ProdEstimate); // faccio update in cascata dei record collegati x macchine e items var listMachDb = dbCtx .DbSetProdPlant .Select(x => x.ProdPlantCod) .ToList(); List listMaccCurr = currWLD.MachineCalcResults.Select(x => x.Name).OrderBy(x => x).ToList() ?? new List(); if (listMaccCurr != null && listMaccCurr.Any()) { var listDiff = listMaccCurr.Except(listMachDb).ToList(); if (listDiff.Any()) { foreach (var macch in listDiff) { dbCtx .DbSetProdPlant .Add(new ProductionPlantModel() { ProdPlantCod = macch, ProdPlantDescript = macch }); } } } // resetta assegnazioni prodGroup agli items... var listItem2upd = await dbCtx .DbSetProdItem .Where(x => x.OrderRowID == currRec.OrderRowID && x.ProdGroupID != null) .ToListAsync(); if (listItem2upd != null && listItem2upd.Count > 0) { // li aggiorna tutti resettando ProdGroupID listItem2upd.ForEach(x => x.ProdGroupID = null); } // elimina eventuali oggetti ProductionGroup precedenti List listProdGroup2Rem = await dbCtx .DbSetProdGroup .Where(x => x.OrderRowID == currRec.OrderRowID) .ToListAsync(); // rimuovo... if (listProdGroup2Rem != null && listProdGroup2Rem.Count > 0) { dbCtx.DbSetProdGroup.RemoveRange(listProdGroup2Rem); } /*---------------------------------- * Generazione ProdGroup * FixMe ToDo !!! * * rifare considerando le REALI combinazioni scaturite x questo specifico caso e * - ENUMERARE le combinazioni * - ogni combinazione sarà un caso specifico tra 0...N dove N è il totale delle macchine gestite * - i successivi calcoli di balance/stima saranno fatti x questo SPECIFICO ID GROUP così da fare prima... a sto punto GroupIP potrebbe essere un int 0...n oppure l'id del record... forse meglio il counter 0..n * * */ int grpIdx = 1; // preparo x add nuovi ProductionGroup da analisi ritorno stime List listProdGroup2Add = new List(); // 1: non lavorabili... if (currWLD.ListUnWorkable.Count > 0) { // calcolo il dizionario degli elementi... Dictionary newWorkGroupList = new(); ProdMachineDetailDto detProd = new ProdMachineDetailDto() { TagList = currWLD.ListUnWorkable }; newWorkGroupList.Add("", detProd); string rawWGL = JsonConvert.SerializeObject(newWorkGroupList); ProductionGroupModel newRec = new ProductionGroupModel() { OrderRowID = currRec.OrderRowID, GrpIdx = grpIdx++, WorkGroupListRaw = rawWGL }; listProdGroup2Add.Add(newRec); } // dizionario x macchina delle parts LAVORABILI su impianto.. var machineTags = currWLD.MachineCalcResults .ToDictionary( m => m.Name, m => m.PartList .Where(p => p.CalcResult == EgwCoreLib.Lux.Core.Enums.PartVerificationResult.MACHINABLE) .ToList() ); // ciclo x tutte le combinazioni di gruppi lavorabilità... foreach (var item in currWLD.LoadDetail) { // calcolo il dizionario degli elementi... Dictionary newWorkGroupList = new(); foreach (var machineName in item.Machines) { decimal effectiveTime = 0; if (currRec.Envir == Constants.EXECENVIRONMENTS.WINDOW) { // Creiamo un set dei tag del gruppo per una ricerca veloce O(1) var groupTagsSet = item.Tags.ToHashSet(); effectiveTime = currWLD.TotMaxTime; } else { // Recuperiamo i dati della macchina dal dizionario if (machineTags.TryGetValue(machineName, out var machineParts)) { // Creiamo un set dei tag del gruppo per una ricerca veloce O(1) var groupTagsSet = item.Tags.ToHashSet(); // Sommiamo il tempo solo per i pezzi che appartengono a questo gruppo effectiveTime = machineParts .Where(p => groupTagsSet.Contains(p.Tag)) .Sum(p => p.Time); } } ProdMachineDetailDto detProd = new ProdMachineDetailDto() { TagList = item.Tags, Time = effectiveTime // Tempo reale specifico per questa macchina/gruppo }; newWorkGroupList.Add(machineName, detProd); } string rawWGL = JsonConvert.SerializeObject(newWorkGroupList); ProductionGroupModel newRec = new ProductionGroupModel() { OrderRowID = currRec.OrderRowID, GrpIdx = grpIdx++, WorkGroupListRaw = rawWGL }; listProdGroup2Add.Add(newRec); } // aggiungo i record... dbCtx.DbSetProdGroup.AddRange(listProdGroup2Add); // aggiorno info Estimation, tempi e stato currRec.ProdEstimate = prodEstim; if (!string.IsNullOrEmpty(prodEstim)) { currRec.OrderRowState = OrderStates.Estimated; } var totEstim = listProdGroup2Add.Sum(x => x.TotalEstimTime); currRec.ProdEstimTime = totEstim; dbCtx.Entry(currRec).State = EntityState.Modified; } // salvo TUTTI i cambiamenti... bool done = await dbCtx.SaveChangesAsync() > 0; if (done) await tx.CommitAsync(); return done; } catch { await tx.RollbackAsync(); throw; } } /// public async Task ValidateAsync(List list2chk) { int numDone = 0; // Add validation for null or empty list if (list2chk.Count == 0) return numDone; // context await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // verifica preliminare: serve SSE stato e estimate non corrispondono... var list2fix = list2chk .Where(x => x.OrderRowState == OrderStates.Created && !string.IsNullOrEmpty(x.ProdEstimate)) .ToList(); if (list2fix.Any()) { // per ogni record processo intera validazione foreach (var item in list2fix) { bool fatto = await SaveProdEstAsync(item.OrderRowUID, item.ProdEstimate); numDone += fatto ? 1 : 0; } } // salvo TUTTI i cambiamenti... bool done = await dbCtx.SaveChangesAsync() > 0; if (done) await tx.CommitAsync(); return numDone; } catch { await tx.RollbackAsync(); throw; } } #endregion Public Methods } }