namespace EgwCoreLib.Lux.Data.Services.Sales { public class OfferRowService : BaseServ, IOfferRowService { #region Public Constructors public OfferRowService( IConfiguration config, IConnectionMultiplexer redis, IOfferRowRepository repo) : base(config, redis) { _className = "OfferRow"; _repo = repo; } #endregion Public Constructors #region Public Methods /// /// Eliminazione record /// /// /// public async Task DeleteAsync(OfferRowModel rec2del) { return await TraceAsync($"{_className}.Delete", async (activity) => { var dbResult = await _repo.GetByIdAsync(rec2del.OfferRowID); if (dbResult == null) return false; bool success = await _repo.DeleteAsync(dbResult); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua fix TipoImg righe child dell' Offer indicato /// /// Key /// public async Task FixImgTypeAsync(int offerId) { return await TraceAsync($"{_className}.FixImgType", async (activity) => { // 1. Recupero righe var rows = await _repo.GetByParentAsync(offerId); // 2. Trovo quelle da sistemare var list2fix = rows .Where(x => x.ImgType == ImageType.ND && x.CalcEnabled) .ToList(); // 3. Se non c'è nulla da fare → ritorno (nessun cambio necessario) if (list2fix.Count == 0) return true; // 5. Aggiorno i record foreach (var row in list2fix) { // se è calcolato il selling item --> img calcolata if (row.SellingItemNav != null && (row.SellingItemNav.SourceType == ItemSourceType.Jwd || row.SellingItemNav.SourceType == ItemSourceType.FileBTL)) { row.ImgType = ImageType.Calculated; } } // 6. Salvo bool success = await _repo.SaveRowsAsync(list2fix); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return true; }); } /// /// Effettua fix UID righe child del temoplate indicato e restituisce elenco UID da chiamare x refresh /// /// Key /// public async Task> FixUidAsync(int offerId) { return await TraceAsync($"{_className}.FixUid", async (activity) => { var rows = await _repo.GetByParentAsync(offerId); // 2. Trovo quelle da sistemare var list2fix = rows .Where(x => string.IsNullOrEmpty(x.OfferRowUID) || x.OfferRowUID != x.OfferRowDtx) .ToList(); // 3. Se non c'è nulla da fare → ritorno if (list2fix.Count == 0) return new List(); // 4. Preparo la lista da restituire var result = list2fix .Select(x => x.OfferRowDtx) .ToList(); // 5. Aggiorno i record foreach (var row in list2fix) row.OfferRowUID = row.OfferRowDtx; // 6. Salvo bool success = await _repo.SaveRowsAsync(list2fix); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return result; }); } public async Task GetByIdAsync(int offerRowId) { return await TraceAsync($"{_className}.GetById", async (activity) => { return await GetOrSetCacheAsync( $"{_redisBaseKey}:{_className}:ById:{offerRowId}", async () => await _repo.GetByIdAsync(offerRowId), LongCache ); }); } /// /// Elenco filtrato (parent) OfferRow da DB /// /// /// public async Task> GetByParentAsync(int offerId) { return await TraceAsync($"{_className}.GetByParent", async (activity) => { return await GetOrSetCacheAsync( $"{_redisBaseKey}:{_className}:ByParent:{offerId}", async () => await _repo.GetByParentAsync(offerId), LongCache ); }); } public async Task GetByUidAsync(string offerRowUid) { return await TraceAsync($"{_className}.GetByUid", async (activity) => { return await GetOrSetCacheAsync( $"{_redisBaseKey}:{_className}:ByUid:{offerRowUid}", async () => await _repo.GetByUidAsync(offerRowUid), LongCache ); }); } public async Task UpdateAwaitStateAsync(int OfferRowId, bool? awaitBom, bool? awaitPrice, bool flushCache = false) { return await TraceAsync($"{_className}.UpdateAwaitState", async (activity) => { var currRec = await _repo.GetByIdAsync(OfferRowId); if (currRec == null) return false; currRec.AwaitBom = awaitBom ?? currRec.AwaitBom; currRec.AwaitPrice = awaitPrice ?? currRec.AwaitPrice; activity?.SetTag("db.operation", "UPDATE"); bool success = await _repo.UpdateAsync(currRec); if (success && flushCache) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua update della BOM (esplicita) della Riga Offer /// /// ID Riga Offer da aggiornare /// BOM List esplicita /// public async Task UpdateBomAsync(int OfferRowID, List newBomList) { return await TraceAsync($"{_className}.UpdateBomById", async (activity) => { var currRec = await _repo.GetByIdAsync(OfferRowID); if (currRec == null) return false; var itemGroups = await _repo.GetItemGroupsAsync(); var bomItems = await _repo.GetBomItemsAsync(); // calcolo il NUOVO costo e lo aggiorno... double totCost = 0; double totPrice = 0; int totItemQty = 0; int numGroupOk = 0; int numItemOk = 0; int numElems = newBomList.Count; // validazione e completamento BOM BomCalculator.Validate(itemGroups, bomItems, ref newBomList, null, ref totCost, ref totPrice, ref totItemQty, ref numGroupOk, ref numItemOk); // salvo BOM... string itemBom = JsonConvert.SerializeObject(newBomList); 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; activity?.SetTag("db.operation", "UpdateBomById"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua update della BOM (esplicita) della Riga Offer /// /// UID Riga Offer da aggiornare /// BOM List esplicita /// public async Task UpdateBomAsync(string uID, List newBomList) { return await TraceAsync($"{_className}.UpdateBomByUid", async (activity) => { var currRec = await _repo.GetByUidAsync(uID); if (currRec == null) return false; var itemGroups = await _repo.GetItemGroupsAsync(); var bomItems = await _repo.GetBomItemsAsync(); // recupero la BOM list precedente var bomListPrev = JsonConvert.DeserializeObject>(currRec.ItemBOM); // calcolo il NUOVO costo e lo aggiorno... double totCost = 0; double totPrice = 0; int totItemQty = 0; int numGroupOk = 0; int numItemOk = 0; int numElems = newBomList.Count; // validazione e completamento BOM BomCalculator.Validate(itemGroups, bomItems, ref newBomList, bomListPrev, ref totCost, ref totPrice, ref totItemQty, ref numGroupOk, ref numItemOk); // salvo BOM... string itemBom = JsonConvert.SerializeObject(newBomList); 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; // setto ok await di BOM e Price currRec.AwaitBom = false; currRec.AwaitPrice = false; currRec.ProdItemQty = totItemQty; activity?.SetTag("db.operation", "UpdateBomByUid"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua update delle info legate al file per il Offer indicato /// /// Riga Offer coi dati da aggiornare /// public async Task UpdateFileDataAsync(OfferRowModel updRec) { return await TraceAsync($"{_className}.UpdateFileData", async (activity) => { var currRec = await _repo.GetByIdAsync(updRec.OfferRowID); if (currRec == null) return false; currRec.FileName = updRec.FileName; currRec.FileResource = updRec.FileResource; currRec.FileSize = updRec.FileSize; currRec.SerStruct = updRec.SerStruct; currRec.ImgType = Core.Enums.ImageType.Calculated; activity?.SetTag("db.operation", "UPDATE"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// /// public async Task UpdatePendReqAsync(int OfferRowID, Dictionary reqDict) { return await TraceAsync($"{_className}.UpdatePendReqAsync", async (activity) => { var currRec = await _repo.GetByIdAsync(OfferRowID); if (currRec == null) return false; currRec.DictPendPresRaw = JsonConvert.SerializeObject(reqDict); activity?.SetTag("db.operation", "UPDATE"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua update del valore serializzato della Riga Offer /// /// ID Riga Offer da aggiornare /// Serializzazione oggetto (es JWD) /// public async Task UpdateSerStructAsync(int OfferRowID, string serStruct) { return await TraceAsync($"{_className}.UpdateSerStruct", async (activity) => { var currRec = await _repo.GetByIdAsync(OfferRowID); if (currRec == null) return false; currRec.SerStruct = serStruct; currRec.ImgType = Core.Enums.ImageType.Calculated; activity?.SetTag("db.operation", "UPDATE"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Effettua update del valore serializzato della Riga Offer + dizionario richieste ancora da applicare (stato dirty) /// /// ID Riga Offer da aggiornare /// Serializzazione oggetto (es JWD) /// Dizionario richieste da salvare x review manuale /// public async Task UpdateSerStructDirtyAsync(int OfferRowID, string serStruct, Dictionary reqDict) { return await TraceAsync($"{_className}.UpdateSerStructDirty", async (activity) => { var currRec = await _repo.GetByIdAsync(OfferRowID); if (currRec == null) return false; currRec.SerStruct = serStruct; currRec.ImgType = Core.Enums.ImageType.Calculated; currRec.DictPendPresRaw = JsonConvert.SerializeObject(reqDict); activity?.SetTag("db.operation", "UPDATE"); bool success = await _repo.UpdateAsync(currRec); if (success) { await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return success; }); } /// /// Upsert record OfferRow /// /// /// public async Task UpsertAsync(OfferRowModel upsRec) { return await TraceAsync($"{_className}.Upsert", async (activity) => { OfferRowModel? currRec = await _repo.GetByIdAsync(upsRec.OfferRowID); string operation = "UPDATE"; bool success = false; if (currRec != null) { success = await _repo.UpdateAsync(upsRec); } else { operation = "INSERT"; success = await _repo.AddAsync(upsRec); if (!success) return null; // Return null on insert failure } activity?.SetTag("db.operation", operation); if (success) { // sistemo UID... await FixUidAsync(upsRec.OfferID); await FixImgTypeAsync(upsRec.OfferID); currRec = await _repo.GetByIdAsync(upsRec.OfferRowID); } else { // svuoto comunque cache... await ClearCacheAsync($"{_redisBaseKey}:Offer:*"); await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); } return currRec; }); } #endregion Public Methods #region Private Fields private readonly string _className; private readonly IOfferRowRepository _repo; #endregion Private Fields } }