namespace EgwCoreLib.Lux.Data.Repository.Supplier { public class BuyOrderRepository : BaseRepository, IBuyOrderRepository { #region Public Constructors public BuyOrderRepository(IDbContextFactory ctxFactory) : base(ctxFactory) { } #endregion Public Constructors #region Public Methods /// public async Task AddAsync(BuyOrderModel entity) { await using var dbCtx = await CreateContextAsync(); await dbCtx.DbSetBuyOrder.AddAsync(entity); return await dbCtx.SaveChangesAsync() > 0; } /// public async Task DeleteAsync(BuyOrderModel entity) { await using var dbCtx = await CreateContextAsync(); dbCtx.DbSetBuyOrder.Remove(entity); return await dbCtx.SaveChangesAsync() > 0; } /// public async Task> GetAllAsync() { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetBuyOrder .Include(c => c.SupplierNav) .Include(o => o.BuyOrderRowNav) .ThenInclude(s => s.ItemNav) .Include(m => m.BuyOrderRowNav) .ThenInclude(s => s.MatReqNav) .AsNoTracking() .ToListAsync(); } /// public async Task GenerateFromSelectionAsync(Dictionary> currSelDict) { if (currSelDict == null) throw new ArgumentNullException(nameof(currSelDict)); if (currSelDict.Values.Any(l => l == null || l.Count == 0)) throw new ArgumentException("La selezione contiene valori null o vuoti."); await using var dbCtx = await CreateContextAsync(); // 1. Avvia transazione per garantire atomicità await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 2. Calcola il prossimo RefNum per l'anno corrente int nextRefNum = await GetNextRefNumAsync(DateTime.Today.Year); // 3. Crea il nuovo ordine padre var newOrder = new BuyOrderModel { RefYear = DateTime.Today.Year, RefNum = nextRefNum, RefRev = 1, Description = $"Acquisto raggruppato - {DateTime.Now:yyyy-MM-dd HH:mm}", BuyOrderRowNav = new List() }; dbCtx.DbSetBuyOrder.Add(newOrder); await dbCtx.SaveChangesAsync(); // Necessario per generare BuyOrderID // 4. Recupera in BATCH tutti i MatReq selezionati (Anti-N+1) var allSelectedIds = currSelDict.Values.SelectMany(ids => ids).Distinct().ToList(); var matReqLookup = await dbCtx.DbSetMaterialReq .Where(m => allSelectedIds.Contains(m.MatReqID)) .ToListAsync(); //.Select(m => new { m.MatReqID, m.ItemID, m.TotQty, m.CodGroup }) //.ToDictionaryAsync(m => m.MatReqID); // 5. Costruisci righe e join table int rowCounter = 1; foreach (var matReqIds in currSelDict.Values) { foreach (var matReqId in matReqIds) { var matReqInfo = matReqLookup.FirstOrDefault(x => x.MatReqID == matReqId); if (matReqInfo == null) continue; var row = new BuyOrderRowModel { BuyOrderID = newOrder.BuyOrderID, ItemID = matReqInfo.ItemID, RowNum = rowCounter++, ClassCode = matReqInfo.CodGroup, ItemCode = matReqInfo.ItemCode, DescriptionCode = matReqInfo.Description }; var join = new BuyOrderRow2MatReqModel { MatReqID = matReqId, Qty = matReqInfo.TotQty, // BuyOrderRowID verrà popolato automaticamente da EF Core al SaveChanges }; // segno processato matReqInfo.Processed = true; // EF Core gestisce le relazioni attraverso le collection row.MatReqNav.Add(join); newOrder.BuyOrderRowNav.Add(row); } } await dbCtx.SaveChangesAsync(); await tx.CommitAsync(); return newOrder; } catch { await tx.RollbackAsync(); throw; } } /// /// Helper calcolo del nuovo refNum dell'ordine /// /// /// private async Task GetNextRefNumAsync(int year) { await using var dbCtx = await CreateContextAsync(); var max = await dbCtx.DbSetBuyOrder .Where(o => o.RefYear == year) .MaxAsync(o => (int?)o.RefNum); return (max ?? 0) + 1; } /// public async Task GetByIdAsync(int recId) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetBuyOrder .Where(x => x.BuyOrderID == recId) .Include(c => c.SupplierNav) .Include(o => o.BuyOrderRowNav) .ThenInclude(s => s.ItemNav) .Include(m => m.BuyOrderRowNav) .ThenInclude(s => s.MatReqNav) .FirstOrDefaultAsync(); } /// public async Task> GetFiltAsync(DateTime inizio, DateTime fine) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetBuyOrder .Where(x => x.Inserted >= inizio && x.Inserted <= fine) .Include(c => c.SupplierNav) .Include(o => o.BuyOrderRowNav) .ThenInclude(s => s.ItemNav) .Include(m => m.BuyOrderRowNav) .ThenInclude(s => s.MatReqNav) .AsNoTracking() .ToListAsync(); } /// public async Task> GetRowsAsync(int recId) { await using var dbCtx = await CreateContextAsync(); return await dbCtx.DbSetBuyOrderRow .Where(x => x.BuyOrderID == recId) .ToListAsync(); } /// public async Task SaveRowsAsync(List rows) { // Add validation for null or empty list if (rows == null || rows.Count == 0) 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 { 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(BuyOrderModel entity) { await using var dbCtx = await CreateContextAsync(); // Recuperiamo l'entità tracciata dal context var trackedEntity = await dbCtx.DbSetBuyOrder.FirstOrDefaultAsync(x => x.BuyOrderID == entity.BuyOrderID); if (trackedEntity != null) { // Aggiorna i valori dell'entità tracciata con quelli della nuova dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity); } else { dbCtx.DbSetBuyOrder.Update(entity); } return await dbCtx.SaveChangesAsync() > 0; } #endregion Public Methods } }