155 Commits

Author SHA1 Message Date
Samuele Locatelli 14f0a59735 Merge branch 'release/MoveCompoGroup' 2026-03-24 16:43:07 +01:00
Samuele Locatelli 2fda59f6f0 Rimozione compo inutilizzato 2026-03-24 16:42:49 +01:00
Samuele Locatelli b0e1dcee8a Fix GenValMan/GenList 2026-03-24 16:41:19 +01:00
Samuele Locatelli d8abf28daa Spostamento compo generic 2026-03-24 16:38:41 +01:00
Samuele Locatelli 2e40246d1f Move common e order compo 2026-03-24 16:34:48 +01:00
Samuele Locatelli 1e6efc742f Continuo spostamenti offer 2026-03-24 16:29:23 +01:00
Samuele Locatelli c1db6ca7af Update spostamento componenti offerta 2026-03-24 16:25:53 +01:00
Samuele Locatelli 29765b910c Update gestione offerte + spostamento in components 2026-03-24 16:25:00 +01:00
Samuele Locatelli 742243a535 Fix gestione immagini offerta 2026-03-24 16:20:39 +01:00
Samuele Locatelli 4b5c8b2252 Update gesitone async contesto di transazione su DB 2026-03-24 16:01:34 +01:00
Samuele Locatelli c80e6dbf48 Fix gestione note offerta (inseribili dopo riga item) 2026-03-24 16:01:22 +01:00
Annamaria Sassi c071537424 Layout Template 2026-03-24 13:26:25 +01:00
Samuele Locatelli f91edeee5e Merge tag 'FixSavetemplateRow_serStruct' into develop
Completo Fix x TemplateRow che dava errori upsert SerStruct
2026-03-24 13:04:47 +01:00
Samuele Locatelli 190495b7db Merge branch 'release/FixSavetemplateRow_serStruct' 2026-03-24 13:04:28 +01:00
Samuele Locatelli 27f9277f6b Ancora fix templateRow 2026-03-24 13:03:57 +01:00
Samuele Locatelli 7ef594326d Fix interface IIDataLayerServices x metodi non riportati + Fix save TemplateRow 2026-03-24 13:03:51 +01:00
Samuele Locatelli 53e69c0b78 Completamento move DataLayerServices 2026-03-24 12:55:16 +01:00
Samuele Locatelli fe03d05930 Merge tag 'CompleteServiceMove' into develop
Completamento move dei servizi + refactor vari x ottimizzarne la
gestione
2026-03-24 12:38:06 +01:00
Samuele Locatelli e6914a4844 Merge branch 'release/CompleteServiceMove' 2026-03-24 12:37:47 +01:00
Samuele Locatelli 0685c93139 Spostamento StatsCollectServices 2026-03-24 12:31:58 +01:00
Samuele Locatelli f91920790e Move di RedisSubscriptionManager 2026-03-24 12:29:23 +01:00
Samuele Locatelli 2020eb6345 Spostamento MessagePipe in subfolder Internal 2026-03-24 12:20:44 +01:00
Samuele Locatelli e2028e0a4f Ancora fix per move RedisServices in General folder 2026-03-24 12:02:36 +01:00
Samuele Locatelli 60b054670d mOVE SERVIZI REDIS PER INIT 2026-03-24 11:59:48 +01:00
Samuele Locatelli 019d92c270 Renaming async metodi ProdService 2026-03-24 11:50:22 +01:00
Samuele Locatelli e5bc914931 Fix interfaccia prodservice 2026-03-24 11:47:25 +01:00
Samuele Locatelli ca7c964eb1 implementazioe meccasmo di lock al processing risposte su ch Pub/Sub via Redis 2026-03-24 11:40:44 +01:00
Samuele Locatelli d45bbf6584 Fix configurazioni in Async 2026-03-24 11:14:47 +01:00
Samuele Locatelli c736cbc801 Fix API proj file 2026-03-24 10:39:57 +01:00
Samuele Locatelli 946397bede Merge branch 'develop' of https://gitlab.steamware.net/etis/lux into develop 2026-03-24 10:39:03 +01:00
Samuele Locatelli f1d4c26764 Fix naming convention: Async sui reloadDAta asincroni 2026-03-24 10:39:01 +01:00
Annamaria Sassi 0e6faace39 Gestione modifica campi delivery offerta 2026-03-24 10:36:13 +01:00
Samuele Locatelli 12d1090540 Completo le modifiche del config data service in async x ottimizzare chaimate 2026-03-24 10:31:19 +01:00
Samuele Locatelli 869ec0ef1c Inizio migrazione ProdService 2026-03-24 10:22:15 +01:00
Samuele Locatelli a59e256c98 Rename metodi cancel x offerta + rimozione step final check offerta 2026-03-24 10:05:09 +01:00
Samuele Locatelli 67b8d15334 Fix metodo cancel edit offerta 2026-03-24 10:01:32 +01:00
Annamaria Sassi bea6909bee Merge branch 'develop' of https://gitlab.steamware.net/etis/lux into develop 2026-03-24 09:37:02 +01:00
Annamaria Sassi 60ecb70e38 Gestione modifica campi generali offerta 2026-03-24 09:36:55 +01:00
Samuele Locatelli a154539950 Ancora fileservice 2026-03-24 09:34:46 +01:00
Samuele Locatelli 3b3a02580e move + fix namespace per FileService 2026-03-24 09:31:30 +01:00
Samuele Locatelli 2a18895363 Aggiunta interfaccia x servizio CalcRequest 2026-03-24 09:24:04 +01:00
Samuele Locatelli 6cf54dc715 Modifica servizio CalcRequestService 2026-03-24 09:22:19 +01:00
Samuele Locatelli 02f0150d09 Fix integrazione servizio ConfigDataService 2026-03-24 08:33:51 +01:00
Samuele Locatelli d4cfe83a9e ancora fix selezione metodo 2026-03-24 08:30:59 +01:00
Samuele Locatelli 23bf2f0b65 Fix gestione ConfigDataService con interfaccia 2026-03-24 08:30:52 +01:00
Samuele Locatelli ae68fe976d Fix metodi ImageCache x minro fix 2026-03-24 08:23:15 +01:00
Samuele Locatelli 3f839c17f1 Completo move CalcRuidService 2026-03-24 07:25:55 +01:00
Samuele Locatelli 9d0c18c3b8 cleanup commenti 2026-03-24 07:18:58 +01:00
Samuele Locatelli 4c1d09942f Clenaup + move CalcRuid in fer General 2026-03-24 07:18:44 +01:00
Samuele Locatelli 32b5a94a51 Ancora review metodi da interface 2026-03-23 19:46:00 +01:00
Samuele Locatelli 17201d746f Refresh (da testare) 2026-03-23 19:39:17 +01:00
Samuele Locatelli 4cef12e37f Fix CalcRuid x interface 2026-03-23 19:39:11 +01:00
Samuele Locatelli 0e1c22f96b Reorg progetto CalcRuid 2026-03-23 19:13:11 +01:00
Samuele Locatelli 3124f08474 refresh 2026-03-23 19:01:40 +01:00
Samuele Locatelli f8441f933f Move ImageCacheServices + fix vari in cascata 2026-03-23 19:01:36 +01:00
Samuele Locatelli e987a80d29 Review metodo chiamata RestSharp x CalcRequest 2026-03-23 18:55:31 +01:00
Samuele Locatelli c5d3ee3504 Rimozione metodi inutilizzati in repository x updateCost, oltretutto antipattern 2026-03-23 18:21:11 +01:00
Samuele Locatelli 8c957edad8 Update x gestione metodi upsert 2026-03-23 17:17:31 +01:00
Samuele Locatelli 84144b4beb Fix metodi folder Offer 2026-03-23 10:25:42 +01:00
Samuele Locatelli 151224bc56 Update gestione selezione detail preview offerte 2026-03-23 09:50:40 +01:00
Samuele Locatelli b2b3ed53e1 Merge tag 'AddMigratedDataServicesResult' into develop
Completata migrazione DataServices con
- rimozione LuxControler
- Mantenimento DataLayerServices come solo facade di instradamento
- nuovo pattern repository + Services x dominio
- testing vario
2026-03-21 12:47:56 +01:00
Samuele Locatelli 29112f8f48 Merge branch 'release/AddMigratedDataServicesResult' 2026-03-21 12:47:17 +01:00
Samuele Locatelli 320d2626fb Merge branch 'feature/MigrateDataLayerService' into develop 2026-03-21 12:46:41 +01:00
Samuele Locatelli 00868a24c0 Code Cleanup commenti! 2026-03-21 12:46:11 +01:00
Samuele Locatelli db89b3d0d3 Completato cleanup metodi rimasti in DataLayer gestiti altrove 2026-03-21 12:45:14 +01:00
Samuele Locatelli a288ed6a5b Fix nome parametro batch 2026-03-21 12:38:07 +01:00
Samuele Locatelli 9388ad6493 Fix stored ProductioBatch + fix DataLayerService commenti 2026-03-21 12:20:42 +01:00
Samuele Locatelli ec58c870ea Completata dismissione LuxController!!! 2026-03-21 12:14:35 +01:00
Samuele Locatelli 4916dd42ff Migrazione controller stats e CalcRuid (complessa...) 2026-03-21 11:33:49 +01:00
Samuele Locatelli 4aa21de3b7 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-20 19:03:44 +01:00
Samuele Locatelli 1b4c4d7cb8 Continuo porting metodi 2026-03-20 19:03:32 +01:00
Samuele E. Locatelli (W11-AI) 10cecc4139 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-20 15:38:11 +01:00
Samuele E. Locatelli (W11-AI) 1cc55a6057 Aggiunta conf opencode (da valutare vs continue) 2026-03-20 15:38:05 +01:00
Samuele Locatelli 669b5b36f1 Update componente window x display corretto 2026-03-20 10:41:52 +01:00
Samuele Locatelli 252f07cb29 Continuo embedding metodi profile 2026-03-20 08:27:38 +01:00
Samuele Locatelli 07eacd022e Spostamento altri metodi... 2026-03-19 16:42:19 +01:00
Samuele Locatelli 72a880fb11 Spostamento metodi PROD da DataLayer + fix Cache namespace 2026-03-19 16:07:02 +01:00
Samuele Locatelli 0d5e3b6698 Refresh 2026-03-19 14:36:29 +01:00
Samuele Locatelli a205f4f77e Continuo implementazione Repo/Serv spostati 2026-03-19 14:36:24 +01:00
Samuele Locatelli 40193db94f Semplificazione init Repo/Servizi x DI 2026-03-19 14:36:03 +01:00
Samuele Locatelli 1d5e80df53 Code cleanup 2026-03-19 12:15:30 +01:00
Samuele Locatelli 9f8ce27816 migrazione repo/Service ProdItem x assegnazioni prodGroup / prodBatch 2026-03-19 10:13:18 +01:00
Samuele Locatelli 7b4b3294f0 Aggiunto metodi x CostDriver e ProdItem come Repo/service 2026-03-19 09:37:15 +01:00
Samuele Locatelli 85ea97634e FIX DI/inclusione ProdGroup per Repo/service in UI 2026-03-18 19:29:28 +01:00
Samuele E. Locatelli (W11-AI) 72627f8128 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 19:27:21 +01:00
Samuele E. Locatelli (W11-AI) 912af1e60e Aggiunta commenti, part I 2026-03-18 19:27:19 +01:00
Samuele Locatelli fd40a6906f Move Repo/Serv x TagModel... 2026-03-18 19:25:50 +01:00
Samuele Locatelli 2b97d85014 Completo move Repo/Serv di ProdGroup 2026-03-18 19:18:43 +01:00
Samuele Locatelli 3081e8a908 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 18:56:28 +01:00
Samuele Locatelli 0b58359db7 Completo porting ItemGroup 2026-03-18 18:56:24 +01:00
Samuele Locatelli d4f26def6f Aggiunta ItemGroup Repo/Serv 2026-03-18 18:52:59 +01:00
Samuele E. Locatelli (W11-AI) 1ec15d956d Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 18:50:42 +01:00
Samuele E. Locatelli (W11-AI) d2de5f4657 Fix null input vari 2026-03-18 18:50:37 +01:00
Samuele Locatelli 3b311a8ce1 Completo porting nuove classi Customer/Dealer 2026-03-18 18:45:21 +01:00
Samuele Locatelli 95ed44155b Update nuovi servizi (da completare) 2026-03-18 18:39:24 +01:00
Samuele Locatelli 8d9ddaa4a0 Update conf x cache GetAll lato key REDIS 2026-03-18 18:39:16 +01:00
Samuele Locatelli b375148943 Aggiunta Repo/Serv x Customer/Dealers 2026-03-18 18:38:57 +01:00
Samuele Locatelli 58f130e059 Move Repo/servizio counters (NON impiegato al momento) 2026-03-18 18:26:38 +01:00
Samuele E. Locatelli (W11-AI) 2310fa020a Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 18:11:31 +01:00
Samuele E. Locatelli (W11-AI) 2c1bf139c9 Fix controli null ingresso 2026-03-18 18:11:29 +01:00
Samuele Locatelli e3b9c6e0e7 Eliminazione commenti e fix nome servizi 2026-03-18 18:09:43 +01:00
Samuele Locatelli e73766589d Migrazione serivce/repository production 2026-03-18 18:07:02 +01:00
Samuele Locatelli f59d4c9876 Inizio aggiunta repo/servizi x ProdPlant 2026-03-18 16:20:10 +01:00
Samuele Locatelli b629b5ce1f Inizio inserimento info Production 2026-03-18 16:14:56 +01:00
Samuele Locatelli cb51e3f7fc registrazione repository/servizi orderRow 2026-03-18 16:14:47 +01:00
Samuele Locatelli 5593ddc7fc Code cleanup 2026-03-18 13:16:30 +01:00
Samuele Locatelli edaa834a9c Completato sostituzione metodi (DA TESTARE!!!) 2026-03-18 13:15:07 +01:00
Samuele Locatelli 490011bae5 Fix metodo post merge... 2026-03-18 13:05:08 +01:00
Samuele Locatelli cc9e69efda Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 13:03:29 +01:00
Samuele Locatelli 7b72561b8f Fix img calcolata x update offerta su serStruc e FileData 2026-03-18 12:58:56 +01:00
Samuele E. Locatelli (W11-AI) 0d47f1ae7c Correzioni minori update OfferRow 2026-03-18 12:58:21 +01:00
Samuele Locatelli 35ba495030 Completo implementazione base porting OrdeRow a servizio a se 2026-03-18 12:44:27 +01:00
Samuele Locatelli 274f0ed3ae Inizio aggiunta servizi OrderRow e metodi relativi (DA PROVARE) 2026-03-18 12:29:25 +01:00
Samuele Locatelli 0b3fb477dd Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 11:20:47 +01:00
Samuele Locatelli dc63e7677d Aggiungo force ricalcolo immagine (non basta maybe) 2026-03-18 11:20:45 +01:00
Samuele E. Locatelli (W11-AI) 66e8eae0f1 Aggiunta transaction x Offer, OfferRow, Order e GenVal 2026-03-18 10:59:04 +01:00
Samuele Locatelli 3964eddd45 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 10:30:02 +01:00
Samuele Locatelli b1d69ba8f8 Refresh e inizio interfaccia OrderRow 2026-03-18 10:29:59 +01:00
Samuele Locatelli 93ffb43953 Aggiunta pagina distinta x selling vs buy items 2026-03-18 10:29:39 +01:00
Samuele Locatelli fc5c4f6abd ELiminazione commenti 2026-03-18 10:29:22 +01:00
Samuele E. Locatelli (W11-AI) 36e3c59114 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 10:25:57 +01:00
Samuele E. Locatelli (W11-AI) d587ee3a45 Update transaction x jobs repository 2026-03-18 10:25:42 +01:00
Samuele Locatelli 99d64bdb60 Fix clona x servizi/prodotti vendita 2026-03-18 09:30:36 +01:00
Samuele Locatelli 0214ebec10 Merge branch 'feature/MigrateDataLayerService' of https://gitlab.steamware.net/etis/lux into feature/MigrateDataLayerService 2026-03-18 09:17:56 +01:00
Samuele Locatelli e52d64bd08 Fix nuova nota 2026-03-18 09:17:54 +01:00
Samuele E. Locatelli (W11-AI) 349cce9462 Aggiunta transactionx clone/save rows 2026-03-18 09:17:30 +01:00
Samuele Locatelli e5f0d7976a Fix button ricalcolo prezzi/BOM x offerta 2026-03-18 08:29:10 +01:00
Samuele Locatelli 273fa4fc0c Migration + fix gestione note in offerta 2026-03-18 08:12:19 +01:00
Samuele Locatelli 0813e3b2d2 Fix delete righe offerta + fix (maybe) calcolo BOM selettivo 2026-03-18 07:22:10 +01:00
Samuele Locatelli b55d30654f Primi test OrderRow sembra OK 2026-03-18 07:05:53 +01:00
Samuele Locatelli ea228a9691 Da testare ma completato porting OrderRows 2026-03-17 19:17:49 +01:00
Samuele Locatelli a6f42b827e Inizio migrazione metodi OfferRow 2026-03-17 18:33:05 +01:00
Samuele Locatelli 5b74509348 Rimozione commenti 2026-03-17 17:10:35 +01:00
Samuele Locatelli 0404435ec3 Implementazione testata ordini 2026-03-17 17:09:41 +01:00
Samuele Locatelli f5181dd4f3 Inizio implementazione Repositori x Offerte (Testata) 2026-03-17 16:25:49 +01:00
Samuele Locatelli 870cf85e3f Migrazione gestione risorse in obj separati (Repository/Service) 2026-03-17 15:23:05 +01:00
Samuele Locatelli 3ec1511aec pulizia commenti 2026-03-17 14:56:46 +01:00
Samuele Locatelli 9989c7767e Review namespace Task --> Job + fix JobStep/JobTask 2026-03-17 14:54:16 +01:00
Samuele Locatelli 7c5adc9012 Fix implementazione parametrica cache x servizi 2026-03-17 11:35:38 +01:00
Samuele Locatelli 0f61efdf58 Code cleanup 2026-03-17 11:19:21 +01:00
Samuele Locatelli 3079068c62 Implementazione repository/servizio x ITEM 2026-03-17 11:17:27 +01:00
Samuele Locatelli f70814d444 Review schema gestione repository x evitare concorrezza contesto DbContext 2026-03-17 09:15:52 +01:00
Samuele Locatelli afaa9a5516 INserito servizi, problemi primo render (due chiamate sovrapposte da gestire?) 2026-03-16 19:09:37 +01:00
Samuele Locatelli 8195f63702 Aggiunta repository x Glass, Profile, Wood e servizi (da integrare) 2026-03-16 18:47:16 +01:00
Samuele Locatelli ac6695e7b0 Completato porting/estrazione Template e Template Row da DataLayer 2026-03-16 11:27:33 +01:00
Samuele Locatelli 2e05e8cac6 ELiminazione vecchio metodo validazione bom embedded in DataLayer 2026-03-16 09:41:54 +01:00
Samuele Locatelli 9e1946c636 Code cleanup 2026-03-16 09:38:57 +01:00
Samuele Locatelli d560cf97d9 Impiego nuovi servizi implementati 2026-03-16 09:37:26 +01:00
Samuele Locatelli 6a1e5ff3f3 implementazione servizi x gestione Template 2026-03-16 09:37:18 +01:00
Samuele Locatelli 5d656f2dce Spostamento area BomCalculator 2026-03-16 09:36:50 +01:00
Samuele Locatelli 179d358604 Inizio implementazione template service (DA TESTARE e spostare fuori) 2026-03-14 17:17:27 +01:00
Samuele Locatelli 26f64d1ecf inizio porting template
- oggetto statico BomCalculator
- sezioni #if true in LuxController
- creazione Repository
2026-03-14 17:10:06 +01:00
Samuele Locatelli 8897c3cc3b Riorganizzazione area Template in Catalog x estrazione metodi 2026-03-14 16:29:27 +01:00
Samuele Locatelli 2588ef155c Fix filtro logging da console 2026-03-14 16:21:22 +01:00
Samuele Locatelli ae634079f9 Completo spostamento Utils.GenClass eUtils. GenVal da oggetti generici a repository/servizi specializzati e separati 2026-03-14 16:09:11 +01:00
259 changed files with 21231 additions and 14910 deletions
+2 -2
View File
@@ -8,8 +8,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Egw.Lux.WebWindow.Base" Version="3.1.3.916" />
<PackageVersion Include="Egw.Lux.WebWindowComplex" Version="3.1.3.916" />
<PackageVersion Include="Egw.Lux.WebWindow.Base" Version="3.1.3.2010" />
<PackageVersion Include="Egw.Lux.WebWindowComplex" Version="3.1.3.2010" />
<PackageVersion Include="Egw.Window.Data" Version="2.8.1.2611" />
<PackageVersion Include="EgwCoreLib.Razor" Version="1.5.2511.312" />
<PackageVersion Include="EgwCoreLib.Utils" Version="1.5.2511.312" />
+39
View File
@@ -0,0 +1,39 @@
using Newtonsoft.Json;
namespace EgwCoreLib.Lux.Core
{
public static class CloneExtensions
{
#region Public Methods
/// <summary>
/// Clone profondo tramite serializzazione/deserializzazione di obj generici
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T DeepClone<T>(this T obj)
{
// Configurazione serializzatore JSON per risolvere errore di loop circolare
var JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var json = JsonConvert.SerializeObject(obj, JSSettings);
return JsonConvert.DeserializeObject<T>(json)!;
}
/// <summary>
/// Comparatore statico tra entità
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="original"></param>
/// <param name="edited"></param>
/// <returns></returns>
public static bool IsChanged<T>(T original, T edited) => !EqualityComparer<T>.Default.Equals(original, edited);
#endregion Public Methods
}
}
+62 -19
View File
@@ -4,6 +4,19 @@
{
#region Public Enums
/// <summary>
/// Stato compilazione offerta
/// </summary>
public enum CompileStep
{
Draft = 0,
Header = 1,
General,
Rows,
Delivery,
FinalCheck
}
public enum DisplayMode
{
Standard,
@@ -44,6 +57,27 @@
JobCycle
}
/// <summary>
/// Tipologia immagine
/// </summary>
public enum ImageType
{
/// <summary>
/// Non definita (da calcolare...)
/// </summary>
ND = 0,
/// <summary>
/// Calcolata (es JWD, BTL)
/// </summary>
Calculated,
/// <summary>
/// Fissa (tipicamente prodotto da rivendita/servizio)
/// </summary>
Fixed
}
/// <summary>
/// Tipologia item (classe/natura articolo)
/// </summary>
@@ -77,25 +111,6 @@
BomAlt
}
/// <summary>
/// Tipologia immagine
/// </summary>
public enum ImageType
{
/// <summary>
/// Non definita (da calcolare...)
/// </summary>
ND = 0,
/// <summary>
/// Calcolata (es JWD, BTL)
/// </summary>
Calculated,
/// <summary>
/// Fissa (tipicamente prodotto da rivendita/servizio)
/// </summary>
Fixed
}
/// <summary>
/// Tipologia item per Source (modalità costruzione)
/// </summary>
@@ -205,6 +220,34 @@
MACHINABLE = 1,
}
/// <summary>
/// Tipo di code del Prod
/// </summary>
public enum ProdQueueType
{
waiting,
running,
done
}
/// <summary>
/// Modalità raggruppamento (giornalieri, orari...)
/// </summary>
public enum RuidGroupMode
{
Day,
Hour
}
/// <summary>
/// Tipo di dati raggruppamento gestiti
/// </summary>
public enum RuidTagMode
{
Envir,
Mode
}
#endregion Public Enums
#if false
@@ -0,0 +1,8 @@
namespace EgwCoreLib.Lux.Core.Generic
{
public class EditStepDto
{
public Enums.CompileStep SrcStep { get; set; }
public bool Changed { get; set; } = false;
}
}
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,11 +1,12 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.DbModel.Config;
using EgwCoreLib.Lux.Data.DbModel.Cost;
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Lux.Data.DbModel.Stock;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using EgwCoreLib.Lux.Data.DbModel.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
@@ -0,0 +1,119 @@
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.Production;
using EgwCoreLib.Lux.Data.Repository.Sales;
using EgwCoreLib.Lux.Data.Repository.Stats;
using EgwCoreLib.Lux.Data.Repository.Utils;
using EgwCoreLib.Lux.Data.Services.Config;
using EgwCoreLib.Lux.Data.Services.Cost;
using EgwCoreLib.Lux.Data.Services.General;
using EgwCoreLib.Lux.Data.Services.Internal;
using EgwCoreLib.Lux.Data.Services.Items;
using EgwCoreLib.Lux.Data.Services.Job;
using EgwCoreLib.Lux.Data.Services.Production;
using EgwCoreLib.Lux.Data.Services.Sales;
using EgwCoreLib.Lux.Data.Services.Stats;
using EgwCoreLib.Lux.Data.Services.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace EgwCoreLib.Lux.Data
{
public static class DataServiceCollectionExtensions
{
public static IServiceCollection AddLuxData(this IServiceCollection services, string connectionString)
{
//// DbContextFactory: preferibile in Blazor Server e scenari concorrenti
//services.AddDbContextFactory<DataLayerContext>(options =>
// options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
// servizi preliminari
//services.TryAddSingleton<IConnectionMultiplexer>(redisConn);
services.TryAddSingleton<IRedisService, RedisService>();
services.TryAddSingleton<RedisSubscriptionManager>();
// Repository Scoped
services.TryAddScoped<IConfGlassRepository, ConfGlassRepository>();
services.TryAddScoped<IConfProfileRepository, ConfProfileRepository>();
services.TryAddScoped<IConfWoodRepository, ConfWoodRepository>();
services.TryAddScoped<ICostDriverRepository, CostDriverRepository>();
services.TryAddScoped<ICustomerRepository, CustomerRepository>();
services.TryAddScoped<IDealerRepository, DealerRepository>();
services.TryAddScoped<IEnvirParamRepository, EnvirParamRepository>();
services.TryAddScoped<IGenClassRepository, GenClassRepository>();
services.TryAddScoped<IGenValRepository, GenValRepository>();
services.TryAddScoped<IItemGroupRepository, ItemGroupRepository>();
services.TryAddScoped<IItemRepository, ItemRepository>();
services.TryAddScoped<IJobStepRepository, JobStepRepository>();
services.TryAddScoped<IJobTaskRepository, JobTaskRepository>();
services.TryAddScoped<IOfferRepository, OfferRepository>();
services.TryAddScoped<IOfferRowRepository, OfferRowRepository>();
services.TryAddScoped<IOrderRepository, OrderRepository>();
services.TryAddScoped<IOrderRowRepository, OrderRowRepository>();
services.TryAddScoped<IPhaseRepository, PhaseRepository>();
services.TryAddScoped<IProductionBatchRepository, ProductionBatchRepository>();
services.TryAddScoped<IProductionGroupRepository, ProductionGroupRepository>();
services.TryAddScoped<IProductionItemRepository, ProductionItemRepository>();
services.TryAddScoped<IProductionOdlRepository, ProductionOdlRepository>();
services.TryAddScoped<IProductionPlantRepository, ProductionPlantRepository>();
services.TryAddScoped<IResourceRepository, ResourceRepository>();
services.TryAddScoped<ISellingItemRepository, SellingItemRepository>();
services.TryAddScoped<IStatsAggrRepository, StatsAggrRepository>();
services.TryAddScoped<IStatsDetailRepository, StatsDetailRepository>();
services.TryAddScoped<ITagRepository, TagRepository>();
services.TryAddScoped<ITemplateRepository, TemplateRepository>();
services.TryAddScoped<ITemplateRowRepository, TemplateRowRepository>();
// Servizi Scoped
services.TryAddScoped<IConfGlassService, ConfGlassService>();
services.TryAddScoped<IConfProfileService, ConfProfileService>();
services.TryAddScoped<IConfWoodService, ConfWoodService>();
services.TryAddScoped<ICostDriverService, CostDriverService>();
services.TryAddScoped<ICustomerService, CustomerService>();
services.TryAddScoped<IDealerService, DealerService>();
services.TryAddScoped<IEnvirParamService, EnvirParamService>();
services.TryAddScoped<IGenClassService, GenClassService>();
services.TryAddScoped<IGenValService, GenValService>();
services.TryAddScoped<IItemService, ItemService>();
services.TryAddScoped<IItemGroupService, ItemGroupService>();
services.TryAddScoped<IJobStepService, JobStepService>();
services.TryAddScoped<IJobTaskService, JobTaskService>();
services.TryAddScoped<IOfferService, OfferService>();
services.TryAddScoped<IOfferRowService, OfferRowService>();
services.TryAddScoped<IOrderService, OrderService>();
services.TryAddScoped<IOrderRowService, OrderRowService>();
services.TryAddScoped<IPhaseService, PhaseService>();
services.TryAddScoped<IProductionGroupService, ProductionGroupService>();
services.TryAddScoped<IProductionItemService, ProductionItemService>();
services.TryAddScoped<IProductionBatchService, ProductionBatchService>();
services.TryAddScoped<IProductionOdlService, ProductionOdlService>();
services.TryAddScoped<IProductionPlantService, ProductionPlantService>();
services.TryAddScoped<IResourceService, ResourceService>();
services.TryAddScoped<ISellingItemService, SellingItemService>();
services.TryAddScoped<IStatsAggrService, StatsAggrService>();
services.TryAddScoped<IStatsDetailService, StatsDetailService>();
services.TryAddScoped<ITagService, TagService>();
services.TryAddScoped<ITemplateService, TemplateService>();
services.TryAddScoped<ITemplateRowService, TemplateRowService>();
// Facade / DataLayerService
services.TryAddScoped<IDataLayerServices, DataLayerServices>();
services.TryAddScoped<ICalcRuidService, CalcRuidService>();
//builder.Services.AddSingleton<DataLayerServices>();
//services.TryAddScoped<IDataLayerServices, DataLayerServices>();
// aggiunta servizi finali Singleton...
services.TryAddSingleton<IImageCacheService, ImageCacheService>();
services.TryAddSingleton<IConfigDataService, ConfigDataService>();
services.TryAddSingleton<ICalcRequestService, CalcRequestService>();
services.TryAddSingleton<IFileService, FileService>();
services.TryAddSingleton<IProdService, ProdService>();
//services.TryAddSingleton<CalcRequestService>();
return services;
}
}
}
@@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.DbModel.Sales
namespace EgwCoreLib.Lux.Data.DbModel.Catalog
{
/// <summary>
/// Classe dei template di oggetti gestiti
@@ -6,7 +6,7 @@ using static EgwCoreLib.Lux.Core.Enums;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Sales
namespace EgwCoreLib.Lux.Data.DbModel.Catalog
{
[Table("sales_template_row")]
public class TemplateRowModel
@@ -1,5 +1,5 @@
using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -1,4 +1,4 @@
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static EgwCoreLib.Lux.Core.Enums;
@@ -126,6 +126,12 @@ namespace EgwCoreLib.Lux.Data.DbModel.Items
get => false;
}
[NotMapped]
public bool HasBOM
{
get => SourceType == ItemSourceType.Jwd || SourceType == ItemSourceType.FileBTL;
}
/// <summary>
/// Navigazione Job/Cicli
/// </summary>
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
/// <summary>
/// Configurazione JobDriver abilitati per un Job e relativi coefficienti di conversione verso CostDriver
@@ -7,7 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
/// <summary>
/// Definizione driver di calcolo dall'Engine x la stima del CostDriver equivalente tempi/costi
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
[Table("task_job_step_item")]
public class JobStepItemModel
@@ -1,5 +1,5 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using EgwCoreLib.Lux.Data.DbModel.Utils;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -7,7 +7,7 @@ using System.ComponentModel.DataAnnotations.Schema;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
/// <summary>
/// Routing dei cicli di lavoro, con riferimento a risorse e fasi
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
[Table("task_job_step_tag")]
public class JobStepTagModel
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
/// <summary>
/// Definizione macro dei Cicli di Lavoro / Job
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
[Table("task_job_task_tag")]
public class JobTaskTagModel
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
// <Auto-Generated>
// This is here so CodeMaid doesn't reorganize this document
// </Auto-Generated>
namespace EgwCoreLib.Lux.Data.DbModel.Task
namespace EgwCoreLib.Lux.Data.DbModel.Job
{
/// <summary>
/// Fase di lavorazione / ProductionStage
@@ -1,5 +1,5 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -1,7 +1,7 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Cost;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -1,4 +1,5 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.DbModel.Items;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static EgwCoreLib.Lux.Core.Enums;
@@ -50,10 +51,15 @@ namespace EgwCoreLib.Lux.Data.DbModel.Sales
/// <summary>
/// ID dell'articolo di vendita offerto
/// </summary>
public int SellingItemID { get; set; }
public int? SellingItemID { get; set; }
/// <summary>
/// Definisce se sia calcolabile, dato il tipo SellingItem
/// </summary>
[NotMapped]
public bool IsNote => SellingItemID == null;
/// <summary>
/// Riferimento (opzionale9 al template da cui è derivato
/// Riferimento (opzionale) al template da cui è derivato
/// </summary>
public int? TemplateRowID { get; set; } = null;
@@ -64,16 +70,6 @@ namespace EgwCoreLib.Lux.Data.DbModel.Sales
public bool CalcEnabled
{
get => ImgType == ImageType.Calculated;
#if false
{
bool answ = false;
if (SellingItemNav != null)
{
answ = SellingItemNav.SourceType == ItemSourceType.Jwd || SellingItemNav.SourceType == ItemSourceType.FileBTL;
}
return answ;
}
#endif
}
/// <summary>
@@ -103,7 +99,7 @@ namespace EgwCoreLib.Lux.Data.DbModel.Sales
}
else if (SellingItemID > 0)
{
answ = SellingItemModel.ImgUrl(SellingItemID);
answ = SellingItemModel.ImgUrl(SellingItemID ?? 0);
}
}
return answ;
@@ -1,4 +1,5 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Production;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -57,7 +58,7 @@ namespace EgwCoreLib.Lux.Data.DbModel.Sales
/// <summary>
/// ID dell'articolo di vendita Orderto
/// </summary>
public int SellingItemID { get; set; }
public int? SellingItemID { get; set; }
/// <summary>
/// Riferimento (opzionale9 al template da cui è derivato
@@ -195,7 +196,7 @@ namespace EgwCoreLib.Lux.Data.DbModel.Sales
else if (SellingItemID > 0)
{
//answ = $"SP.{SellingItemID:X12}";
answ = SellingItemModel.ImgUrl(SellingItemID);
answ = SellingItemModel.ImgUrl(SellingItemID ?? 0);
}
}
return answ;
@@ -1,4 +1,4 @@
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -0,0 +1,150 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
using Newtonsoft.Json;
namespace EgwCoreLib.Lux.Data.Domains
{
public static class BomCalculator
{
#region Public Methods
public static List<BomItemDTO> GetBomList(string itemBOM)
{
List<BomItemDTO> answ = new List<BomItemDTO>();
if (!string.IsNullOrEmpty(itemBOM) && itemBOM.Length > 2)
{
var bomList = JsonConvert.DeserializeObject<List<BomItemDTO>>(itemBOM);
if (bomList != null)
{
answ = bomList;
}
}
return answ;
}
/// <summary>
/// Esegue completamento e la validazione dei dati BOM da lista articoli + gruppi,
/// validando i dati stessi
/// </summary>
/// <param name="itemGroupList">Elenco ItemGroup da considerare</param>
/// <param name="bomGenList">Item di tipo BOM/BomAlt AMMESSI</param>
/// <param name="bomList">Lista BOM ricevuta da validare</param>
/// <param name="bomList">Lista BOM precedente da confrontare x scelta alternativi</param>
/// <param name="totCost">Costo netto componenti BOM calcolato</param>
/// <param name="totPrice">Prezzo complessivo calcolato (con aggiunta marginalità)</param>
/// <param name="totItemQty">Numero toale di item dello step BOM</param>
/// <param name="numGroupOk">Controllo coerenza calcoli sui gruppi list2upd</param>
/// <param name="numItemOk">Controllo coerenza calcoli su num list2upd</param>
public static void Validate(
List<ItemGroupModel> itemGroupList,
List<ItemModel> bomGenList,
ref List<BomItemDTO> bomList,
List<BomItemDTO>? bomListPrev,
ref double totCost,
ref double totPrice,
ref int totItemQty,
ref int numGroupOk,
ref int numItemOk)
{
double margin = 0;
// ciclo x ogni elemento della BOM, cercando x gruppo e ExtItemCode
foreach (var item in bomList)
{
// init del margine
margin = 0;
// verifico item group esistente...
if (itemGroupList.Where(x => x.CodGroup == item.ClassCode).Count() > 0)
{
numGroupOk++;
}
// 2025.09.16: se il prezzo arriva dalla BOM calcolata uso quello...
if (item.Price > 0)
{
// resetto ItemID ma NON il prezzo
item.ItemID = 0;
// conto l'item
numItemOk++;
item.PriceEff = item.Price;
// dovrei recuperare margine da BOM... da rivedere, x ora cablato 20%...
margin = 0.2;
}
else
{
/*************************************************
* Ricerca costo item:
* - se ho un itemID --> cerco record ESATTO e sostituisco descrizioni & co...
* - se non ho itemID --> cerco dati di selezione + qtyRange
*************************************************/
ItemModel? recCost = null;
bool selExact = item.ItemID > 0;
if (selExact)
{
recCost = bomGenList
.Where(x => x.ItemID == item.ItemID)
.FirstOrDefault();
}
else
{
recCost = bomGenList
.Where(x => x.CodGroup == item.ClassCode
&& x.ItemIDParent == 0 // voglio NON sia un record CHILD
&& x.ExtItemCode == item.ItemCode
&& item.Qty >= x.QtyMin
&& item.Qty < x.QtyMax)
.OrderByDescending(x => x.Cost)
.FirstOrDefault();
// 2025.09.24: se ho un elenco item della BOM precedente
if (bomListPrev != null && bomListPrev.Count > 0 && recCost != null)
{
// ...cerco item trovato come PARENT di altri item
var listAlt = bomGenList.Where(x => x.ItemIDParent == recCost.ItemID).ToList();
// che cerco nella nella BOM precedente...
var result = listAlt
.Join(
bomListPrev,
l1 => l1.ItemID,
l2 => l2.ItemID,
(l1, l2) => l1)
.ToList();
// nel caso ne trovassi solo 1 uso quello al posto del recCost...
if (result.Count == 1)
{
recCost = result.FirstOrDefault();
}
}
}
// se trovato valorizzo!
if (recCost != null)
{
numItemOk++;
item.ItemID = recCost.ItemID;
//item.PriceEff = recCost.BomCost * (1 + recCost.Margin);
item.PriceEff = recCost.Cost;
// se selezione esatta sovrascrivo altri valori
if (selExact)
{
item.ItemCode = recCost.ExtItemCode;
//item.DescriptionCode = recCost.Name;
}
margin = recCost.Margin;
}
else
{
item.ItemID = 0;
item.PriceEff = 0;
}
}
// ...e aggiorno totale
totCost += item.TotalCost;
// e prezzo totale compreso margine
totPrice += item.TotalCost * (1 + margin);
totItemQty += item.ItemQty;
}
}
#endregion Public Methods
}
}
@@ -48,13 +48,13 @@
<PackageReference Include="NLog" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
<PackageReference Include="RestSharp" />
<PackageReference Include="Scrutor" />
<PackageReference Include="StackExchange.Redis" />
</ItemGroup>
<ItemGroup>
<Folder Include="DbModel\Engine\" />
<Folder Include="Migrations\" />
<Folder Include="Services\Internal\" />
</ItemGroup>
<ItemGroup>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,251 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EgwCoreLib.Lux.Data.Migrations
{
/// <inheritdoc />
public partial class EnableSellItemNullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "SellingItemID",
table: "sales_order_row",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AlterColumn<int>(
name: "SellingItemID",
table: "sales_offer_row",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 1,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 18, 7, 36, 8, 630, DateTimeKind.Local).AddTicks(2026) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 2,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 18, 7, 36, 8, 630, DateTimeKind.Local).AddTicks(2044) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 3,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 18, 7, 36, 8, 630, DateTimeKind.Local).AddTicks(2051) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 4,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 17, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 18, 7, 36, 8, 630, DateTimeKind.Local).AddTicks(2057) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 1,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 2,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 3,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 4,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 5,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 6,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 7,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 8,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 9,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 10,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 18, 0, 0, 0, 0, DateTimeKind.Local) });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "SellingItemID",
table: "sales_order_row",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "SellingItemID",
table: "sales_offer_row",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 1,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 12, 5, 41, 29, 364, DateTimeKind.Local).AddTicks(1262) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 2,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 12, 5, 41, 29, 364, DateTimeKind.Local).AddTicks(1276) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 3,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 12, 5, 41, 29, 364, DateTimeKind.Local).AddTicks(1282) });
migrationBuilder.UpdateData(
table: "sales_offer",
keyColumn: "OfferID",
keyValue: 4,
columns: new[] { "DueDateProm", "DueDateReq", "ValidUntil" },
values: new object[] { new DateTime(2026, 5, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 11, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 4, 12, 5, 41, 29, 364, DateTimeKind.Local).AddTicks(1289) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 1,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 2,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 3,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 4,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 5,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 6,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 7,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 8,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 9,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
migrationBuilder.UpdateData(
table: "sales_offer_row",
keyColumn: "OfferRowID",
keyValue: 10,
columns: new[] { "Inserted", "Modified" },
values: new object[] { new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local), new DateTime(2026, 3, 12, 0, 0, 0, 0, DateTimeKind.Local) });
}
}
}
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@ using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.DbModel.Stock;
using EgwCoreLib.Lux.Data.DbModel.Task;
using EgwCoreLib.Lux.Data.DbModel.Job;
using EgwCoreLib.Lux.Data.DbModel.Utils;
using Microsoft.EntityFrameworkCore;
@@ -1,14 +1,46 @@
namespace EgwCoreLib.Lux.Data.Repository
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository
{
public abstract class BaseRepository : IBaseRepository
{
protected readonly DataLayerContext _dbCtx;
protected BaseRepository(DataLayerContext db) => _dbCtx = db;
#region Protected Fields
protected readonly IDbContextFactory<DataLayerContext> _ctxFactory;
#endregion Protected Fields
#region Protected Constructors
protected BaseRepository(IDbContextFactory<DataLayerContext> ctxFactory)
=> _ctxFactory = ctxFactory;
#endregion Protected Constructors
#region Protected Methods
/// <summary>
/// Creazione dbcontext per singola transazione
/// </summary>
/// <returns></returns>
protected async Task<DataLayerContext> CreateContextAsync()
=> await _ctxFactory.CreateDbContextAsync();
#if false
/// <summary>
/// Salvataggio dati asincrono
/// </summary>
/// <returns></returns>
protected async Task<bool> SaveChangesAsync(DataLayerContext ctx)
=> await ctx.SaveChangesAsync() > 0;
#endif
#endregion Protected Methods
#if false
protected readonly DataLayerContext _dbCtx;
protected BaseRepository(DataLayerContext db) => _dbCtx = db;
public async Task<bool> SaveChangesAsync() => await _dbCtx.SaveChangesAsync() > 0;
#endif
}
}
}
@@ -0,0 +1,35 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.DbModel.Items;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public interface ITemplateRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(TemplateModel entity);
Task<bool> CloneAsync(TemplateModel rec2clone);
Task<int> CountChildrenAsync(int TemplateID);
Task<bool> DeleteAsync(TemplateModel entity);
Task<List<TemplateModel>> GetAllWithNavAsync();
Task<List<ItemModel>> GetBomItemsAsync();
Task<TemplateModel?> GetByIdAsync(int TemplateID);
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<List<TemplateRowModel>> GetRowsAsync(int TemplateID);
Task<bool> SaveRowsAsync(List<TemplateRowModel> rows);
Task<bool> UpdateAsync(TemplateModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public interface ITemplateRowRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(TemplateRowModel entity);
Task<bool> CloneAsync(TemplateRowModel rec2clone);
Task<bool> DeleteAsync(TemplateRowModel entity);
Task<List<TemplateRowModel>> GetAllWithNavAsync();
Task<TemplateRowModel?> GetRowAsync(int templateRowId);
Task<List<TemplateRowModel>> GetRowsAsync(int templateId);
Task<bool> SaveRowsAsync(List<TemplateRowModel> rows);
Task<bool> UpdateAsync(TemplateRowModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,203 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.DbModel.Items;
using Microsoft.EntityFrameworkCore;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public class TemplateRepository : BaseRepository, ITemplateRepository
{
#region Public Constructors
public TemplateRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(TemplateModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetTemplate.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Esegue il cloning completo di un Template e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<bool> CloneAsync(TemplateModel rec2clone)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
DateTime now = DateTime.Now;
// 1. Recupero il record originale con i child
var currRec = await dbCtx.DbSetTemplate
.Include(x => x.TemplateRowNav)
.FirstOrDefaultAsync(x => x.TemplateID == rec2clone.TemplateID);
if (currRec == null)
{
await tx.RollbackAsync();
return false;
}
// 2. Creo il nuovo parent
var newRec = new TemplateModel
{
Name = rec2clone.Name,
Envir = rec2clone.Envir,
Description = rec2clone.Description,
SourceType = rec2clone.SourceType,
};
// 3. Clono i child
newRec.TemplateRowNav = currRec.TemplateRowNav
.Select(c => new TemplateRowModel
{
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 = now,
ItemBOM = c.ItemBOM,
ItemJCD = c.ItemJCD,
ItemOk = c.ItemOk,
ItemSteps = c.ItemSteps,
ItemTags = c.ItemTags,
JobID = c.JobID,
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,
Name = c.Name,
TemplateRowUID = c.TemplateRowDtx
})
.ToList();
// 4. Aggiungo il nuovo parent (EF aggiunge anche i child)
dbCtx.DbSetTemplate.Add(newRec);
// 5. Salvo tutto in transazione
bool done = await dbCtx.SaveChangesAsync() > 0;
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<int> CountChildrenAsync(int TemplateID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplateRow.CountAsync(x => x.TemplateID == TemplateID);
}
public async Task<bool> DeleteAsync(TemplateModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetTemplate.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<TemplateModel>> GetAllWithNavAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplate
.Include(o => o.TemplateRowNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<List<ItemModel>> 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<TemplateModel?> GetByIdAsync(int TemplateID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplate.FirstOrDefaultAsync(x => x.TemplateID == TemplateID);
}
public async Task<List<ItemGroupModel>> GetItemGroupsAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup.ToListAsync();
}
public async Task<List<TemplateRowModel>> GetRowsAsync(int TemplateID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplateRow
.Where(x => x.TemplateID == TemplateID)
.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<TemplateRowModel> rows)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity when saving 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;
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> UpdateAsync(TemplateModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetTemplate.FirstOrDefaultAsync(x => x.TemplateID == entity.TemplateID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetTemplate.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,176 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public class TemplateRowRepository : BaseRepository, ITemplateRowRepository
{
#region Public Constructors
public TemplateRowRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(TemplateRowModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetTemplateRow.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Esegue il cloning completo di un Template e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<bool> CloneAsync(TemplateRowModel rec2clone)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetTemplateRow
.FirstOrDefaultAsync(x => x.TemplateRowID == rec2clone.TemplateRowID);
if (currRec == null)
{
await tx.RollbackAsync();
return false;
}
// cerco ultimo rec...
var lastRec = await dbCtx
.DbSetTemplateRow
.Where(x => x.TemplateID == rec2clone.TemplateID)
.OrderByDescending(x => x.RowNum)
.FirstOrDefaultAsync();
int rowNum = lastRec != null ? lastRec.RowNum + 1 : 1;
var newRec = new TemplateRowModel()
{
Name = rec2clone.Name,
Envir = rec2clone.Envir,
AwaitBom = rec2clone.AwaitBom,
AwaitPrice = rec2clone.AwaitPrice,
BomCost = rec2clone.BomCost,
BomOk = rec2clone.BomOk,
BomPrice = rec2clone.BomPrice,
FileName = rec2clone.FileName,
FileResource = rec2clone.FileResource,
FileSize = rec2clone.FileSize,
Inserted = rec2clone.Inserted,
ItemBOM = rec2clone.ItemBOM,
ItemJCD = rec2clone.ItemJCD,
ItemOk = rec2clone.ItemOk,
ItemSteps = rec2clone.ItemSteps,
ItemTags = rec2clone.ItemTags,
JobID = rec2clone.JobID,
Note = rec2clone.Note,
ProdItemQty = rec2clone.ProdItemQty,
Qty = rec2clone.Qty,
RowNum = rowNum,
SellingItemID = rec2clone.SellingItemID,
SellingItemNav = rec2clone.SellingItemNav,
SerStruct = rec2clone.SerStruct,
StepCost = rec2clone.StepCost,
StepFlowTime = rec2clone.StepFlowTime,
StepLeadTime = rec2clone.StepLeadTime,
StepPrice = rec2clone.StepPrice,
TemplateID = rec2clone.TemplateID,
TemplateRowUID = rec2clone.TemplateRowDtx
};
dbCtx.DbSetTemplateRow.Add(newRec);
bool done = await dbCtx.SaveChangesAsync() > 0;
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> DeleteAsync(TemplateRowModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetTemplateRow.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<TemplateRowModel>> GetAllWithNavAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplateRow
.Include(o => o.SellingItemNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<TemplateRowModel?> GetRowAsync(int templateRowId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplateRow
.FirstOrDefaultAsync(x => x.TemplateRowID == templateRowId);
}
public async Task<List<TemplateRowModel>> GetRowsAsync(int templateId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTemplateRow
.Where(x => x.TemplateID == templateId)
.Include(r => r.SellingItemNav)
.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<TemplateRowModel> rows)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity when saving 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;
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> UpdateAsync(TemplateRowModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetTemplateRow.FirstOrDefaultAsync(x => x.TemplateRowID == entity.TemplateRowID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetTemplateRow.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,64 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public class ConfGlassRepository : BaseRepository, IConfGlassRepository
{
#region Public Constructors
public ConfGlassRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(GlassModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetConfGlass.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(GlassModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetConfGlass.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<GlassModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfGlass.AsNoTracking().ToListAsync();
}
public async Task<GlassModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfGlass
.Where(x => x.GlassID == recId)
.FirstOrDefaultAsync();
}
public async Task<bool> UpdateAsync(GlassModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetConfGlass.FirstOrDefaultAsync(x => x.GlassID == entity.GlassID);
if (trackedEntity != null)
{
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetConfGlass.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,77 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public class ConfProfileRepository : BaseRepository, IConfProfileRepository
{
#region Public Constructors
public ConfProfileRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(ProfileModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetConfProfile.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> AddRangeAsync(List<ProfileModel> entityList)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetConfProfile.AddRangeAsync(entityList);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(ProfileModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetConfProfile.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<ProfileModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfProfile.AsNoTracking().ToListAsync();
}
public async Task<ProfileModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfProfile
.FirstOrDefaultAsync(x => x.ProfileID == recId);
}
public async Task<ProfileModel?> GetByUidAsync(string uID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfProfile
.FirstOrDefaultAsync(x => x.Code == uID);
}
public async Task<bool> UpdateAsync(ProfileModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetConfProfile.FirstOrDefaultAsync(x => x.ProfileID == entity.ProfileID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetConfProfile.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,67 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public class ConfWoodRepository : BaseRepository, IConfWoodRepository
{
#region Public Constructors
public ConfWoodRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(WoodModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetConfWood.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(WoodModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetConfWood.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<WoodModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfWood
.AsNoTracking()
.ToListAsync();
}
public async Task<WoodModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetConfWood
.Where(x => x.WoodID == recId)
.FirstOrDefaultAsync();
}
public async Task<bool> UpdateAsync(WoodModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetConfWood.FirstOrDefaultAsync(x => x.WoodID == entity.WoodID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetConfWood.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public class EnvirParamRepository : BaseRepository, IEnvirParamRepository
{
#region Public Constructors
public EnvirParamRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<EnvirParamModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetEnvirPar
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,21 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public interface IConfGlassRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(GlassModel entity);
Task<bool> DeleteAsync(GlassModel entity);
Task<List<GlassModel>> GetAllAsync();
Task<GlassModel?> GetByIdAsync(int recId);
Task<bool> UpdateAsync(GlassModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,21 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public interface IConfProfileRepository : IBaseRepository
{
Task<bool> AddAsync(ProfileModel entity);
Task<bool> AddRangeAsync(List<ProfileModel> entityList);
Task<bool> DeleteAsync(ProfileModel entity);
Task<List<ProfileModel>> GetAllAsync();
Task<ProfileModel?> GetByIdAsync(int recId);
Task<ProfileModel?> GetByUidAsync(string uID);
Task<bool> UpdateAsync(ProfileModel entity);
}
}
@@ -0,0 +1,18 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public interface IConfWoodRepository : IBaseRepository
{
Task<bool> AddAsync(WoodModel entity);
Task<bool> DeleteAsync(WoodModel entity);
Task<List<WoodModel>> GetAllAsync();
Task<WoodModel?> GetByIdAsync(int recId);
Task<bool> UpdateAsync(WoodModel entity);
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
namespace EgwCoreLib.Lux.Data.Repository.Config
{
public interface IEnvirParamRepository
{
Task<List<EnvirParamModel>> GetAllAsync();
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Cost
{
public class CostDriverRepository : BaseRepository, ICostDriverRepository
{
#region Public Constructors
public CostDriverRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<CostDriverModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetCostDriver
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
namespace EgwCoreLib.Lux.Data.Repository.Cost
{
public interface ICostDriverRepository
{
Task<List<CostDriverModel>> GetAllAsync();
}
}
@@ -0,0 +1,21 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
namespace EgwCoreLib.Lux.Data.Repository.Cost
{
public interface IResourceRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(ResourceModel entity);
Task<bool> DeleteAsync(ResourceModel entity);
Task<List<ResourceModel>> GetAllAsync();
Task<ResourceModel?> GetByIdAsync(int recId);
Task<bool> UpdateAsync(ResourceModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,68 @@
using EgwCoreLib.Lux.Data.DbModel.Cost;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Cost
{
public class ResourceRepository : BaseRepository, IResourceRepository
{
#region Public Constructors
public ResourceRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(ResourceModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetResource.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(ResourceModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetResource.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<ResourceModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetResource
.Include(d => d.DriverNav)
.Include(j => j.JobStepNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<ResourceModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetResource.FirstOrDefaultAsync(x => x.ResourceID == recId);
}
public async Task<bool> UpdateAsync(ResourceModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetResource.FirstOrDefaultAsync(x => x.ResourceID == entity.ResourceID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetResource.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -2,6 +2,7 @@
{
public interface IBaseRepository
{
Task<bool> SaveChangesAsync();
//Task<DataLayerContext> CreateContextAsync();
//Task<bool> SaveChangesAsync(DataLayerContext ctx);
}
}
@@ -0,0 +1,18 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public interface IItemGroupRepository
{
#region Public Methods
Task<bool> AddMissingAsync(List<BomItemDTO> bomList);
Task<bool> AddRangeAsync(List<ItemGroupModel> entityList);
Task<List<ItemGroupModel>> GetAllAsync();
#endregion Public Methods
}
}
@@ -0,0 +1,31 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public interface IItemRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(ItemModel entity);
Task<bool> DeleteAsync(ItemModel entity);
Task<ItemModel?> GetByIdAsync(int recId);
Task<List<ItemModel>> GetAltAsync(int recId);
Task<List<ItemModel>> GetFiltAsync(string CodGroup, ItemClassType ItemType);
Task<List<ItemModel>> GetSearchAsync(string term);
Task<bool> MassUpdateAsync(List<BomItemDTO> list2upd, double setCost, double defMargin, double defQtyMax, string defUM, int roundVal = 0, double scaleFactor = 1_000_000.0);
Task<bool> UpdateAsync(ItemModel entity);
Task<bool> UpsertFromBomAsync(List<BomItemDTO> bomList);
#endregion Public Methods
}
}
@@ -0,0 +1,24 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public interface ISellingItemRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(SellingItemModel entity);
Task<bool> DeleteAsync(SellingItemModel entity);
Task<SellingItemModel?> GetByIdAsync(int recId);
Task<List<SellingItemModel>> GetByEnvirAsync(EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir);
Task<List<SellingItemModel>> GetFiltAsync(EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir, ItemSourceType sourceType);
Task<bool> UpdateAsync(SellingItemModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,70 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public class ItemGroupRepository : BaseRepository, IItemGroupRepository
{
#region Public Constructors
public ItemGroupRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddMissingAsync(List<BomItemDTO> bomList)
{
await using var dbCtx = await CreateContextAsync();
bool fatto = false;
// recupero lista esistenti
var itemGroupList = await dbCtx.DbSetItemGroup
.AsNoTracking()
.ToListAsync();
// calcolo i distinct dei CodGroup x eventuale insert preventivo
List<string> distCodGroups = bomList
.Select(i => i.ClassCode)
.Distinct()
.Where(c => !string.IsNullOrWhiteSpace(c))
.ToList();
// elenco da inserire...
var codGroupsToInsert = distCodGroups
.Where(x => !itemGroupList.Any(i => i.CodGroup == x))
.Select(x => new ItemGroupModel() { CodGroup = x, Description = x })
.ToList();
// se ci sono inserisco!
if (codGroupsToInsert != null && codGroupsToInsert.Count > 0)
{
await dbCtx
.DbSetItemGroup
.AddRangeAsync(codGroupsToInsert);
// e salvo
fatto = await dbCtx.SaveChangesAsync() > 0;
}
return fatto;
}
public async Task<bool> AddRangeAsync(List<ItemGroupModel> entityList)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetItemGroup.AddRangeAsync(entityList);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<ItemGroupModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,282 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public class ItemRepository : BaseRepository, IItemRepository
{
#region Public Constructors
public ItemRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(ItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetItem.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(ItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetItem.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<ItemModel>> GetAltAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
List<ItemModel> dbResult = new List<ItemModel>();
// cerco singolo record x partire...
var currRec = dbCtx
.DbSetItem
.Where(x => x.ItemID == recId)
.FirstOrDefault();
if ((currRec != null))
{
// se è un record che ha parentId > 0 --> cerco da quello record analoghi + parent
if (currRec.ItemIDParent > 0)
{
dbResult = await dbCtx
.DbSetItem
.Where(x => x.ItemID == currRec.ItemIDParent || x.ItemIDParent == currRec.ItemIDParent)
.ToListAsync();
}
// altrimenti cerco child collegati
else
{
dbResult = await dbCtx
.DbSetItem
.Where(x => x.ItemID == currRec.ItemID || x.ItemIDParent == currRec.ItemID)
.ToListAsync();
}
}
return dbResult;
}
public async Task<ItemModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItem
.Where(x => x.ItemID == recId)
.FirstOrDefaultAsync();
}
public async Task<List<ItemModel>> GetFiltAsync(string CodGroup, ItemClassType ItemType)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItem
.Where(x => (string.IsNullOrEmpty(CodGroup) || x.CodGroup == CodGroup) && (ItemType == ItemClassType.ND || x.ItemType == ItemType))
.AsNoTracking()
.Include(g => g.ItemGroupNav)
.ToListAsync();
}
public async Task<List<ItemModel>> GetSearchAsync(string term)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItem
.Where(x => x.Description.Contains(term, StringComparison.InvariantCultureIgnoreCase) || x.ExtItemCode.Contains(term, StringComparison.InvariantCultureIgnoreCase) || x.SupplCode.Contains(term, StringComparison.InvariantCultureIgnoreCase))
.AsNoTracking()
//.Include(g => g.ItemGroupNav)
.ToListAsync();
}
public async Task<bool> MassUpdateAsync(List<BomItemDTO> list2upd, double setCost, double defMargin, double defQtyMax, string defUM, int roundVal = 0, double scaleFactor = 1_000_000.0)
{
bool answ = false;
if (list2upd == null || !list2upd.Any())
return answ;
await using var dbCtx = await CreateContextAsync();
// Validate input
if (setCost <= 0)
throw new ArgumentException("setCost must be greater than 0.");
// Step 1: Extract width and height from ExtItemCode
var itemUpdates = new List<ItemModel>();
foreach (var item in list2upd)
{
if (string.IsNullOrWhiteSpace(item.ItemCode))
continue;
// Try to parse ExtItemCode: "Pine-200.0x360.0"
if (!item.ItemCode.Contains("-") || !item.ItemCode.Contains("x"))
{
// Skip invalid format
continue;
}
// Split by "-" to get prefix and number part
var parts = item.ItemCode.Split('-', 2);
if (parts.Length < 2)
{
continue;
}
string numberPart = parts[1]; // e.g. "200.0x360.0"
// Split by "x" to get width and height
var widthHeight = numberPart.Split('x', 2);
if (widthHeight.Length < 2)
{
continue;
}
if (!double.TryParse(widthHeight[0], NumberStyles.Any, CultureInfo.InvariantCulture, out double width) ||
!double.TryParse(widthHeight[1], NumberStyles.Any, CultureInfo.InvariantCulture, out double height))
{
continue;
}
// Step 2: Calculate Cost using formula:
// Cost = setCost / 1,000,000 * width * height
double calculatedCost = (setCost / scaleFactor) * width * height;
// if requested do round ceiling
if (roundVal > 0)
{
calculatedCost = Math.Ceiling(calculatedCost / roundVal) * roundVal;
}
// Optional: you can also apply margin to cost if needed, but you said "Cost = ..."
// So we're just computing it based on width/height
// Step 4: Create a new ItemModel with updated values
var updatedItem = new ItemModel
{
ItemID = item.ItemID,
ExtItemCode = item.ItemCode, // keep original
Cost = calculatedCost,
Margin = defMargin,
QtyMax = defQtyMax,
UM = defUM
};
itemUpdates.Add(updatedItem);
}
// Step 5: Update database using EF Core (EF Core doesn't support "update" on entire list directly)
// We'll use .UpdateRange() or a raw update via .Where() and .SetProperty()
// ✅ Use EF Core's Update method (for bulk update)
if (itemUpdates.Any())
{
// Update only the ones that exist in the DB
var existingItems = await dbCtx.DbSetItem
.Where(i => itemUpdates.Select(ui => ui.ItemID).Contains(i.ItemID))
.ToListAsync();
if (existingItems.Any())
{
// Update existing records using EF Core's Update method
foreach (var updatedItem in itemUpdates)
{
var existing = existingItems.FirstOrDefault(i => i.ItemID == updatedItem.ItemID);
if (existing != null)
{
// Update only the fields we're setting
existing.Cost = updatedItem.Cost;
existing.Margin = updatedItem.Margin;
existing.QtyMax = updatedItem.QtyMax;
}
}
answ = await dbCtx.SaveChangesAsync() > 0;
}
}
return answ;
}
public async Task<bool> UpdateAsync(ItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetItem.FirstOrDefaultAsync(x => x.ItemID == entity.ItemID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetItem.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> UpsertFromBomAsync(List<BomItemDTO> bomList)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// prendo solo elementi a prezzo 0 da salvare sul DB
var item2save = bomList
.Where(x => x.Price == 0)
.ToList();
List<ItemModel> listInserted = new List<ItemModel>();
// ciclo x ogni elemento della BOM, cercando x gruppo e ExtItemCode
foreach (var item in item2save)
{
var currRec = dbCtx
.DbSetItem
.Where(x => x.CodGroup == item.ClassCode && x.ExtItemCode == item.ItemCode)
.FirstOrDefault();
// se nullo --> verifico x inserire!!!
if (currRec == null)
{
// verifico NON sia tra gli list2upd già in fase di inserimento
if (!listInserted.Any(x => x.CodGroup == item.ClassCode && x.ExtItemCode == item.ItemCode))
{
ItemModel newRec = new ItemModel()
{
CodGroup = item.ClassCode,
ItemType = Core.Enums.ItemClassType.Bom,
IsService = false,
// da calcolare meglio x gruppo
ItemCode = 0,
ExtItemCode = item.ItemCode,
SupplCode = "BOM ITEM",
Description = $"BOM | {item.ClassCode} | {item.ItemCode}",
Cost = 0,
Margin = 0,
QtyMin = 0,
QtyMax = 0,
UM = "#"
};
dbCtx.DbSetItem.Add(newRec);
listInserted.Add(newRec);
}
}
}
bool done = await dbCtx.SaveChangesAsync() > 0;
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
#endregion Public Methods
}
}
@@ -0,0 +1,80 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwMultiEngineManager.Data;
using Microsoft.EntityFrameworkCore;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Items
{
public class SellingItemRepository : BaseRepository, ISellingItemRepository
{
#region Public Constructors
public SellingItemRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(SellingItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetSellItem.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(SellingItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetSellItem.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<SellingItemModel>> GetByEnvirAsync(Constants.EXECENVIRONMENTS envir)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetSellItem
.Where(x => x.Envir == envir)
.AsNoTracking()
.ToListAsync();
}
public async Task<SellingItemModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetSellItem
.Where(x => x.SellingItemID == recId)
.FirstOrDefaultAsync();
}
public async Task<List<SellingItemModel>> GetFiltAsync(Constants.EXECENVIRONMENTS envir, ItemSourceType sourceType)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetSellItem
.Where(x => (x.Envir == envir || envir == Constants.EXECENVIRONMENTS.NULL) && (sourceType == ItemSourceType.ND || x.SourceType == sourceType))
.AsNoTracking()
.ToListAsync();
}
public async Task<bool> UpdateAsync(SellingItemModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetSellItem.FirstOrDefaultAsync(x => x.SellingItemID == entity.SellingItemID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetSellItem.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,23 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public interface IJobStepRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(JobStepModel entity);
Task<bool> DeleteAsync(JobStepModel entity);
Task<JobStepModel?> GetByIdAsync(int recId);
Task<List<JobStepModel>> GetByParentAsync(int jobID);
Task<bool> MoveAsync(JobStepModel selRec, bool moveUp);
Task<bool> UpdateAsync(JobStepModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,25 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public interface IJobTaskRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(JobTaskModel entity);
Task<bool> DeleteAsync(JobTaskModel entity);
Task<List<JobTaskModel>> GetAllAsync();
Task<JobTaskModel?> GetByIdAsync(int recId);
Task<bool> MergeTagsAsync(int JobID, List<string> reqTagList);
Task<bool> MoveAsync(JobTaskModel selRec, bool moveUp);
Task<bool> UpdateAsync(JobTaskModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public interface IPhaseRepository
{
Task<List<PhaseModel>> GetAllAsync();
}
}
@@ -0,0 +1,158 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public class JobStepRepository : BaseRepository, IJobStepRepository
{
#region Public Constructors
public JobStepRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(JobStepModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetJobStep.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(JobStepModel rec2del)
{
// Add validation for null entity
if (rec2del == null) return false;
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var dbResult = await dbCtx.DbSetJobStep
.FirstOrDefaultAsync(x => x.JobStepID == rec2del.JobStepID);
if (dbResult == null)
return false;
var list2Move = await dbCtx.DbSetJobStep
.Where(x => x.JobID == rec2del.JobID && x.Index > dbResult.Index)
.ToListAsync();
foreach (var item in list2Move)
{
item.Index--;
dbCtx.Entry(item).State = EntityState.Modified;
}
dbCtx.DbSetJobStep.Remove(dbResult);
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<JobStepModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetJobStep.FirstOrDefaultAsync(x => x.JobStepID == recId);
}
public async Task<List<JobStepModel>> GetByParentAsync(int jobID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetJobStep
.Where(x => x.JobID == jobID)
.Include(c => c.JobNav)
.Include(c => c.PhaseNav)
.Include(c => c.ResourceNav)
.Include(c => c.TagNav)
.Include(c => c.ResourceNav.DriverNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<bool> MoveAsync(JobStepModel selRec, bool moveUp)
{
// Add validation for null entity
if (selRec == null) return false;
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobStep
.FirstOrDefaultAsync(x => x.JobStepID == selRec.JobStepID);
if (currRec == null)
return false;
int numRec = await dbCtx.DbSetJobStep
.CountAsync(x => x.JobID == selRec.JobID);
int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1;
bool canMove = moveUp ? newPos > 0 : newPos <= numRec;
if (!canMove)
return false;
var otherRec = await dbCtx.DbSetJobStep
.FirstOrDefaultAsync(x => x.JobID == selRec.JobID && x.Index == newPos);
if (otherRec == null)
return false;
otherRec.Index = currRec.Index;
currRec.Index = newPos;
dbCtx.Entry(otherRec).State = EntityState.Modified;
dbCtx.Entry(currRec).State = EntityState.Modified;
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> UpdateAsync(JobStepModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetJobStep.FirstOrDefaultAsync(x => x.JobStepID == entity.JobStepID);
if (trackedEntity != null)
{
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetJobStep.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,209 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public class JobTaskRepository : BaseRepository, IJobTaskRepository
{
#region Public Constructors
public JobTaskRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(JobTaskModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetJobTask.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(JobTaskModel rec2del)
{
// Add validation for null entity
if (rec2del == null) return false;
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update + delete)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var dbResult = await dbCtx.DbSetJobTask
.FirstOrDefaultAsync(x => x.JobID == rec2del.JobID);
if (dbResult == null)
return false;
var list2Move = await dbCtx.DbSetJobTask
.Where(x => x.JobID == rec2del.JobID && x.Index > dbResult.Index)
.ToListAsync();
foreach (var item in list2Move)
{
item.Index--;
dbCtx.Entry(item).State = EntityState.Modified;
}
dbCtx.DbSetJobTask.Remove(dbResult);
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<List<JobTaskModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetJobTask
.Include(c => c.TagNav)
.Include(c => c.JobStepNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<JobTaskModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetJobTask.FirstOrDefaultAsync(x => x.JobID == recId);
}
public async Task<bool> MergeTagsAsync(int JobID, List<string> reqTagList)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row add + multi-row remove)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobTask
.Where(x => x.JobID == JobID)
.Include(t => t.TagNav)
.FirstOrDefaultAsync();
if (currRec == null)
return false;
var currentTags = currRec.TagNav.Select(t => t.CodTag).ToList();
// calcolo modifiche
var toAdd = reqTagList.Except(currentTags).ToList();
var toRemove = currentTags.Except(reqTagList).ToList();
// aggiunte
foreach (var tag in toAdd)
{
currRec.TagNav.Add(new JobTaskTagModel
{
JobID = JobID,
CodTag = tag
});
}
// rimozioni
foreach (var tag in toRemove)
{
var entity = currRec.TagNav.FirstOrDefault(t => t.CodTag == tag);
if (entity != null)
dbCtx.Remove(entity);
}
dbCtx.Entry(currRec).State = EntityState.Modified;
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> MoveAsync(JobTaskModel selRec, bool moveUp)
{
// Add validation for null entity
if (selRec == null) return false;
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (multi-row update)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var currRec = await dbCtx.DbSetJobTask
.FirstOrDefaultAsync(x => x.JobID == selRec.JobID);
if (currRec == null)
return false;
int numRec = await dbCtx.DbSetJobTask
.CountAsync();
int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1;
bool canMove = moveUp ? newPos > 0 : newPos <= numRec;
if (!canMove)
return false;
var otherRec = await dbCtx.DbSetJobTask
.FirstOrDefaultAsync(x => x.JobID == selRec.JobID && x.Index == newPos);
if (otherRec == null)
return false;
otherRec.Index = currRec.Index;
currRec.Index = newPos;
dbCtx.Entry(otherRec).State = EntityState.Modified;
dbCtx.Entry(currRec).State = EntityState.Modified;
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> UpdateAsync(JobTaskModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetJobTask.FirstOrDefaultAsync(x => x.JobID == entity.JobID);
if (trackedEntity != null)
{
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetJobTask.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Job;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Job
{
public class PhaseRepository : BaseRepository, IPhaseRepository
{
#region Public Constructors
public PhaseRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<PhaseModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetPhase
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,15 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public interface IProductionBatchRepository
{
/// <summary>
/// Creazione di un Batch con relativo Tag e info x creazione ODL correlati
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
Task<ProductionBatchModel?> CreateAsync(ProductionBatchModel entity);
}
}
@@ -0,0 +1,14 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public interface IProductionGroupRepository
{
Task<List<ProductionGroupModel>> GetByOrderRowAsync(int orderRowID);
Task<List<ProductionGroupModel>> GetByOrderStateAsync(OrderStates reqState);
Task<bool> UpsertBalanceAsync(string uID, string rGroup, string rawBalance);
}
}
@@ -0,0 +1,19 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public interface IProductionItemRepository
{
#region Public Methods
Task<int> BulkAssignProdBatchAsync(int orderRowId, int prodBatchId);
Task<int> BulkAssignProdGroupAsync(int OrderRowId, int ProdGroupId, Dictionary<string, double> itemsToAssign);
Task<List<ProductionItemModel>> GetByOrderRowAsync(int orderRowId);
Task<int> ResetAssignAsync(int orderRowID);
#endregion Public Methods
}
}
@@ -0,0 +1,55 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Data.DbModel.Production;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public interface IProductionOdlRepository
{
#region Public Methods
/// <summary>
/// Metodo aggiunta record
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
Task<bool> AddAsync(ProductionODLModel entity);
Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts);
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
/// <param name="listOdl2ins"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
Task<List<ProductionODLModel>> CreateAsync(List<ProductionODLModel> listOdl2ins);
/// <summary>
/// Recupero record ProdOdl dato Tag/uID
/// </summary>
/// <param name="uID"></param>
/// <returns></returns>
Task<ProductionODLModel?> GetByUidAsync(string uID);
/// <summary>
/// Elenco PODL non assegnati
/// </summary>
/// <returns></returns>
Task<List<ProductionODLModel>> GetUnassignAsync();
/// <summary>
/// Elenco PODL non assegnati con struttura DTO appiattita
/// </summary>
/// <returns></returns>
Task<List<OdlAssignDto>> GetUnassignOdlDtoAsync();
/// <summary>
/// Update generico record
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
Task<bool> UpdateAsync(ProductionODLModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public interface IProductionPlantRepository
{
Task<List<ProductionPlantModel>> GetAllAsync();
}
}
@@ -0,0 +1,47 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public class ProductionBatchRepository : BaseRepository, IProductionBatchRepository
{
#region Public Constructors
public ProductionBatchRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Creazione di un Batch con relativo Tag e info x creazione ODL correlati
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
public async Task<ProductionBatchModel?> CreateAsync(ProductionBatchModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Definiamo i parametri di input
var pDesc = new MySqlParameter("@pDescription", entity.Description ?? (object)DBNull.Value);
var pDate = new MySqlParameter("@pDueDate", entity.DueDate);
var pPref = new MySqlParameter("@pPrefix", "BC.");
var pYear = new MySqlParameter("@pYear", entity.DueDate.Year);
var pEnv = new MySqlParameter("@pEnv", entity.Envir);
// Eseguiamo la procedura e mappiamo il risultato direttamente sul modello
// Nota: DbSetProdBatch deve essere configurato nel DbContext
var dbResult = await dbCtx
.DbSetProdBatch
.FromSqlRaw("CALL stp_ProdBatch_insert(@pDescription, @pDueDate, @pPrefix, @pYear, @pEnv)", pDesc, pDate, pPref, pYear, pEnv)
.FirstOrDefaultAsync();
return dbResult;
}
#endregion Public Methods
}
}
@@ -0,0 +1,134 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public class ProductionGroupRepository : BaseRepository, IProductionGroupRepository
{
#region Public Constructors
public ProductionGroupRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Elenco record ProductionGroup dato OrderRow
/// </summary>
/// <returns></returns>
public async Task<List<ProductionGroupModel>> GetByOrderRowAsync(int orderRowID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdGroup
.AsNoTracking()
.Where(x => x.OrderRowID == orderRowID)
.Include(i => i.ItemsNav)
.ToListAsync();
}
/// <summary>
/// Elenco record ProductionGroup dato Stato dell'OrderRow
/// </summary>
/// <returns></returns>
public async Task<List<ProductionGroupModel>> GetByOrderStateAsync(OrderStates reqState)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdGroup
.AsNoTracking()
.Include(o => o.OrderRowNav)
.Where(x => x.OrderRowNav.OrderRowState == reqState)
.Include(i => i.ItemsNav)
.ToListAsync();
}
/// <summary>
/// Add record di un singolo ProdGroup da fase Balance
/// </summary>
/// <param name="uID">UID dell'item offerta di cui si è ricevuto l'oggetto Balance'</param>
/// <param name="rGroup">Prod Group di riferimento</param>
/// <param name="rawBalance"></param>
public async Task<bool> UpsertBalanceAsync(string uID, string rGroup, string rawBalance)
{
bool answ = false;
// Add validation for null or empty list
if (string.IsNullOrWhiteSpace(uID) ||
string.IsNullOrWhiteSpace(rGroup) ||
string.IsNullOrWhiteSpace(rawBalance))
{
return answ;
}
await using var dbCtx = await CreateContextAsync();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// Tentativo di deserializzazione
var data = JsonConvert.DeserializeObject<Dictionary<string, ProdMachineDetailDto>>(rawBalance);
// proseguo solo se è valida la deserializzazione...
if (data != null)
{
// Togliamo la 'G' e convertiamo in int (gestisce automaticamente "01" -> 1)
int grpIdx = int.Parse(rGroup.TrimStart('G'));
// recupero ord row (parent)...
var ordRowRec = dbCtx
.DbSetOrderRow
.Where(x => x.OrderRowUID == uID)
.FirstOrDefault();
if (ordRowRec != null)
{
// recupero record specifico
var currRec = dbCtx
.DbSetProdGroup
.Where(x => x.OrderRowID == ordRowRec.OrderRowID && x.GrpIdx == grpIdx)
.FirstOrDefault();
// se trovato aggiorno
if (currRec != null)
{
currRec.WorkGroupListRaw = rawBalance;
dbCtx.Entry(currRec).State = EntityState.Modified;
}
// altrimenti aggiungo
else
{
ProductionGroupModel newRec = new ProductionGroupModel()
{
OrderRowID = ordRowRec.OrderRowID,
GrpIdx = grpIdx,
WorkGroupListRaw = rawBalance
};
dbCtx
.DbSetProdGroup
.Add(newRec);
}
// segno ordine come Assigned se non lo fosse...
if (ordRowRec.OrderRowState != OrderStates.Assigned)
{
ordRowRec.OrderRowState = OrderStates.Assigned;
dbCtx.Entry(ordRowRec).State = EntityState.Modified;
}
// salvo TUTTI i cambiamenti...
answ = await dbCtx.SaveChangesAsync() > 0;
await tx.CommitAsync();
}
}
return answ;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
#endregion Public Methods
}
}
@@ -0,0 +1,117 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public class ProductionItemRepository : BaseRepository, IProductionItemRepository
{
#region Public Constructors
public ProductionItemRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdBatch parent (per ora totale x RigaOrd)
/// </summary>
/// <param name="orderRowId"></param>
/// <param name="prodBatchId"></param>
/// <returns></returns>
public async Task<int> BulkAssignProdBatchAsync(int orderRowId, int prodBatchId)
{
await using var dbCtx = await CreateContextAsync();
int totUpd = 0;
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
int rowsAffected = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == orderRowId)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdBatchID, prodBatchId)
);
totUpd += rowsAffected;
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
return totUpd;
}
/// <summary>
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdGroup parent
/// </summary>
/// <param name="orderRowId"></param>
/// <param name="prodGroupId"></param>
/// <param name="itemsToAssign"></param>
/// <returns></returns>
public async Task<int> BulkAssignProdGroupAsync(int orderRowId, int prodGroupId, Dictionary<string, double> itemsToAssign)
{
await using var dbCtx = await CreateContextAsync();
int totUpd = 0;
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var entry in itemsToAssign)
{
int rowsAffected = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == orderRowId && p.ProdItemTag == entry.Key)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdGroupID, prodGroupId)
.SetProperty(p => p.EstimTime, entry.Value)
);
totUpd += rowsAffected;
}
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
return totUpd;
}
public async Task<List<ProductionItemModel>> GetByOrderRowAsync(int orderRowId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdItem
.AsNoTracking()
.Where(x => x.OrderRowID == orderRowId)
.ToListAsync();
}
public async Task<int> ResetAssignAsync(int orderRowID)
{
await using var dbCtx = await CreateContextAsync();
int numItem = 0;
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
numItem = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == orderRowID)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdGroupID, (int?)null)
.SetProperty(p => p.EstimTime, 0)
);
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
return numItem;
}
#endregion Public Methods
}
}
@@ -0,0 +1,238 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public class ProductionOdlRepository : BaseRepository, IProductionOdlRepository
{
#region Public Constructors
public ProductionOdlRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(ProductionODLModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetProdODL.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
public async Task<int> AssignProdItem2OdlAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
int totalCreated = 0;
await using var dbCtx = await CreateContextAsync();
// 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query
List<int> batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList();
if (batchIds != null && batchIds.Count > 0)
{
// 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM)
var itemsList = await dbCtx.DbSetProdItem
.Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "")
.Select(x => new { x.ProdItemID, x.ProdItemTag })
.ToListAsync();
// 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag
// perché il filtro .Where sopra garantisce che non sia null.
var itemLookup = itemsList
.GroupBy(x => x.ProdItemTag!)
.ToDictionary(
g => g.Key,
g => g.First().ProdItemID,
StringComparer.OrdinalIgnoreCase
);
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
var relationsToInsert = new List<ProductionItem2ODLModel>();
foreach (var odl in dbList)
{
var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index);
if (dictParts.TryGetValue(key, out List<string> tagList))
{
foreach (var tag in tagList)
{
// 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale
if (itemLookup.TryGetValue(tag, out int realItemId))
{
relationsToInsert.Add(new ProductionItem2ODLModel
{
ProdODLID = odl.ProdODLID,
ProdItemID = realItemId,
DtAssign = DateTime.Now
});
}
}
}
}
if (relationsToInsert.Any())
{
await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert);
totalCreated = relationsToInsert.Count;
await dbCtx.SaveChangesAsync();
}
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
}
return totalCreated;
}
/// <summary>
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
/// </summary>
/// <param name="listOdl2ins"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<List<ProductionODLModel>> CreateAsync(List<ProductionODLModel> listOdl2ins)
{
await using var dbCtx = await CreateContextAsync();
// avvio transazione
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// verifico di avere dati
if (listOdl2ins.Count == 0)
{
return listOdl2ins;
}
int cYear = DateTime.Today.Year;
// insert in blocco
dbCtx.DbSetProdODL.AddRange(listOdl2ins);
// 3. Salvataggio massivo
// EF Core 8 ottimizzerà gli insert in batch dove possibile
await dbCtx.SaveChangesAsync();
// stored update Tags
var pProdBatchID = new MySqlParameter("@pProdBatchID", listOdl2ins.FirstOrDefault()?.ProdBatchID ?? 0);
var pPref = new MySqlParameter("@pPrefix", "ODL.");
var pYear = new MySqlParameter("@pYear", cYear);
await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdOdl_UpdateTag(@pProdBatchID, @pPrefix, @pYear)", pProdBatchID, pPref, pYear);
// 4. Conferma transazione
await tx.CommitAsync();
// A questo punto, ogni oggetto in 'listOdl2ins' ha il ProdODLID aggiornato dal DB
return listOdl2ins;
}
catch
{
await tx.RollbackAsync();
// Logga l'errore secondo le tue necessità
throw;
}
}
/// <summary>
/// Recupero record ProdOdl dato Tag/uID
/// </summary>
/// <param name="uID"></param>
/// <returns></returns>
public async Task<ProductionODLModel?> GetByUidAsync(string uID)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdODL
.AsNoTracking()
.FirstOrDefaultAsync(x => x.OdlTag == uID);
}
/// <summary>
/// Elenco PODL non assegnati
/// </summary>
/// <returns></returns>
public async Task<List<ProductionODLModel>> GetUnassignAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdODL
.Where(x => !x.DateAssign.HasValue)
.AsNoTracking()
.Include(x => x.Item2OdlNav)
.ToListAsync();
}
/// <summary>
/// Elenco PODL non assegnati con struttura DTO appiattita
/// </summary>
/// <returns></returns>
public async Task<List<OdlAssignDto>> GetUnassignOdlDtoAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdODL
.Where(x => !x.DateAssign.HasValue)
.AsNoTracking()
.Select(odl => new OdlAssignDto
{
Envir = odl.ProdBatchNav.Envir,
ProdODLID = odl.ProdODLID,
Description = odl.Description,
EstimTime = odl.EstimTime,
Index = odl.Index,
OdlTag = odl.OdlTag,
PhaseID = odl.PhaseID,
ProdBatchID = odl.ProdBatchID,
ProdPlantCod = odl.ProdPlantCod,
Qty = odl.Qty,
ResourceID = odl.ResourceID,
ItemList = odl.Item2OdlNav.Select(i => new ItemAssignDto
{
ProdItemID = i.ProdItemID,
OrderID = i.ProductionItemNav.OrderRowNav.OrderNav.OrderID,
OrderRowID = i.ProductionItemNav.OrderRowNav.OrderRowID,
OrderTag = i.ProductionItemNav.OrderRowNav.OrderNav.OrderCode,
OrderRowTag = i.ProductionItemNav.OrderRowNav.OrderRowCode,
ProdItemTag = i.ProductionItemNav.ProdItemTag ?? "***"
}).ToList()
})
.ToListAsync();
}
/// <summary>
/// Update generico record
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public async Task<bool> UpdateAsync(ProductionODLModel entity)
{
await using var dbCtx = await CreateContextAsync();
var trackedEntity = await dbCtx.DbSetProdODL.FirstOrDefaultAsync(x => x.ProdODLID == entity.ProdODLID);
if (trackedEntity != null)
{
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetProdODL.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Production;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Production
{
public class ProductionPlantRepository : BaseRepository, IProductionPlantRepository
{
#region Public Constructors
public ProductionPlantRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<ProductionPlantModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetProdPlant
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class CustomerRepository : BaseRepository, ICustomerRepository
{
#region Public Constructors
public CustomerRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<CustomerModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetCustomer
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class DealerRepository : BaseRepository, IDealerRepository
{
#region Public Constructors
public DealerRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<DealerModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetDealer
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Sales;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public interface ICustomerRepository
{
Task<List<CustomerModel>> GetAllAsync();
}
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Sales;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public interface IDealerRepository
{
Task<List<DealerModel>> GetAllAsync();
}
}
@@ -0,0 +1,37 @@
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<bool> AddAsync(OfferModel entity);
Task<bool> CheckExpiredAsync();
Task<bool> CloneAsync(OfferModel rec2clone);
Task<bool> DeleteAsync(OfferModel entity);
Task<List<OfferModel>> GetAllAsync();
Task<List<ItemModel>> GetBomItemsAsync();
Task<OfferModel?> GetByIdAsync(int recId);
Task<List<OfferModel>> GetFiltAsync(DateTime inizio, DateTime fine);
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<List<OfferRowModel>> GetRowsAsync(int recId);
Task<bool> SaveRowsAsync(List<OfferRowModel> rows);
Task<bool> UpdateAsync(OfferModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,30 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Sales;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public interface IOfferRowRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(OfferRowModel entity);
Task<bool> DeleteAsync(OfferRowModel entity);
Task<List<ItemModel>> GetBomItemsAsync();
Task<OfferRowModel?> GetByIdAsync(int offerRowId);
Task<List<OfferRowModel>> GetByParentAsync(int offerId);
Task<OfferRowModel?> GetByUidAsync(string offerRowUid);
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<bool> SaveRowsAsync(List<OfferRowModel> rows);
Task<bool> UpdateAsync(OfferRowModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,34 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Sales;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public interface IOrderRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(OrderModel entity);
Task<OrderModel?> CloneOfferAsync(OfferModel rec2clone);
Task<bool> DeleteAsync(OrderModel entity);
Task<List<OrderModel>> GetAllAsync();
Task<List<ItemModel>> GetBomItemsAsync();
Task<OrderModel?> GetByIdAsync(int recId);
Task<List<OrderModel>> GetFiltAsync(DateTime inizio, DateTime fine);
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<List<OrderRowModel>> GetRowsAsync(int recId);
Task<bool> SaveRowsAsync(List<OrderRowModel> rows);
Task<bool> UpdateAsync(OrderModel entity);
#endregion Public Methods
}
}
@@ -0,0 +1,39 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public interface IOrderRowRepository : IBaseRepository
{
#region Public Methods
Task<bool> AddAsync(OrderRowModel entity);
Task<bool> DeleteAsync(OrderRowModel entity);
Task<List<ItemModel>> GetBomItemsAsync();
Task<OrderRowModel?> GetByIdAsync(int OrderRowId);
Task<List<OrderRowModel>> GetByParentAsync(int offerId);
Task<List<OrderRowModel>> GetByStateAsync(OrderStates reqState, DateTime dtStart, DateTime dtEnd);
Task<List<OrderRowModel>> GetByStateMinAsync(OrderStates reqState, DateTime dtStart, DateTime dtEnd);
Task<OrderRowModel?> GetByUidAsync(string OrderRowUid);
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<bool> SaveProdEstAsync(string uID, string prodEstim);
Task<bool> SaveRowsAsync(List<OrderRowModel> rows);
Task<bool> UpdateAsync(OrderRowModel entity);
Task<int> ValidateAsync(List<OrderRowModel> list2chk);
#endregion Public Methods
}
}
@@ -0,0 +1,270 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class OfferRepository : BaseRepository, IOfferRepository
{
#region Public Constructors
public OfferRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(OfferModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetOffer.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> CheckExpiredAsync()
{
await using var dbCtx = await CreateContextAsync();
DateTime adesso = DateTime.Now;
// recupero offerta...
var listExpired = await dbCtx
.DbSetOffer
.Where(x => x.ValidUntil < adesso && x.OffertState == OfferStates.Open)
.ToListAsync();
// se trovo le aggiorno come stato
if (listExpired != null)
{
foreach (var item in listExpired)
{
item.OffertState = OfferStates.Expired;
dbCtx.Entry(item).State = EntityState.Modified;
}
// salvo TUTTI i cambiamenti...
return await dbCtx.SaveChangesAsync() > 0;
}
else
{
return false;
}
}
/// <summary>
/// Esegue il cloning completo di un Offerta e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<bool> CloneAsync(OfferModel rec2clone)
{
await using var dbCtx = await CreateContextAsync();
// Wrap in transaction for atomicity (parent + children clone)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
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 in transazione
await dbCtx.SaveChangesAsync();
await tx.CommitAsync();
return true;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> DeleteAsync(OfferModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetOffer.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<OfferModel>> 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<List<ItemModel>> 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<OfferModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOffer.FirstOrDefaultAsync(x => x.OfferID == recId);
}
public async Task<List<OfferModel>> 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<List<ItemGroupModel>> GetItemGroupsAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup.ToListAsync();
}
public async Task<List<OfferRowModel>> GetRowsAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOfferRow
.Where(x => x.OfferID == recId)
.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<OfferRowModel> 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<bool> UpdateAsync(OfferModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetOffer.FirstOrDefaultAsync(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;
}
#endregion Public Methods
}
}
@@ -0,0 +1,185 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class OfferRowRepository : BaseRepository, IOfferRowRepository
{
#region Public Constructors
public OfferRowRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(OfferRowModel entity)
{
await using var dbCtx = await CreateContextAsync(); await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// SE ci fossero righe di indice pari o superiore le deve spostare...
// 2. Recupero i record successivi da shiftare
var list2Move = await dbCtx.DbSetOfferRow
.Where(x => x.OfferID == entity.OfferID && x.RowNum >= entity.RowNum)
.ToListAsync();
// aggiungo record
await dbCtx.DbSetOfferRow.AddAsync(entity);
foreach (var item in list2Move)
{
item.RowNum++;
dbCtx.Entry(item).State = EntityState.Modified;
}
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<bool> DeleteAsync(OfferRowModel 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.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)
{
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)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public async Task<List<ItemModel>> 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<OfferRowModel?> GetByIdAsync(int offerRowId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOfferRow
.Include(r => r.SellingItemNav)
.FirstOrDefaultAsync(x => x.OfferRowID == offerRowId);
}
public async Task<List<OfferRowModel>> GetByParentAsync(int offerId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOfferRow
.Where(x => x.OfferID == offerId)
.Include(r => r.SellingItemNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<OfferRowModel?> GetByUidAsync(string offerRowUid)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOfferRow
.Include(r => r.SellingItemNav)
.FirstOrDefaultAsync(x => x.OfferRowUID == offerRowUid);
}
public async Task<List<ItemGroupModel>> GetItemGroupsAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<OfferRowModel> 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<bool> UpdateAsync(OfferRowModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetOfferRow.FirstOrDefaultAsync(x => x.OfferRowID == entity.OfferRowID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetOfferRow.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,285 @@
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class OrderRepository : BaseRepository, IOrderRepository
{
#region Public Constructors
public OrderRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(OrderModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetOrder.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Esegue il cloning completo di un Offerta e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<OrderModel?> CloneOfferAsync(OfferModel rec2clone)
{
OrderModel? newRec = null;
await using var dbCtx = await CreateContextAsync();
// avvio transazione
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
DateTime now = DateTime.Now;
var currRec = await dbCtx.DbSetOffer
.Include(x => x.OfferRowNav)
.FirstOrDefaultAsync(x => x.OfferID == rec2clone.OfferID);
if (currRec == null)
return null;
DateTime adesso = DateTime.Now;
var lastRec = dbCtx
.DbSetOrder
.Where(x => x.RefYear == adesso.Year)
.OrderByDescending(x => x.RefNum)
.FirstOrDefault();
int newRefNum = lastRec != null ? lastRec.RefNum + 1 : 1;
// 2. Creo il nuovo parent
newRec = new OrderModel()
{
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,
OfferID = rec2clone.OfferID,
OrderState = OrderStates.Created,
RefNum = newRefNum,
RefRev = 1,
RefYear = adesso.Year,
ValidUntil = currRec.ValidUntil
};
// 3. Clono i child
// sistemo child...
newRec.OrderRowNav = currRec.OfferRowNav
.Select(c => new OrderRowModel()
{
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 = adesso,
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
})
.ToList();
// 4. Aggiungo il nuovo parent (EF aggiunge anche i child)
dbCtx.DbSetOrder.Add(newRec);
// 5. Salvo tutto
var numSave = await dbCtx.SaveChangesAsync();
// se ok sistemo UID...
if (numSave > 0 && newRec != null)
{
// sistemo UID...
foreach (var item in newRec.OrderRowNav)
{
item.OrderRowUID = item.OrderRowCode;
// alternativa da valutare..
if (false)
{
// genero tanti record collegati alla riga d'ordine...
for (int i = 0; i < item.ProdItemQtyTot; i++)
{
var child = new ProductionItemModel
{
OrderRowID = item.OrderRowID,
//OrderRowNav = item,
ItemCode = i + 1,
ExtItemCode = $"{item.OrderRowCode}-{i + 1:000}",
ProdBatchID = null
};
// aggiungo record
item.ProdItemNav.Add(child);
}
}
else
{
item.ProdItemNav = Enumerable.Range(1, (int)item.ProdItemQtyTot)
.Select(i => new ProductionItemModel
{
//OrderRowID = item.OrderRowID,
OrderRowNav = item,
ItemCode = i,
ExtItemCode = $"{item.OrderRowCode}-{i:000}",
ProdBatchID = null,
ProdItemTag = null //nullo e POI verrà sistemato
})
.ToList();
}
dbCtx.Entry(item).State = EntityState.Modified;
}
// salvo ulteriori variazioni
await dbCtx.SaveChangesAsync();
// tutti gli ordini e anno corrente...
await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdItem_UpdateProdItemTag(0,0);");
// committo in un unica transazione (da provare!!!)
await tx.CommitAsync();
}
}
catch
{
await tx.RollbackAsync();
throw;
}
return newRec;
}
public async Task<bool> DeleteAsync(OrderModel entity)
{
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetOrder.Remove(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<List<OrderModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOrder
.Include(c => c.CustomerNav)
.Include(d => d.DealerNav)
.Include(o => o.OrderRowNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<List<ItemModel>> 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<OrderModel?> GetByIdAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOrder.FirstOrDefaultAsync(x => x.OrderID == recId);
}
public async Task<List<OrderModel>> GetFiltAsync(DateTime inizio, DateTime fine)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOrder
.Where(x => x.Inserted >= inizio && x.Inserted <= fine)
.Include(c => c.CustomerNav)
.Include(d => d.DealerNav)
.Include(o => o.OrderRowNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<List<ItemGroupModel>> GetItemGroupsAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup.ToListAsync();
}
public async Task<List<OrderRowModel>> GetRowsAsync(int recId)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetOrderRow
.Where(x => x.OrderID == recId)
.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<OrderRowModel> 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<bool> UpdateAsync(OrderModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx.DbSetOrder.FirstOrDefaultAsync(x => x.OrderID == entity.OrderID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetOrder.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
}
}
@@ -0,0 +1,395 @@
using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Items;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Repository.Sales
{
public class OrderRowRepository : BaseRepository, IOrderRowRepository
{
#region Public Constructors
public OrderRowRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(OrderRowModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetOrderRow.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> 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<List<ItemModel>> 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<OrderRowModel?> 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<List<OrderRowModel>> 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<List<OrderRowModel>> 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<List<OrderRowModel>> 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<OrderRowModel?> 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<List<ItemGroupModel>> GetItemGroupsAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetItemGroup.ToListAsync();
}
public async Task<bool> SaveRowsAsync(List<OrderRowModel> 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<bool> 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<bool> 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<string> listMaccCurr = currWLD.MachineCalcResults.Select(x => x.Name).OrderBy(x => x).ToList() ?? new List<string>();
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...
List<ProductionItemModel> 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<ProductionGroupModel> 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<ProductionGroupModel> listProdGroup2Add = new List<ProductionGroupModel>();
// 1: non lavorabili...
if (currWLD.ListUnWorkable.Count > 0)
{
// calcolo il dizionario degli elementi...
Dictionary<string, ProdMachineDetailDto> 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<string, ProdMachineDetailDto> newWorkGroupList = new();
foreach (var machineName in item.Machines)
{
decimal effectiveTime = 0;
// 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<int> ValidateAsync(List<OrderRowModel> 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
}
}
@@ -0,0 +1,18 @@
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Utils;
namespace EgwCoreLib.Lux.Data.Repository.Stats
{
public interface IStatsAggrRepository
{
#region Public Methods
Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd);
Task<DtUtils.Periodo> GetRangeAsync();
Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld);
#endregion Public Methods
}
}
@@ -0,0 +1,18 @@
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Utils;
namespace EgwCoreLib.Lux.Data.Repository.Stats
{
public interface IStatsDetailRepository
{
#region Public Methods
Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "");
Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType);
Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld);
#endregion Public Methods
}
}
@@ -0,0 +1,108 @@
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Utils;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Stats
{
public class StatsAggrRepository : BaseRepository, IStatsAggrRepository
{
#region Public Constructors
public StatsAggrRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Elenco da DB delel stats aggregate dato periodo inizio/fine
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
public async Task<List<StatsAggregatedModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx
.DbSetStatsAggr
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd)
.AsNoTracking()
.OrderBy(x => x.Hour)
.ToListAsync();
}
/// <summary>
/// Range periodo per chiamate aggregate
/// </summary>
/// <returns></returns>
public async Task<DtUtils.Periodo> GetRangeAsync()
{
await using var dbCtx = await CreateContextAsync();
DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
var query = dbCtx.DbSetStatsAggr.AsQueryable();
var minHour = await query.MinAsync(x => x.Hour);
var maxHour = await query.MaxAsync(x => x.Hour);
answ.Inizio = minHour;
answ.Fine = maxHour;
// ritorno!
return answ;
}
/// <summary>
/// Esegue insert statistiche aggregate sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
public async Task<int> UpsertManyAsync(List<StatsAggregatedModel> listRecords, bool removeOld)
{
int answ = 0;
await using var dbCtx = await CreateContextAsync();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// in primis se richiesto calcolo range periodo e svuoto...
if (removeOld)
{
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
if (firstRec != null && lastRec != null)
{
DateTime startDate = firstRec.Hour;
DateTime endDate = lastRec.Hour;
// uso direttamente ExecuteDelete
await dbCtx
.DbSetStatsAggr
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
.ExecuteDeleteAsync();
}
}
// ora preparo inserimento massivo
await dbCtx
.DbSetStatsAggr
.AddRangeAsync(listRecords);
// salvo!
answ = await dbCtx.SaveChangesAsync();
// commit transazione
await tx.CommitAsync();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
return answ;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
#endregion Public Methods
}
}
@@ -0,0 +1,132 @@
using EgwCoreLib.Lux.Data.DbModel.Stats;
using EgwCoreLib.Utils;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Stats
{
public class StatsDetailRepository : BaseRepository, IStatsDetailRepository
{
#region Public Constructors
public StatsDetailRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Internal Methods
/// <summary>
/// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
public async Task<List<StatsDetailModel>> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
{
await using var dbCtx = await CreateContextAsync();
List<StatsDetailModel> answ = new List<StatsDetailModel>();
// recupero ed ordino per data-ora
var query = dbCtx.DbSetStatsDet
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd);
if (!string.IsNullOrEmpty(sEnvir))
query = query.Where(x => x.Environment == sEnvir);
if (!string.IsNullOrEmpty(sType))
query = query.Where(x => x.Type == sType);
answ = await query
.AsNoTracking()
.OrderBy(x => x.Hour)
.ThenBy(x => x.Environment)
.ThenBy(x => x.Type)
.ToListAsync();
return answ;
}
/// <summary>
/// Range periodo x chiamate detail eventualmente filtrate
/// </summary>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
public async Task<DtUtils.Periodo> GetRangeAsync(string sEnvir, string sType)
{
await using var dbCtx = await CreateContextAsync();
DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today);
var query = dbCtx.DbSetStatsDet.AsQueryable();
if (!string.IsNullOrEmpty(sEnvir))
query = query.Where(x => x.Environment == sEnvir);
if (!string.IsNullOrEmpty(sType))
query = query.Where(x => x.Type == sType);
var minHour = await query.MinAsync(x => x.Hour);
var maxHour = await query.MaxAsync(x => x.Hour);
answ.Inizio = minHour;
answ.Fine = maxHour;
return answ;
}
/// <summary>
/// Esegue insert statistiche di dettaglio sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
public async Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld)
{
int answ = 0;
await using var dbCtx = await CreateContextAsync();
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// in primis se richiesto calcolo range periodo e svuoto...
if (removeOld)
{
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
if (firstRec != null && lastRec != null)
{
DateTime startDate = firstRec.Hour;
DateTime endDate = lastRec.Hour;
// uso direttamente ExecuteDelete
await dbCtx
.DbSetStatsDet
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
.ExecuteDeleteAsync();
}
}
// ora preparo inserimento massivo
await dbCtx
.DbSetStatsDet
.AddRangeAsync(listRecords);
// salvo!
answ = await dbCtx.SaveChangesAsync();
// commit transazione
await tx.CommitAsync();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
return answ;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
#endregion Internal Methods
}
}
@@ -0,0 +1,54 @@
using EgwCoreLib.Lux.Data.DbModel.Utils;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using System.Data;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public class CounterRepository : BaseRepository, ICounterRepository
{
#region Public Constructors
public CounterRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<CounterModel>> GetAllAsync(int? yearRef = null)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetCounters
.AsNoTracking()
.Where(x => yearRef == null || x.RefYear == yearRef)
.ToListAsync();
}
public async Task<int> GetNextAsync(int yearRef, string countName)
{
await using var dbCtx = await CreateContextAsync();
int newCount = 0;
var outParam = new MySqlParameter("@pValue", MySqlDbType.Int32)
{
Direction = ParameterDirection.Output
};
await dbCtx.Database.ExecuteSqlRawAsync(
"CALL GetNextCounter(@pYear, @pName, @pValue);",
new MySqlParameter("@pYear", yearRef),
new MySqlParameter("@pName", countName),
outParam
);
if (outParam != null)
{
int.TryParse($"{outParam.Value}", out newCount);
}
return newCount;
}
#endregion Public Methods
}
}
@@ -7,48 +7,72 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
{
#region Public Constructors
public GenClassRepository(DataLayerContext db) : base(db)
public GenClassRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<bool> AddAsync(GenClassModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetGenClass.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(GenClassModel entity)
{
// Add validation for null entity
if (entity == null) return false;
await using var dbCtx = await CreateContextAsync();
dbCtx.DbSetGenClass.Remove(entity);
bool done = await dbCtx.SaveChangesAsync() > 0;
return done;
}
public void Add(GenClassModel entity) => _dbCtx.DbSetGenClass.Add(entity);
public async Task<int> CountChildrenAsync(string classCod)
{
return await _dbCtx.DbSetGenVal.CountAsync(x => x.ClassCod == classCod);
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetGenVal.CountAsync(x => x.ClassCod == classCod);
}
public void Delete(GenClassModel entity) => _dbCtx.DbSetGenClass.Remove(entity);
public async Task<List<GenClassModel>> GetAllWithNavAsync()
public async Task<List<GenClassModel>> GetAllAsync()
{
// EF Core 8 è già molto veloce, lasciamo che l'eccezione salga
return await _dbCtx.DbSetGenClass
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetGenClass
.Include(o => o.GenValNav)
.AsNoTracking()
.ToListAsync();
}
public async Task<GenClassModel?> GetByCodeAsync(string code) =>
await _dbCtx.DbSetGenClass.FirstOrDefaultAsync(x => x.ClassCod == code);
public void Update(GenClassModel entity)
public async Task<GenClassModel?> GetByCodeAsync(string code)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetGenClass.FirstOrDefaultAsync(x => x.ClassCod == code);
}
public async Task<bool> UpdateAsync(GenClassModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = _dbCtx.DbSetGenClass.Local.FirstOrDefault(x => x.ClassCod == entity.ClassCod);
var trackedEntity = await dbCtx.DbSetGenClass.FirstOrDefaultAsync(x => x.ClassCod == entity.ClassCod);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
_dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
_dbCtx.DbSetGenClass.Update(entity);
dbCtx.DbSetGenClass.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
@@ -3,11 +3,11 @@ using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
internal class GenValRepository : BaseRepository, IGenValRepository
public class GenValRepository : BaseRepository, IGenValRepository
{
#region Public Constructors
public GenValRepository(DataLayerContext db) : base(db)
public GenValRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
@@ -15,42 +15,70 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
#region Public Methods
public void Add(GenValueModel entity) => _dbCtx.DbSetGenVal.Add(entity);
public async Task<bool> AddAsync(GenValueModel entity)
{
await using var dbCtx = await CreateContextAsync();
await dbCtx.DbSetGenVal.AddAsync(entity);
return await dbCtx.SaveChangesAsync() > 0;
}
public async Task<bool> DeleteAsync(GenValueModel rec2del)
{
// 1. Recupero il record da eliminare
var dbResult = await _dbCtx.DbSetGenVal
.FirstOrDefaultAsync(x => x.GenValID == rec2del.GenValID);
// Add validation for null entity
if (rec2del == null) return false;
if (dbResult == null)
return false;
await using var dbCtx = await CreateContextAsync();
// 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 = await dbCtx.Database.BeginTransactionAsync();
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)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
// 3. Rimuovo il record
_dbCtx.DbSetGenVal.Remove(dbResult);
// 4. Salvo tutto
return await _dbCtx.SaveChangesAsync() > 0;
}
public async Task<GenValueModel?> GetByIdAsync(int Id) =>
await _dbCtx.DbSetGenVal.FirstOrDefaultAsync(x => x.GenValID == Id);
public async Task<GenValueModel?> GetByIdAsync(int Id)
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetGenVal.FirstOrDefaultAsync(x => x.GenValID == Id);
}
public async Task<List<GenValueModel>> GetFiltAsync(string codClass)
{
return await _dbCtx.DbSetGenVal
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetGenVal
.Where(x => x.ClassCod == codClass)
.AsNoTracking()
.ToListAsync();
@@ -58,56 +86,78 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
public async Task<bool> MoveAsync(GenValueModel selRec, bool moveUp)
{
// 1. Recupero il record corrente
var currRec = await _dbCtx.DbSetGenVal
.FirstOrDefaultAsync(x => x.GenValID == selRec.GenValID);
// Add validation for null entity
if (selRec == null) return false;
if (currRec == null)
return false;
await using var dbCtx = await CreateContextAsync();
// 2. Numero totale record della classe
int numRec = await _dbCtx.DbSetGenVal
.CountAsync(x => x.ClassCod == selRec.ClassCod);
// Wrap in transaction for atomicity (multi-row update - swap positions)
await using var tx = await dbCtx.Database.BeginTransactionAsync();
try
{
// 1. Recupero il record corrente
var currRec = await dbCtx.DbSetGenVal
.FirstOrDefaultAsync(x => x.GenValID == selRec.GenValID);
// 3. Calcolo nuova posizione
int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1;
if (currRec == null)
return false;
bool canMove = moveUp ? newPos > 0 : newPos <= numRec;
if (!canMove)
return false;
// 2. Numero totale record della classe
int numRec = await dbCtx.DbSetGenVal
.CountAsync(x => x.ClassCod == selRec.ClassCod);
// 4. Recupero il record da scambiare
var otherRec = await _dbCtx.DbSetGenVal
.FirstOrDefaultAsync(x => x.ClassCod == selRec.ClassCod && x.Index == newPos);
// 3. Calcolo nuova posizione
int newPos = moveUp ? currRec.Index - 1 : currRec.Index + 1;
if (otherRec == null)
return false;
bool canMove = moveUp ? newPos > 0 : newPos <= numRec;
if (!canMove)
return false;
// 5. Swap indici
otherRec.Index = currRec.Index;
currRec.Index = newPos;
// 4. Recupero il record da scambiare
var otherRec = await dbCtx.DbSetGenVal
.FirstOrDefaultAsync(x => x.ClassCod == selRec.ClassCod && x.Index == newPos);
_dbCtx.Entry(otherRec).State = EntityState.Modified;
_dbCtx.Entry(currRec).State = EntityState.Modified;
if (otherRec == null)
return false;
// 6. Salvo
return await _dbCtx.SaveChangesAsync() > 0;
// 5. Swap indici
otherRec.Index = currRec.Index;
currRec.Index = newPos;
dbCtx.Entry(otherRec).State = EntityState.Modified;
dbCtx.Entry(currRec).State = EntityState.Modified;
// 6. Salvo
bool done = await dbCtx.SaveChangesAsync() > 0;
if (done)
await tx.CommitAsync();
return done;
}
catch
{
await tx.RollbackAsync();
throw;
}
}
public void Update(GenValueModel entity)
public async Task<bool> UpdateAsync(GenValueModel entity)
{
await using var dbCtx = await CreateContextAsync();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = _dbCtx.DbSetGenVal.Local.FirstOrDefault(x => x.GenValID == entity.GenValID);
var trackedEntity = dbCtx.DbSetGenVal.FirstOrDefaultAsync(x => x.GenValID == entity.GenValID);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
_dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
_dbCtx.DbSetGenVal.Update(entity);
dbCtx.DbSetGenVal.Update(entity);
}
return await dbCtx.SaveChangesAsync() > 0;
}
#endregion Public Methods
@@ -0,0 +1,11 @@
using EgwCoreLib.Lux.Data.DbModel.Utils;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public interface ICounterRepository
{
Task<List<CounterModel>> GetAllAsync(int? yearRef = null);
Task<int> GetNextAsync(int yearRef, string countName);
}
}
@@ -6,17 +6,17 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
{
#region Public Methods
void Add(GenClassModel entity);
Task<bool> AddAsync(GenClassModel entity);
Task<int> CountChildrenAsync(string classCod);
void Delete(GenClassModel entity);
Task<bool> DeleteAsync(GenClassModel entity);
Task<List<GenClassModel>> GetAllWithNavAsync();
Task<List<GenClassModel>> GetAllAsync();
Task<GenClassModel?> GetByCodeAsync(string code);
void Update(GenClassModel entity);
Task<bool> UpdateAsync(GenClassModel entity);
#endregion Public Methods
}
@@ -6,7 +6,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
{
#region Public Methods
void Add(GenValueModel entity);
Task<bool> AddAsync(GenValueModel entity);
Task<bool> DeleteAsync(GenValueModel entity);
@@ -16,7 +16,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils
Task<bool> MoveAsync(GenValueModel selRec, bool moveUp);
void Update(GenValueModel entity);
Task<bool> UpdateAsync(GenValueModel entity);
#endregion Public Methods
}
@@ -0,0 +1,9 @@
using EgwCoreLib.Lux.Data.DbModel.Utils;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public interface ITagRepository
{
Task<List<TagsModel>> GetAllAsync();
}
}
@@ -0,0 +1,28 @@
using EgwCoreLib.Lux.Data.DbModel.Utils;
using Microsoft.EntityFrameworkCore;
namespace EgwCoreLib.Lux.Data.Repository.Utils
{
public class TagRepository : BaseRepository, ITagRepository
{
#region Public Constructors
public TagRepository(IDbContextFactory<DataLayerContext> ctxFactory) : base(ctxFactory)
{
}
#endregion Public Constructors
#region Public Methods
public async Task<List<TagsModel>> GetAllAsync()
{
await using var dbCtx = await CreateContextAsync();
return await dbCtx.DbSetTags
.AsNoTracking()
.ToListAsync();
}
#endregion Public Methods
}
}
+17 -51
View File
@@ -1,7 +1,9 @@
using Microsoft.Extensions.Configuration;
using EgwCoreLib.Lux.Data.Services.Internal;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
@@ -31,6 +33,13 @@ namespace EgwCoreLib.Lux.Data.Services
_config = Configuration;
_redisConn = RedisConn;
_redisDb = _redisConn.GetDatabase();
// configuro la base key x la cache Redis, con verifica contenga Cache finale
_redisBaseKey = _config.GetValue<string>("ServerConf:RedisBaseKey") ?? "Lux:Cache";
// aggiungo cache se non finisse per ":cache"
if (!_redisBaseKey.EndsWith(":Cache"))
{
_redisBaseKey += ":Cache";
}
// setup tracing
// Verifica conf trace...
_traceEnabled = _config.GetValue<bool>("Otel:EnableTracing", false);
@@ -116,7 +125,7 @@ namespace EgwCoreLib.Lux.Data.Services
public MessagePipe PipeProfElement { get; set; } = null!;
/// <summary>
/// Pipe dei messaggi per ritorno ProfileList calcolate da Engine di calcolo verso interfaccia utente.
/// Pipe dei messaggi per ritorno ProfileListAsync calcolate da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelProfList.
/// </summary>
public MessagePipe PipeProfList { get; set; } = null!;
@@ -270,47 +279,6 @@ namespace EgwCoreLib.Lux.Data.Services
}
}
#if false
/// <summary>
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <param name="expiration"></param>
/// <returns></returns>
protected async Task<T> GetOrSetCacheAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
using var activity = ActivitySource.StartActivity($"Cache:{key}");
string source = "REDIS";
// 1. Tenta il recupero da Redis
var rawData = await _redisDb.StringGetAsync(key);
if (rawData.HasValue)
{
activity?.SetTag("data.source", source);
// LogTrace opzionale qui
return JsonConvert.DeserializeObject<T>(rawData.ToString())!;
}
// 2. Cache Miss: Esegui la funzione factory (di solito la query al DB)
source = "DB";
activity?.SetTag("data.source", source);
T result = await factory();
if (result != null)
{
// 3. Salva in Redis per la prossima volta
var serialized = JsonConvert.SerializeObject(result, JSSettings);
await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache);
}
return result!;
}
#endif
/// <summary>
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
/// </summary>
@@ -323,8 +291,6 @@ namespace EgwCoreLib.Lux.Data.Services
{
using var activity = StartActivity();
string source = "DB";
//// 🔍 Ricavo il nome del metodo chiamante dalla factory
//string caller = factory.Method.DeclaringType?.Name + "." + factory.Method.Name;
// 1. Provo Redis
var cached = await _redisDb.StringGetAsync(key);
@@ -334,8 +300,7 @@ namespace EgwCoreLib.Lux.Data.Services
var cachedResult = JsonConvert.DeserializeObject<T>(cached!)!;
activity?.SetTag("data.source", source);
//LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms");
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: caller);
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", LogLevel.Trace, caller);
return cachedResult;
}
@@ -355,14 +320,11 @@ namespace EgwCoreLib.Lux.Data.Services
activity?.Stop();
// log in console
//LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms");
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", methodName: caller);
LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", LogLevel.Trace, caller);
//return value;
return result!;
}
/// <summary>
/// Helper trace messaggio log (SE abilitato)
/// </summary>
@@ -399,6 +361,8 @@ namespace EgwCoreLib.Lux.Data.Services
}
var result = await body(activity);
activity?.SetStatus(ActivityStatusCode.Ok);
activity?.Stop();
LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name);
return result;
}
catch (Exception ex)
@@ -414,6 +378,8 @@ namespace EgwCoreLib.Lux.Data.Services
#region Private Fields
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
/// <summary>
/// Durata della cache lunga in secondi (predefinito: 5 minuti)
/// Utilizzato nella proprietà LongCache per definire quanto a lungo i dati devono essere memorizzati in cache.
@@ -0,0 +1,29 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
namespace EgwCoreLib.Lux.Data.Services.Utils
{
public interface ITemplateRowService
{
#region Public Methods
Task<bool> CloneAsync(TemplateRowModel rec2clone);
Task<bool> DeleteAsync(TemplateRowModel model);
Task<List<string>> FixRowUidAsync(int templateId);
Task<List<TemplateRowModel>> GetAllAsync();
Task<List<TemplateRowModel>> GetByParentAsync(int templateId);
Task<bool> UpdateAwaitStateAsync(int templateRowId, bool? awaitBom, bool? awaitPrice, bool flushCache = false);
Task<bool> UpdateFileDataAsync(TemplateRowModel updRec);
Task<bool> UpdateSerStructAsync(int TemplateRowID, string serStruct);
Task<bool> UpsertAsync(TemplateRowModel upsRec);
#endregion Public Methods
}
}
@@ -0,0 +1,21 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
namespace EgwCoreLib.Lux.Data.Services.Utils
{
public interface ITemplateService
{
#region Public Methods
Task<bool> CloneAsync(TemplateModel rec2clone);
Task<bool> DeleteAsync(TemplateModel model);
Task<List<TemplateModel>> GetAllAsync();
Task<bool> UpsertAsync(TemplateModel upsRec);
Task<bool> UpdateCostAsync(int templateId);
#endregion Public Methods
}
}
@@ -0,0 +1,303 @@
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.Repository.Utils;
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Services.Utils
{
public class TemplateRowService : BaseServ, ITemplateRowService
{
#region Public Constructors
public TemplateRowService(
IConfiguration config,
IConnectionMultiplexer redis,
ITemplateRowRepository repo) : base(config, redis)
{
_className = "TemplateRow";
_repo = repo;
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Esegue il cloning completo di un Template e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<bool> CloneAsync(TemplateRowModel 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}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return result;
});
}
/// <summary>
/// Eliminazione record
/// </summary>
/// <param name="rec2del"></param>
/// <returns></returns>
public async Task<bool> DeleteAsync(TemplateRowModel rec2del)
{
return await TraceAsync($"{_className}.Delete", async (activity) =>
{
var dbResult = await _repo.GetRowAsync(rec2del.TemplateRowID);
if (dbResult == null) return false;
bool success = await _repo.DeleteAsync(dbResult);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return success;
});
}
/// <summary>
/// Effettua fix UID righe child del temoplate indicato e restituisce elenco UID da chiamare x refresh
/// </summary>
/// <param name="TemplateID">Key</param>
/// <returns></returns>
public async Task<List<string>> FixRowUidAsync(int templateId)
{
return await TraceAsync($"{_className}.FixRowUidAsync", async (activity) =>
{
// 1. Recupero righe
var rows = await _repo.GetRowsAsync(templateId);
// 2. Trovo quelle da sistemare
var list2fix = rows
.Where(x => string.IsNullOrEmpty(x.TemplateRowUID) ||
x.TemplateRowUID != x.TemplateRowDtx)
.ToList();
// 3. Se non c’è nulla da fare → ritorno
if (list2fix.Count == 0)
return new List<string>();
// 4. Preparo la lista da restituire
var result = list2fix
.Select(x => x.TemplateRowDtx)
.ToList();
// 5. Aggiorno i record
foreach (var row in list2fix)
row.TemplateRowUID = row.TemplateRowDtx;
// 6. Salvo
bool success = await _repo.SaveRowsAsync(list2fix);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return result;
});
}
/// <summary>
/// Elenco completo TemplateRow da DB
/// </summary>
/// <returns></returns>
public async Task<List<TemplateRowModel>> GetAllAsync()
{
return await TraceAsync($"{_className}.GetAll", async (activity) =>
{
return await GetOrSetCacheAsync(
$"{_redisBaseKey}:{_className}:ALL",
async () => await _repo.GetAllWithNavAsync(),
UltraLongCache
);
});
}
/// <summary>
/// Elenco filtrato (parent) TemplateRow da DB
/// </summary>
/// <param name="templateId"></param>
/// <returns></returns>
public async Task<List<TemplateRowModel>> GetByParentAsync(int templateId)
{
return await TraceAsync($"{_className}.GetByParent", async (activity) =>
{
return await GetOrSetCacheAsync(
$"{_redisBaseKey}:{_className}:{templateId}",
async () => await _repo.GetRowsAsync(templateId),
LongCache
);
});
}
public async Task<bool> UpdateAwaitStateAsync(int templateRowId, bool? awaitBom, bool? awaitPrice, bool flushCache = false)
{
return await TraceAsync($"{_className}.UpdateAwaitState", async (activity) =>
{
var currRec = await _repo.GetRowAsync(templateRowId);
if (currRec == null)
return false;
if (awaitBom.HasValue)
currRec.AwaitBom = awaitBom.Value;
if (awaitPrice.HasValue)
currRec.AwaitPrice = awaitPrice.Value;
//_repo.Update(currRec);
activity?.SetTag("db.operation", "UPDATE");
bool success = await _repo.UpdateAsync(currRec);
if (success && flushCache)
{
await ClearCacheAsync($"{_redisBaseKey}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return success;
});
}
/// <summary>
/// Effettua update delle info legate al file per il Template indicato
/// </summary>
/// <param name="updRec">Riga Template coi dati da aggiornare</param>
/// <returns></returns>
public async Task<bool> UpdateFileDataAsync(TemplateRowModel updRec)
{
return await TraceAsync($"{_className}.UpdateFileData", async (activity) =>
{
var currRec = await _repo.GetRowAsync(updRec.TemplateRowID);
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;
//_repo.Update(currRec);
activity?.SetTag("db.operation", "UPDATE");
bool success = await _repo.UpdateAsync(currRec);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return success;
});
}
/// <summary>
/// Effettua update del valore serializzato della Riga Template
/// </summary>
/// <param name="TemplateRowID">ID Riga Template da aggiornare</param>
/// <param name="serStruct">Serializzazione oggetto (es JWD)</param>
/// <returns></returns>
public async Task<bool> UpdateSerStructAsync(int TemplateRowID, string serStruct)
{
return await TraceAsync($"{_className}.UpdateSerStruct", async (activity) =>
{
var currRec = await _repo.GetRowAsync(TemplateRowID);
if (currRec == null)
return false;
currRec.SerStruct = serStruct;
currRec.ImgType = Core.Enums.ImageType.Calculated;
//_repo.Update(currRec);
activity?.SetTag("db.operation", "UPDATE");
bool success = await _repo.UpdateAsync(currRec);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return success;
});
}
/// <summary>
/// Upsert record TemplateRow
/// </summary>
/// <param name="updRec"></param>
/// <returns></returns>
public async Task<bool> UpsertAsync(TemplateRowModel upsRec)
{
return await TraceAsync($"{_className}.Upsert", async (activity) =>
{
// verifico imgType...
if (upsRec.SellingItemNav != null && (upsRec.SellingItemNav.SourceType == ItemSourceType.Jwd || upsRec.SellingItemNav.SourceType == ItemSourceType.FileBTL))
{
upsRec.ImgType = ImageType.Calculated;
}
// cerso sul DB
var currRec = await _repo.GetRowAsync(upsRec.TemplateRowID);
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}:Template:*");
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
}
return success;
});
}
#endregion Public Methods
#region Private Fields
private readonly string _className;
private readonly ITemplateRowRepository _repo;
#endregion Private Fields
}
}
@@ -0,0 +1,206 @@
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Catalog;
using EgwCoreLib.Lux.Data.Domains;
using EgwCoreLib.Lux.Data.Repository.Utils;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using StackExchange.Redis;
namespace EgwCoreLib.Lux.Data.Services.Utils
{
public class TemplateService : BaseServ, ITemplateService
{
#region Public Constructors
public TemplateService(
IConfiguration config,
IConnectionMultiplexer redis,
ITemplateRepository repo) : base(config, redis)
{
_className = "Template";
_repo = repo;
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Esegue il cloning completo di un Template e di TUTTE le relative righe...
/// </summary>
/// <param name="rec2clone"></param>
/// <returns></returns>
public async Task<bool> CloneAsync(TemplateModel 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}:TemplateRows:*");
}
return result;
});
}
/// <summary>
/// Eliminazione record
/// </summary>
/// <param name="rec2del"></param>
/// <returns></returns>
public async Task<bool> DeleteAsync(TemplateModel rec2del)
{
return await TraceAsync($"{_className}.Delete", async (activity) =>
{
var dbResult = await _repo.GetByIdAsync(rec2del.TemplateID);
if (dbResult == null) return false;
var numChild = await _repo.CountChildrenAsync(rec2del.TemplateID);
if (numChild > 0)
{
activity?.SetTag("delete.status", "rejected_has_children");
return false;
}
bool success = await _repo.DeleteAsync(dbResult);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:{_className}:*");
await ClearCacheAsync($"{_redisBaseKey}:TemplateRows:*");
}
return success;
});
}
/// <summary>
/// Elenco completo Template da DB
/// </summary>
/// <returns></returns>
public async Task<List<TemplateModel>> 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.GetAllWithNavAsync()
);
});
}
/// <summary>
/// Effettua update dei costi di tutte le righe del template indicato
/// </summary>
/// <param name="templateId"></param>
/// <returns></returns>
public async Task<bool> UpdateCostAsync(int templateId)
{
return await TraceAsync($"{_className}.Upsert", async (activity) =>
{
// 1. Recupero dati
var rows = await _repo.GetRowsAsync(templateId);
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<List<BomItemDTO>>(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}:TemplateRows:*");
}
return result;
});
}
/// <summary>
/// Upsert record Template
/// </summary>
/// <param name="updRec"></param>
/// <returns></returns>
public async Task<bool> UpsertAsync(TemplateModel upsRec)
{
return await TraceAsync($"{_className}.Upsert", async (activity) =>
{
var currRec = await _repo.GetByIdAsync(upsRec.TemplateID);
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}:TemplateRows:*");
}
return success;
});
}
#endregion Public Methods
#region Private Fields
private readonly string _className;
private readonly ITemplateRepository _repo;
#endregion Private Fields
}
}
@@ -0,0 +1,111 @@
using EgwCoreLib.Lux.Data.DbModel.Config;
using EgwCoreLib.Lux.Data.Repository.Config;
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
namespace EgwCoreLib.Lux.Data.Services.Config
{
public class ConfGlassService : BaseServ, IConfGlassService
{
#region Public Constructors
public ConfGlassService(
IConfiguration config,
IConnectionMultiplexer redis,
IConfGlassRepository repo) : base(config, redis)
{
_className = "ConfGlass";
_repo = repo;
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Eliminazione record
/// </summary>
/// <param name="rec2del"></param>
/// <returns></returns>
public async Task<bool> DeleteAsync(GlassModel rec2del)
{
return await TraceAsync($"{_className}.Delete", async (activity) =>
{
var dbResult = await _repo.GetByIdAsync(rec2del.GlassID);
if (dbResult == null) return false;
// 3. Eseguo la cancellazione
bool success = await _repo.DeleteAsync(dbResult);
// 4. Se ha avuto successo, pulisco la cache
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:{_className}");
}
return success;
});
}
/// <summary>
/// Elenco completo Glass da DB
/// </summary>
/// <returns></returns>
public async Task<List<GlassModel>> 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(),
UltraLongCache
);
});
}
/// <summary>
/// Upsert record Glass (aggiorna o inserisce)
/// </summary>
/// <param name="upsRec"></param>
/// <returns></returns>
public async Task<bool> UpsertAsync(GlassModel upsRec)
{
return await TraceAsync($"{_className}.Upsert", async (activity) =>
{
var currRec = await _repo.GetByIdAsync(upsRec.GlassID);
string operation = "UPDATE";
bool success = false;
if (currRec != null)
{
upsRec.Code = string.IsNullOrEmpty(upsRec.Code) ? $"{upsRec.GlassID:0000}" : upsRec.Code;
success = await _repo.UpdateAsync(upsRec);
}
else
{
operation = "INSERT";
success = await _repo.AddAsync(upsRec);
}
activity?.SetTag("db.operation", operation);
if (success)
{
await ClearCacheAsync($"{_redisBaseKey}:{_className}");
}
return success;
});
}
#endregion Public Methods
#region Private Fields
private readonly string _className;
private readonly IConfGlassRepository _repo;
#endregion Private Fields
}
}

Some files were not shown because too many files have changed in this diff Show More