diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs index d36cb5fd..482b08ad 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs @@ -35,86 +35,99 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales public async Task CloneAsync(OfferModel rec2clone) { await using var dbCtx = await CreateContextAsync(); - DateTime now = DateTime.Now; - var currRec = await dbCtx.DbSetOffer - .Include(x => x.OfferRowNav) - .FirstOrDefaultAsync(x => x.OfferID == rec2clone.OfferID); - - if (currRec == null) - return false; - - DateTime adesso = DateTime.Now; - var lastRec = dbCtx - .DbSetOffer - .Where(x => x.RefYear == adesso.Year) - .OrderByDescending(x => x.RefNum) - .FirstOrDefault(); - int newRefNum = lastRec != null ? lastRec.RefNum + 1 : 1; - - // 2. Creo il nuovo parent - var newRec = new OfferModel() + // Wrap in transaction for atomicity (parent + children clone) + await using var tx = dbCtx.Database.BeginTransaction(); + try { - ConsNote = rec2clone.ConsNote, - CustomerID = rec2clone.CustomerID, - DealerID = rec2clone.DealerID, - Description = rec2clone.Description, - DictPresel = rec2clone.DictPresel, - Discount = rec2clone.Discount, - DueDateProm = rec2clone.DueDateProm, - DueDateReq = rec2clone.DueDateReq, - Envir = rec2clone.Envir, - Inserted = adesso, - Modified = adesso, - OffertState = OfferStates.Open, - RefNum = newRefNum, - RefRev = 1, - RefYear = adesso.Year, - ValidUntil = currRec.ValidUntil - }; + DateTime now = DateTime.Now; - // 3. Clono i child - // sistemo child... - newRec.OfferRowNav = currRec.OfferRowNav - .Select(c => new OfferRowModel() + var currRec = await dbCtx.DbSetOffer + .Include(x => x.OfferRowNav) + .FirstOrDefaultAsync(x => x.OfferID == rec2clone.OfferID); + + if (currRec == null) + return false; + + DateTime adesso = DateTime.Now; + var lastRec = dbCtx + .DbSetOffer + .Where(x => x.RefYear == adesso.Year) + .OrderByDescending(x => x.RefNum) + .FirstOrDefault(); + int newRefNum = lastRec != null ? lastRec.RefNum + 1 : 1; + + // 2. Creo il nuovo parent + var newRec = new OfferModel() { - AwaitBom = c.AwaitBom, - AwaitPrice = c.AwaitPrice, - BomCost = c.BomCost, - BomOk = c.BomOk, - BomPrice = c.BomPrice, - Envir = c.Envir, - FileName = c.FileName, - FileResource = c.FileResource, - FileSize = c.FileSize, + ConsNote = rec2clone.ConsNote, + CustomerID = rec2clone.CustomerID, + DealerID = rec2clone.DealerID, + Description = rec2clone.Description, + DictPresel = rec2clone.DictPresel, + Discount = rec2clone.Discount, + DueDateProm = rec2clone.DueDateProm, + DueDateReq = rec2clone.DueDateReq, + Envir = rec2clone.Envir, Inserted = adesso, - ItemBOM = c.ItemBOM, - ItemJCD = c.ItemJCD, - ItemOk = c.ItemOk, - ItemSteps = c.ItemSteps, - ItemTags = c.ItemTags, - JobID = c.JobID, - Modified = c.Modified, - Note = c.Note, - ProdItemQty = c.ProdItemQty, - Qty = c.Qty, - RowNum = c.RowNum, - SellingItemID = c.SellingItemID, - SerStruct = c.SerStruct, - StepCost = c.StepCost, - StepFlowTime = c.StepFlowTime, - StepLeadTime = c.StepLeadTime, - StepPrice = c.StepPrice, - //OrderID = dbRec.OrderID, - OfferRowUID = c.OfferRowDtx - }) - .ToList(); + Modified = adesso, + OffertState = OfferStates.Open, + RefNum = newRefNum, + RefRev = 1, + RefYear = adesso.Year, + ValidUntil = currRec.ValidUntil + }; - // 4. Aggiungo il nuovo parent (EF aggiunge anche i child) - dbCtx.DbSetOffer.Add(newRec); + // 3. Clono i child + // sistemo child... + newRec.OfferRowNav = currRec.OfferRowNav + .Select(c => new OfferRowModel() + { + AwaitBom = c.AwaitBom, + AwaitPrice = c.AwaitPrice, + BomCost = c.BomCost, + BomOk = c.BomOk, + BomPrice = c.BomPrice, + Envir = c.Envir, + FileName = c.FileName, + FileResource = c.FileResource, + FileSize = c.FileSize, + Inserted = adesso, + ItemBOM = c.ItemBOM, + ItemJCD = c.ItemJCD, + ItemOk = c.ItemOk, + ItemSteps = c.ItemSteps, + ItemTags = c.ItemTags, + JobID = c.JobID, + Modified = c.Modified, + Note = c.Note, + ProdItemQty = c.ProdItemQty, + Qty = c.Qty, + RowNum = c.RowNum, + SellingItemID = c.SellingItemID, + SerStruct = c.SerStruct, + StepCost = c.StepCost, + StepFlowTime = c.StepFlowTime, + StepLeadTime = c.StepLeadTime, + StepPrice = c.StepPrice, + //OrderID = dbRec.OrderID, + OfferRowUID = c.OfferRowDtx + }) + .ToList(); - // 5. Salvo tutto - return await dbCtx.SaveChangesAsync() > 0; + // 4. Aggiungo il nuovo parent (EF aggiunge anche i child) + dbCtx.DbSetOffer.Add(newRec); + + // 5. Salvo tutto in transazione + await dbCtx.SaveChangesAsync(); + tx.Commit(); + return true; + } + catch + { + tx.Rollback(); + throw; + } } public async Task DeleteAsync(OfferModel entity) @@ -178,10 +191,26 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales public async Task SaveRowsAsync(List rows) { await using var dbCtx = await CreateContextAsync(); - foreach (var row in rows) - dbCtx.Entry(row).State = EntityState.Modified; - return await dbCtx.SaveChangesAsync() > 0; + // Wrap in transaction for atomicity (batch update multiple rows) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + foreach (var row in rows) + dbCtx.Entry(row).State = EntityState.Modified; + + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } public async Task UpdateAsync(OfferModel entity) diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs index f92c1895..323cbffe 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs @@ -28,27 +28,44 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales { await using var dbCtx = await CreateContextAsync(); - // 1. Recupero il record da eliminare - var dbResult = await dbCtx.DbSetOfferRow - .FirstOrDefaultAsync(x => x.OfferRowID == entity.OfferRowID); - - if (dbResult == null) - return false; - - // 2. Recupero i record successivi da shiftare - var list2Move = await dbCtx.DbSetOfferRow - .Where(x => x.OfferID == entity.OfferID && x.RowNum > dbResult.RowNum) - .ToListAsync(); - - foreach (var item in list2Move) + // Wrap in transaction for atomicity (multi-row update + delete) + await using var tx = dbCtx.Database.BeginTransaction(); + try { - item.RowNum--; - dbCtx.Entry(item).State = EntityState.Modified; - } + // 1. Recupero il record da eliminare + var dbResult = await dbCtx.DbSetOfferRow + .FirstOrDefaultAsync(x => x.OfferRowID == entity.OfferRowID); - // 3. Rimuovo il record - dbCtx.DbSetOfferRow.Remove(dbResult); - return await dbCtx.SaveChangesAsync() > 0; + if (dbResult == null) + return false; + + // 2. Recupero i record successivi da shiftare + var list2Move = await dbCtx.DbSetOfferRow + .Where(x => x.OfferID == entity.OfferID && x.RowNum > dbResult.RowNum) + .ToListAsync(); + + foreach (var item in list2Move) + { + item.RowNum--; + dbCtx.Entry(item).State = EntityState.Modified; + } + + // 3. Rimuovo il record + dbCtx.DbSetOfferRow.Remove(dbResult); + + // 4. Salvo tutto + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } public async Task> GetBomItemsAsync() @@ -94,10 +111,26 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales public async Task SaveRowsAsync(List rows) { await using var dbCtx = await CreateContextAsync(); - foreach (var row in rows) - dbCtx.Entry(row).State = EntityState.Modified; - return await dbCtx.SaveChangesAsync() > 0; + // Wrap in transaction for atomicity (batch update multiple rows) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + foreach (var row in rows) + dbCtx.Entry(row).State = EntityState.Modified; + + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } public async Task UpdateAsync(OfferRowModel entity) diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs index 43b88992..4c23329e 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs @@ -241,10 +241,26 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales public async Task SaveRowsAsync(List rows) { await using var dbCtx = await CreateContextAsync(); - foreach (var row in rows) - dbCtx.Entry(row).State = EntityState.Modified; - return await dbCtx.SaveChangesAsync() > 0; + // Wrap in transaction for atomicity (batch update multiple rows) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + foreach (var row in rows) + dbCtx.Entry(row).State = EntityState.Modified; + + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } public async Task UpdateAsync(OrderModel entity) @@ -269,58 +285,73 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales { await using var dbCtx = await CreateContextAsync(); - // recupero righe Orderta... - var offRowList = await dbCtx - .DbSetOrderRow - .Where(x => x.OrderID == OrderID) - .ToListAsync(); - - // recupero l'elenco degli itemGroup gestiti - var itemGroupList = await dbCtx - .DbSetItemGroup - .ToListAsync(); - - // recupero il subset item da BOM / BomAlt... - var bomGenList = await dbCtx - .DbSetItem - .Where(x => (x.ItemType == Core.Enums.ItemClassType.Bom || x.ItemType == Core.Enums.ItemClassType.BomAlt)) - .ToListAsync(); - - // ciclo! - foreach (var currRec in offRowList) + // Wrap in transaction for atomicity (multi-row update in loop) + await using var tx = dbCtx.Database.BeginTransaction(); + try { - // se contiene qualcosa x BOM... - if (!string.IsNullOrEmpty(currRec.ItemBOM) && currRec.ItemBOM.Length > 2) + // recupero righe Orderta... + var offRowList = await dbCtx + .DbSetOrderRow + .Where(x => x.OrderID == OrderID) + .ToListAsync(); + + // recupero l'elenco degli itemGroup gestiti + var itemGroupList = await dbCtx + .DbSetItemGroup + .ToListAsync(); + + // recupero il subset item da BOM / BomAlt... + var bomGenList = await dbCtx + .DbSetItem + .Where(x => (x.ItemType == Core.Enums.ItemClassType.Bom || x.ItemType == Core.Enums.ItemClassType.BomAlt)) + .ToListAsync(); + + // ciclo! + foreach (var currRec in offRowList) { - // deserializzo - var bomList = JsonConvert.DeserializeObject>(currRec.ItemBOM); - // se ho trovato elementi... - if (bomList != null) + // se contiene qualcosa x BOM... + if (!string.IsNullOrEmpty(currRec.ItemBOM) && currRec.ItemBOM.Length > 2) { - // calcolo il NUOVO costo e lo aggiorno... - double totCost = 0; - double totPrice = 0; - int totItemQty = 0; - int numGroupOk = 0; - int numItemOk = 0; - int numElems = bomList.Count; - // validazione e completamento BOM - BomCalculator.Validate(itemGroupList, bomGenList, ref bomList, null, ref totCost, ref totPrice, ref totItemQty, ref numGroupOk, ref numItemOk); - // salvo BOM... - string itemBom = JsonConvert.SerializeObject(bomList); - currRec.ItemBOM = itemBom; - // salvo arrotondato alla 3° decimale - currRec.BomCost = Math.Round(totCost, 3); - currRec.BomPrice = Math.Round(totPrice, 3); - currRec.BomOk = numElems == numGroupOk; - currRec.ItemOk = numElems == numItemOk; - currRec.ProdItemQty = totItemQty; - dbCtx.Entry(currRec).State = EntityState.Modified; + // deserializzo + var bomList = JsonConvert.DeserializeObject>(currRec.ItemBOM); + // se ho trovato elementi... + if (bomList != null) + { + // calcolo il NUOVO costo e lo aggiorno... + double totCost = 0; + double totPrice = 0; + int totItemQty = 0; + int numGroupOk = 0; + int numItemOk = 0; + int numElems = bomList.Count; + // validazione e completamento BOM + BomCalculator.Validate(itemGroupList, bomGenList, ref bomList, null, ref totCost, ref totPrice, ref totItemQty, ref numGroupOk, ref numItemOk); + // salvo BOM... + string itemBom = JsonConvert.SerializeObject(bomList); + currRec.ItemBOM = itemBom; + // salvo arrotondato alla 3° decimale + currRec.BomCost = Math.Round(totCost, 3); + currRec.BomPrice = Math.Round(totPrice, 3); + currRec.BomOk = numElems == numGroupOk; + currRec.ItemOk = numElems == numItemOk; + currRec.ProdItemQty = totItemQty; + dbCtx.Entry(currRec).State = EntityState.Modified; + } } } - } - return await dbCtx.SaveChangesAsync() > 0; + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } #endregion Public Methods diff --git a/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs b/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs index f4ff6fa0..9a1e9cba 100644 --- a/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs @@ -26,29 +26,44 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils { await using var dbCtx = await CreateContextAsync(); - // 1. Recupero il record da eliminare - var dbResult = await dbCtx.DbSetGenVal - .FirstOrDefaultAsync(x => x.GenValID == rec2del.GenValID); - - if (dbResult == null) - return false; - - // 2. Recupero i record successivi da shiftare - var list2Move = await dbCtx.DbSetGenVal - .Where(x => x.ClassCod == rec2del.ClassCod && x.Index > dbResult.Index) - .ToListAsync(); - - foreach (var item in list2Move) + // Wrap in transaction for atomicity (multi-row update + delete) + await using var tx = dbCtx.Database.BeginTransaction(); + try { - item.Index--; - dbCtx.Entry(item).State = EntityState.Modified; + // 1. Recupero il record da eliminare + var dbResult = await dbCtx.DbSetGenVal + .FirstOrDefaultAsync(x => x.GenValID == rec2del.GenValID); + + if (dbResult == null) + return false; + + // 2. Recupero i record successivi da shiftare + var list2Move = await dbCtx.DbSetGenVal + .Where(x => x.ClassCod == rec2del.ClassCod && x.Index > dbResult.Index) + .ToListAsync(); + + foreach (var item in list2Move) + { + item.Index--; + dbCtx.Entry(item).State = EntityState.Modified; + } + + // 3. Rimuovo il record + dbCtx.DbSetGenVal.Remove(dbResult); + + // 4. Salvo tutto + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; } - - // 3. Rimuovo il record - dbCtx.DbSetGenVal.Remove(dbResult); - - // 4. Salvo tutto - return await dbCtx.SaveChangesAsync() > 0; } public async Task GetByIdAsync(int Id) @@ -69,40 +84,56 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils public async Task MoveAsync(GenValueModel selRec, bool moveUp) { await using var dbCtx = await CreateContextAsync(); - // 1. Recupero il record corrente - var currRec = await dbCtx.DbSetGenVal - .FirstOrDefaultAsync(x => x.GenValID == selRec.GenValID); - if (currRec == null) - return false; + // Wrap in transaction for atomicity (multi-row update - swap positions) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // 1. Recupero il record corrente + var currRec = await dbCtx.DbSetGenVal + .FirstOrDefaultAsync(x => x.GenValID == selRec.GenValID); - // 2. Numero totale record della classe - int numRec = await dbCtx.DbSetGenVal - .CountAsync(x => x.ClassCod == selRec.ClassCod); + if (currRec == null) + return false; - // 3. Calcolo nuova posizione - int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1; + // 2. Numero totale record della classe + int numRec = await dbCtx.DbSetGenVal + .CountAsync(x => x.ClassCod == selRec.ClassCod); - bool canMove = moveUp ? newPos > 0 : newPos <= numRec; - if (!canMove) - return false; + // 3. Calcolo nuova posizione + int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1; - // 4. Recupero il record da scambiare - var otherRec = await dbCtx.DbSetGenVal - .FirstOrDefaultAsync(x => x.ClassCod == selRec.ClassCod && x.Index == newPos); + bool canMove = moveUp ? newPos > 0 : newPos <= numRec; + if (!canMove) + return false; - if (otherRec == null) - return false; + // 4. Recupero il record da scambiare + var otherRec = await dbCtx.DbSetGenVal + .FirstOrDefaultAsync(x => x.ClassCod == selRec.ClassCod && x.Index == newPos); - // 5. Swap indici - otherRec.Index = currRec.Index; - currRec.Index = newPos; + if (otherRec == null) + return false; - dbCtx.Entry(otherRec).State = EntityState.Modified; - dbCtx.Entry(currRec).State = EntityState.Modified; + // 5. Swap indici + otherRec.Index = currRec.Index; + currRec.Index = newPos; - // 6. Salvo - return await dbCtx.SaveChangesAsync() > 0; + dbCtx.Entry(otherRec).State = EntityState.Modified; + dbCtx.Entry(currRec).State = EntityState.Modified; + + // 6. Salvo + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } } public async Task UpdateAsync(GenValueModel entity)