diff --git a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs index 373a4513..72afc345 100644 --- a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs +++ b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs @@ -294,6 +294,7 @@ namespace EgwCoreLib.Lux.Data.Controllers return dbResult; } +#if false /// /// Esegue il cloning completo di un offerta e di TUTTE le relative righe di offerta... /// @@ -456,6 +457,130 @@ namespace EgwCoreLib.Lux.Data.Controllers return dbResult; } + /// + /// Effettua update dei costi di tutte le righe dell'offerta indicata + /// + /// + /// + internal async Task OffertUpdateCost(int OfferID) + { + bool answ = false; + //using (DataLayerContext dbCtx = new DataLayerContext(_config)) + using (DataLayerContext dbCtx = new DataLayerContext()) + { + try + { + // recupero righe offerta... + var offRowList = dbCtx + .DbSetOfferRow + .Where(x => x.OfferID == OfferID) + .ToList(); + + // recupero l'elenco degli itemGroup gestiti + var itemGroupList = dbCtx + .DbSetItemGroup + .ToList(); + + // recupero il subset item da BOM / BomAlt... + var bomGenList = dbCtx + .DbSetItem + .Where(x => (x.ItemType == Core.Enums.ItemClassType.Bom || x.ItemType == Core.Enums.ItemClassType.BomAlt)) + .ToList(); + + // ciclo! + foreach (var currRec in offRowList) + { + // se contiene qualcosa x BOM... + if (!string.IsNullOrEmpty(currRec.ItemBOM) && currRec.ItemBOM.Length > 2) + { + // 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; + } + } + } + + // salvo TUTTI i cambiamenti... + var result = await dbCtx.SaveChangesAsync(); + answ = result > 0; + } + catch (Exception exc) + { + Log.Error($"Eccezione durante OffertUpdateCost{Environment.NewLine}{exc}"); + } + } + return answ; + } + + /// + /// Add record offerta + /// + /// + /// + internal async Task OffertUpsert(OfferModel updRec) + { + bool answ = false; + //using (DataLayerContext dbCtx = new DataLayerContext(_config)) + using (DataLayerContext dbCtx = new DataLayerContext()) + { + try + { + // recupero offerta... + var currRec = dbCtx + .DbSetOffer + .Where(x => x.OfferID == updRec.OfferID) + .FirstOrDefault(); + + // se non trovo aggiungo + if (currRec == null) + { + dbCtx.DbSetOffer.Add(updRec); + } + // altrimenti aggiorno + else + { + // verifico eventuale riapertura SE fosse expired ma la data è valida... + if (currRec.OffertState == OfferStates.Expired && updRec.ValidUntil > DateTime.Today) + { + updRec.OffertState = OfferStates.Open; + } + dbCtx.Entry(currRec).CurrentValues.SetValues(updRec); + } + + // salvo TUTTI i cambiamenti... + var result = await dbCtx.SaveChangesAsync(); + answ = result > 0; + } + catch (Exception exc) + { + Log.Error($"Eccezione durante OffertUpsert{Environment.NewLine}{exc}"); + } + } + return answ; + } +#endif + /// /// Recupera da DB riga offerta dato Primary ID /// @@ -928,128 +1053,7 @@ namespace EgwCoreLib.Lux.Data.Controllers return answ; } - /// - /// Effettua update dei costi di tutte le righe dell'offerta indicata - /// - /// - /// - internal async Task OffertUpdateCost(int OfferID) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - // recupero righe offerta... - var offRowList = dbCtx - .DbSetOfferRow - .Where(x => x.OfferID == OfferID) - .ToList(); - // recupero l'elenco degli itemGroup gestiti - var itemGroupList = dbCtx - .DbSetItemGroup - .ToList(); - - // recupero il subset item da BOM / BomAlt... - var bomGenList = dbCtx - .DbSetItem - .Where(x => (x.ItemType == Core.Enums.ItemClassType.Bom || x.ItemType == Core.Enums.ItemClassType.BomAlt)) - .ToList(); - - // ciclo! - foreach (var currRec in offRowList) - { - // se contiene qualcosa x BOM... - if (!string.IsNullOrEmpty(currRec.ItemBOM) && currRec.ItemBOM.Length > 2) - { - // 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; - } - } - } - - // salvo TUTTI i cambiamenti... - var result = await dbCtx.SaveChangesAsync(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante OffertUpdateCost{Environment.NewLine}{exc}"); - } - } - return answ; - } - - /// - /// Add record offerta - /// - /// - /// - internal async Task OffertUpsert(OfferModel updRec) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - // recupero offerta... - var currRec = dbCtx - .DbSetOffer - .Where(x => x.OfferID == updRec.OfferID) - .FirstOrDefault(); - - // se non trovo aggiungo - if (currRec == null) - { - dbCtx.DbSetOffer.Add(updRec); - } - // altrimenti aggiorno - else - { - // verifico eventuale riapertura SE fosse expired ma la data è valida... - if (currRec.OffertState == OfferStates.Expired && updRec.ValidUntil > DateTime.Today) - { - updRec.OffertState = OfferStates.Open; - } - dbCtx.Entry(currRec).CurrentValues.SetValues(updRec); - } - - // salvo TUTTI i cambiamenti... - var result = await dbCtx.SaveChangesAsync(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante OffertUpsert{Environment.NewLine}{exc}"); - } - } - return answ; - } /// /// Esegue upsert del record offerta data la BOM ricevuta diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs new file mode 100644 index 00000000..0dcf1a4a --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs @@ -0,0 +1,36 @@ +using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Data.DbModel.Sales; + +namespace EgwCoreLib.Lux.Data.Repository.Sales +{ + public interface IOfferRepository : IBaseRepository + { + #region Public Methods + + Task AddAsync(OfferModel entity); + + Task CloneAsync(OfferModel rec2clone); + + Task DeleteAsync(OfferModel entity); + + Task> GetAllAsync(); + + Task> GetBomItemsAsync(); + + Task GetByIdAsync(int recId); + + Task> GetFiltAsync(DateTime inizio, DateTime fine); + + Task> GetItemGroupsAsync(); + + Task> GetRowsAsync(int recId); + + Task SaveRowsAsync(List rows); + + Task UpdateAsync(OfferModel entity); + + Task UpdateCostAsync(int OfferID); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRepository.cs new file mode 100644 index 00000000..2ec401ae --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRepository.cs @@ -0,0 +1,21 @@ +using EgwCoreLib.Lux.Data.DbModel.Sales; + +namespace EgwCoreLib.Lux.Data.Repository.Sales +{ + public interface IOrderRepository : IBaseRepository + { + #region Public Methods + + Task AddAsync(OrderModel entity); + + Task DeleteAsync(OrderModel entity); + + Task> GetAllAsync(); + + Task GetByIdAsync(int recId); + + Task UpdateAsync(OrderModel entity); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs new file mode 100644 index 00000000..d36cb5fd --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs @@ -0,0 +1,270 @@ +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Data.DbModel.Sales; +using EgwCoreLib.Lux.Data.Domains; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using static EgwCoreLib.Lux.Core.Enums; + +namespace EgwCoreLib.Lux.Data.Repository.Sales +{ + public class OfferRepository : BaseRepository, IOfferRepository + { + #region Public Constructors + + public OfferRepository(IDbContextFactory ctxFactory) : base(ctxFactory) + { + } + + #endregion Public Constructors + + #region Public Methods + + public async Task AddAsync(OfferModel entity) + { + await using var dbCtx = await CreateContextAsync(); + await dbCtx.DbSetOffer.AddAsync(entity); + return await dbCtx.SaveChangesAsync() > 0; + } + + /// + /// Esegue il cloning completo di un Offerta e di TUTTE le relative righe... + /// + /// + /// + 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() + { + 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 + }; + + // 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(); + + // 4. Aggiungo il nuovo parent (EF aggiunge anche i child) + dbCtx.DbSetOffer.Add(newRec); + + // 5. Salvo tutto + return await dbCtx.SaveChangesAsync() > 0; + } + + public async Task DeleteAsync(OfferModel entity) + { + await using var dbCtx = await CreateContextAsync(); + dbCtx.DbSetOffer.Remove(entity); + return await dbCtx.SaveChangesAsync() > 0; + } + + public async Task> GetAllAsync() + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetOffer + .Include(c => c.CustomerNav) + .Include(d => d.DealerNav) + .Include(o => o.OfferRowNav) + .AsNoTracking() + .ToListAsync(); + } + + 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 recId) + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetOffer.FirstOrDefaultAsync(x => x.OfferID == recId); + } + + public async Task> GetFiltAsync(DateTime inizio, DateTime fine) + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetOffer + .Where(x => x.Inserted >= inizio && x.Inserted <= fine) + .Include(c => c.CustomerNav) + .Include(d => d.DealerNav) + .Include(o => o.OfferRowNav) + .AsNoTracking() + .ToListAsync(); + } + + public async Task> GetItemGroupsAsync() + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetItemGroup.ToListAsync(); + } + + public async Task> GetRowsAsync(int recId) + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetOfferRow + .Where(x => x.OfferID == recId) + .ToListAsync(); + } + + 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; + } + + public async Task UpdateAsync(OfferModel entity) + { + await using var dbCtx = await CreateContextAsync(); + // Recuperiamo l'entità tracciata dal context + var trackedEntity = dbCtx.DbSetOffer.Local.FirstOrDefault(x => x.OfferID == entity.OfferID); + + if (trackedEntity != null) + { + // verifico eventuale riapertura SE fosse expired ma la data è valida... + if (trackedEntity.OffertState == OfferStates.Expired && entity.ValidUntil > DateTime.Today) + { + entity.OffertState = OfferStates.Open; + } + // Aggiorna i valori dell'entità tracciata con quelli della nuova + dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity); + } + else + { + dbCtx.DbSetOffer.Update(entity); + } + return await dbCtx.SaveChangesAsync() > 0; + } + + public async Task UpdateCostAsync(int OfferID) + { + await using var dbCtx = await CreateContextAsync(); + + // recupero righe offerta... + var offRowList = await dbCtx + .DbSetOfferRow + .Where(x => x.OfferID == OfferID) + .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) + { + // se contiene qualcosa x BOM... + if (!string.IsNullOrEmpty(currRec.ItemBOM) && currRec.ItemBOM.Length > 2) + { + // 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; + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs index af9a5312..bb19d526 100644 --- a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs +++ b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs @@ -316,6 +316,7 @@ namespace EgwCoreLib.Lux.Data.Services return result; } +#if false /// /// Esegue clone completo dell'offerta indicata e svuota la cache /// @@ -404,6 +405,65 @@ namespace EgwCoreLib.Lux.Data.Services return result; } + /// + /// Effettua update dei costi di tutte le righe dell'offerta indicata + /// + /// Key + /// + public async Task OffertUpdateCost(int OfferID) + { + using var activity = StartActivity(); + string source = "DB+REDIS"; + // calcolo + bool fatto = await dbController.OffertUpdateCost(OfferID); + // svuoto cache... + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); + activity?.SetTag("data.source", source); + LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); + return fatto; + } + + /// + /// Effettua Add complessivo record offerta + /// + /// Key + /// + public async Task OffertUpsert(OfferModel updRec) + { + using var activity = StartActivity(); + string source = "DB+REDIS"; + // calcolo + bool fatto = await dbController.OffertUpsert(updRec); + // svuoto cache... + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); + activity?.SetTag("data.source", source); + LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); + return fatto; + } +#endif + /// + /// Effettua update dei costi di tutte le righe dell'ordine indicata + /// + /// Key + /// + public async Task OrderUpdateCost(int OrderID) + { + using var activity = StartActivity(); + bool fatto = false; + string source = "DB+REDIS"; + // calcolo +#if false + fatto = await dbController.OrderUpdateCost(OrderID); +#endif + // svuoto cache... + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); + activity?.SetTag("data.source", source); + LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); + return fatto; + } /// /// Elenco righe offerta specificata /// @@ -738,43 +798,6 @@ namespace EgwCoreLib.Lux.Data.Services return dbResult; } - /// - /// Effettua update dei costi di tutte le righe dell'offerta indicata - /// - /// Key - /// - public async Task OffertUpdateCost(int OfferID) - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - // calcolo - bool fatto = await dbController.OffertUpdateCost(OfferID); - // svuoto cache... - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return fatto; - } - - /// - /// Effettua Add complessivo record offerta - /// - /// Key - /// - public async Task OffertUpsert(OfferModel updRec) - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - // calcolo - bool fatto = await dbController.OffertUpsert(updRec); - // svuoto cache... - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return fatto; - } /// /// Restituisce record ordine + righe Ordine dato ID diff --git a/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs b/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs new file mode 100644 index 00000000..967f7235 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs @@ -0,0 +1,21 @@ +using EgwCoreLib.Lux.Data.DbModel.Sales; + +namespace EgwCoreLib.Lux.Data.Services.Sales +{ + public interface IOfferService + { + #region Public Methods + + Task CloneAsync(OfferModel rec2clone); + + Task> GetAllAsync(); + + Task> GetFiltAsync(DateTime inizio, DateTime fine); + + Task UpsertAsync(OfferModel upsRec); + + Task UpdateCostAsync(int OfferId); + + #endregion Public Methods + } +} diff --git a/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs b/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs new file mode 100644 index 00000000..c33e14bf --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs @@ -0,0 +1,193 @@ +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Sales; +using EgwCoreLib.Lux.Data.Domains; +using EgwCoreLib.Lux.Data.Repository.Sales; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace EgwCoreLib.Lux.Data.Services.Sales +{ + public class OfferService : BaseServ, IOfferService + { + #region Public Constructors + + public OfferService( + IConfiguration config, + IConnectionMultiplexer redis, + IOfferRepository repo) : base(config, redis) + { + _className = "Offer"; + _repo = repo; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Esegue il cloning completo di un Offer e di TUTTE le relative righe... + /// + /// + /// + public async Task CloneAsync(OfferModel rec2clone) + { + return await TraceAsync($"{_className}.Clone", async (activity) => + { + // eseguo clone + var result = await _repo.CloneAsync(rec2clone); + + // se eseguito, pulisco la cache correlata + if (result) + { + // Invalido sia la lista classi che eventuali dettagli correlati + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + } + return result; + }); + } + + /// + /// Elenco completo Offer da DB + /// + /// + public async Task> GetAllAsync() + { + // Uso helper TraceAsync che gestisce automaticamente StartActivity, Log e Exception tracking + return await TraceAsync($"{_className}.GetAll", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:ALL", + async () => await _repo.GetAllAsync(), + LongCache + ); + }); + } + + /// + /// Elenco completo Offer da DB + /// + /// + public async Task> GetFiltAsync(DateTime inizio, DateTime fine) + { + // Uso helper TraceAsync che gestisce automaticamente StartActivity, Log e Exception tracking + return await TraceAsync($"{_className}.GetFilt", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:Filt:{inizio:yyMMdd-HHmm}:{fine:yyMMdd-HHmm}", + async () => await _repo.GetFiltAsync(inizio, fine), + LongCache + ); + }); + } + + /// + /// Effettua update dei costi di tutte le righe del Offer indicato + /// + /// + /// + public async Task UpdateCostAsync(int OfferId) + { + return await TraceAsync($"{_className}.Upsert", async (activity) => + { + // 1. Recupero dati + var rows = await _repo.GetRowsAsync(OfferId); + var itemGroups = await _repo.GetItemGroupsAsync(); + var bomItems = await _repo.GetBomItemsAsync(); + + // 2. Calcolo costi BOM + foreach (var row in rows) + { + if (!string.IsNullOrEmpty(row.ItemBOM) && row.ItemBOM.Length > 2) + { + var bomList = JsonConvert.DeserializeObject>(row.ItemBOM); + if (bomList != null) + { + double totCost = 0; + double totPrice = 0; + int totItemQty = 0; + int numGroupOk = 0; + int numItemOk = 0; + + BomCalculator.Validate( + itemGroups, + bomItems, + ref bomList, + null, + ref totCost, + ref totPrice, + ref totItemQty, + ref numGroupOk, + ref numItemOk + ); + + row.ItemBOM = JsonConvert.SerializeObject(bomList); + row.BomCost = Math.Round(totCost, 3); + row.BomPrice = Math.Round(totPrice, 3); + row.BomOk = bomList.Count == numGroupOk; + row.ItemOk = bomList.Count == numItemOk; + row.ProdItemQty = totItemQty; + } + } + } + + // 3. Salvo + var result = await _repo.SaveRowsAsync(rows); + + if (result) + { + // 4. Invalido cache + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + } + + return result; + }); + } + + /// + /// Upsert record Offer + /// + /// + /// + public async Task UpsertAsync(OfferModel upsRec) + { + return await TraceAsync($"{_className}.Upsert", async (activity) => + { + var currRec = await _repo.GetByIdAsync(upsRec.OfferID); + + string operation = "UPDATE"; + bool success = false; + if (currRec != null) + { + success = await _repo.UpdateAsync(upsRec); + } + else + { + operation = "INSERT"; + success = await _repo.AddAsync(upsRec); + } + + activity?.SetTag("db.operation", operation); + + if (success) + { + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + } + + return success; + }); + } + + #endregion Public Methods + + #region Private Fields + + private readonly string _className; + private readonly IOfferRepository _repo; + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index 0a3d51e5..25f5c9ae 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.1.2603.1715 + 1.1.2603.1716 diff --git a/Lux.API/Program.cs b/Lux.API/Program.cs index 805d4189..32878354 100644 --- a/Lux.API/Program.cs +++ b/Lux.API/Program.cs @@ -3,12 +3,14 @@ using EgwCoreLib.Lux.Data.Repository.Config; using EgwCoreLib.Lux.Data.Repository.Cost; using EgwCoreLib.Lux.Data.Repository.Items; using EgwCoreLib.Lux.Data.Repository.Job; +using EgwCoreLib.Lux.Data.Repository.Sales; using EgwCoreLib.Lux.Data.Repository.Utils; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; using EgwCoreLib.Lux.Data.Services.Cost; using EgwCoreLib.Lux.Data.Services.Items; using EgwCoreLib.Lux.Data.Services.Job; +using EgwCoreLib.Lux.Data.Services.Sales; using EgwCoreLib.Lux.Data.Services.Utils; using Lux.API.Services; using Microsoft.EntityFrameworkCore; @@ -180,6 +182,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -195,6 +198,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Lux.UI/Components/Compo/OfferRowMan.razor.cs b/Lux.UI/Components/Compo/OfferRowMan.razor.cs index 47cf2a38..3a99ae39 100644 --- a/Lux.UI/Components/Compo/OfferRowMan.razor.cs +++ b/Lux.UI/Components/Compo/OfferRowMan.razor.cs @@ -7,6 +7,7 @@ using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.DbModel.Utils; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; +using EgwCoreLib.Lux.Data.Services.Sales; using EgwCoreLib.Lux.Data.Services.Utils; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; @@ -303,6 +304,9 @@ namespace Lux.UI.Components.Compo [Inject] private IEnvirParamService EPService { get; set; } = null!; + [Inject] + private IOfferService OService { get; set; } = default!; + [Inject] private ITemplateService TService { get; set; } = default!; @@ -881,7 +885,7 @@ namespace Lux.UI.Components.Compo await InvokeAsync(StateHasChanged); await Task.Delay(300); } - await DLService.OffertUpdateCost(OfferID); + await OService.UpdateCostAsync(OfferID); if (forceResetCalc) { await Task.Delay(300); diff --git a/Lux.UI/Components/Compo/OrderRowMan.razor.cs b/Lux.UI/Components/Compo/OrderRowMan.razor.cs index 30852b84..59db4788 100644 --- a/Lux.UI/Components/Compo/OrderRowMan.razor.cs +++ b/Lux.UI/Components/Compo/OrderRowMan.razor.cs @@ -820,7 +820,7 @@ namespace Lux.UI.Components.Compo await InvokeAsync(StateHasChanged); await Task.Delay(300); } - await DLService.OffertUpdateCost(OrderID); + await DLService.OrderUpdateCost(OrderID); if (forceResetCalc) { await Task.Delay(300); diff --git a/Lux.UI/Components/Compo/Templates/TemplateRowList.razor.cs b/Lux.UI/Components/Compo/Templates/TemplateRowList.razor.cs index e9568fec..3fa00eff 100644 --- a/Lux.UI/Components/Compo/Templates/TemplateRowList.razor.cs +++ b/Lux.UI/Components/Compo/Templates/TemplateRowList.razor.cs @@ -311,6 +311,9 @@ namespace Lux.UI.Components.Compo.Templates [Inject] private ITemplateRowService TRService { get; set; } = default!; + [Inject] + private ITemplateService TplService { get; set; } = default!; + #endregion Private Properties #region Private Methods @@ -612,7 +615,7 @@ namespace Lux.UI.Components.Compo.Templates await InvokeAsync(StateHasChanged); await Task.Delay(300); } - await DLService.OffertUpdateCost(TemplateID); + await TplService.UpdateCostAsync(TemplateID); if (forceResetCalc) { await Task.Delay(300); diff --git a/Lux.UI/Components/Pages/Offers.razor.cs b/Lux.UI/Components/Pages/Offers.razor.cs index 70b36fb4..34ad3b56 100644 --- a/Lux.UI/Components/Pages/Offers.razor.cs +++ b/Lux.UI/Components/Pages/Offers.razor.cs @@ -5,6 +5,7 @@ using EgwCoreLib.Lux.Data.DbModel.Config; using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; +using EgwCoreLib.Lux.Data.Services.Sales; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using static EgwCoreLib.Lux.Core.Enums; @@ -69,6 +70,10 @@ namespace Lux.UI.Components.Pages [Inject] protected IJSRuntime JSRuntime { get; set; } = null!; + [Inject] + private IOfferService OService { get; set; } = default!; + + [Inject] protected ProdService PService { get; set; } = null!; @@ -106,7 +111,7 @@ namespace Lux.UI.Components.Pages if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler duplicare interamente l'offerta selezionata?{Environment.NewLine}---------------------------{Environment.NewLine}Env: {rec2clone.Envir} | {rec2clone.OfferCode}{Environment.NewLine}{rec2clone.Description}{Environment.NewLine}Articoli: {rec2clone.NumItems}{Environment.NewLine}---------------------------{Environment.NewLine}Importo: {rec2clone.TotalPrice:C2}")) return; // clona intera offerta + tutte le righe... - await DLService.OfferClone(rec2clone); + await OService.CloneAsync(rec2clone); await ReloadData(); UpdateTable(); } @@ -124,7 +129,7 @@ namespace Lux.UI.Components.Pages if (!await JSRuntime.InvokeAsync("confirm", $"Vuoi uscire salvando?")) return; // salvo record - await DLService.OffertUpsert(updRec); + await OService.UpsertAsync(updRec); } protected void DoSelect(OfferModel curRec) @@ -277,7 +282,7 @@ namespace Lux.UI.Components.Pages } currRec.OffertState = newStatus; - await DLService.OffertUpsert(currRec); + await OService.UpsertAsync(currRec); await ReloadData(); UpdateTable(); } @@ -392,7 +397,7 @@ namespace Lux.UI.Components.Pages private async Task DoSave(OfferModel updRec) { // salvo record - await DLService.OffertUpsert(updRec); + await OService.UpsertAsync(updRec); // cambio step CompileStep nextStep = currStep < CompileStep.FinalCheck ? currStep + 1 : currStep; AdvStep(nextStep); @@ -443,8 +448,7 @@ namespace Lux.UI.Components.Pages private async Task ReloadData() { await DLService.OffersCheckExpired(); - //AllRecordsRedis = await GVService.OfferGetAll(); - AllRecords = await DLService.OfferGetFilt(PeriodoSel.Inizio, PeriodoSel.Fine); + AllRecords = await OService.GetFiltAsync(PeriodoSel.Inizio, PeriodoSel.Fine); DoFilter(); } diff --git a/Lux.UI/Lux.UI.csproj b/Lux.UI/Lux.UI.csproj index 82971e4b..9bef2b45 100644 --- a/Lux.UI/Lux.UI.csproj +++ b/Lux.UI/Lux.UI.csproj @@ -5,7 +5,7 @@ enable enable aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50 - 1.1.2603.1715 + 1.1.2603.1716 diff --git a/Lux.UI/Program.cs b/Lux.UI/Program.cs index cd5cfffd..ef7c5ffe 100644 --- a/Lux.UI/Program.cs +++ b/Lux.UI/Program.cs @@ -3,12 +3,14 @@ using EgwCoreLib.Lux.Data.Repository.Config; using EgwCoreLib.Lux.Data.Repository.Cost; using EgwCoreLib.Lux.Data.Repository.Items; using EgwCoreLib.Lux.Data.Repository.Job; +using EgwCoreLib.Lux.Data.Repository.Sales; using EgwCoreLib.Lux.Data.Repository.Utils; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; using EgwCoreLib.Lux.Data.Services.Cost; using EgwCoreLib.Lux.Data.Services.Items; using EgwCoreLib.Lux.Data.Services.Job; +using EgwCoreLib.Lux.Data.Services.Sales; using EgwCoreLib.Lux.Data.Services.Utils; using Lux.UI.Components; using Lux.UI.Components.Account; @@ -218,6 +220,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -233,6 +236,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index b8581f91..9e391ab0 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 1.1.2603.1715

+

Versione: 1.1.2603.1716


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index 9222caa1..a8f1f93c 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -1.1.2603.1715 +1.1.2603.1716 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index 23f5bd52..720d6002 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 1.1.2603.1715 + 1.1.2603.1716 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false