/// /// Copyright (c) 2018-2023 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Linq; using System.Net; using Common; using log4net; using NHibernate; using SharedDatabase; using SharedDatabase.Entities; using TBF.Rig.Sequences; namespace TBF.Rig.Output.DB.DatabaseWriter { public enum Retv { OK, Error, } class DateTimeComparer : IComparer { public int Compare(DateTime x, DateTime y) { return DateTime.Compare(x, y); } } public class WritingToDb : ComponentBase, IOperation, GenericDevices.IResultsWriter, GenericDevices.IStartInfoReader, Generic.IDevice { private static readonly ILog log = LogManager.GetLogger(typeof(WritingToDb)); public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } public const string WorkstepName = "Test_bench"; public OrderInfo DefaultOrder; WriterCfg tracingCfg; string workplace { get { TBF.Rig.GenericDevices.IBenchInfo benchInfo = TBF.Rig.Sequences.ProcessData.BenchInfo; return (benchInfo != null) ? benchInfo.TestBenchName : "TestBench"; } } IPAddress ipAddress; IPAddress netMask; public IPAddress IPAddress { get { return ipAddress; } } public IPAddress NetMask { get { return netMask; } } enum OpState { None, CheckPreviousRecordsScheduled, CheckPreviousRecordsRunning, SaveTracingRecordsScheduled, SaveTracingRecordsRunning, } /// OpState currentOpState; bool opCompleted; bool anyError; /// /// Watermeters to check at the beginning of the cycle /// IList waterMeters; /// /// Data (tracing records) to write at the end of cycle /// Results.Entities.Batch batch; public ISessionFactory SessionFactory; /// Factory to create database sessions that is initialized in the constructor public WritingToDb() {} public WritingToDb(Generic.IComponentCfg cfg) : base(cfg) { tracingCfg = cfg as WriterCfg; if (tracingCfg == null) throw new ArgumentException("tracingCfg"); Network.AdapterInfo.RefreshNetAdaptersInfo(); Network.AdapterInfo netadapter = Network.AdapterInfo.GetNetAdapter(tracingCfg.NetAdapter); ipAddress = netadapter.IPAddress; netMask = netadapter.NetMask; currentOpState = OpState.None; log.Warn(this.ToString()); DefaultOrder = null; } /// /// IDevice interface implementation /// public override void Initialize() { if (tracingCfg.DebugLevel == DebugMode.Simulate) return; string ipAddress = GetIPAddress(); /// /// Session factory is used to create database sessions /// SharedDatabase.TracingDB.SessionFactory = this.SessionFactory = FluentNHibernate.Cfg.Fluently.Configure() .Database(FluentNHibernate.Cfg.Db.MySQLConfiguration.Standard.ConnectionString(tracingCfg.ConnStr)) .Mappings(m => m.FluentMappings.AddFromAssemblyOf()) .ExposeConfiguration(SharedDatabase.TracingDB.BuildSchema) .BuildSessionFactory(); try { using (var session = SessionFactory.OpenSession()) { /// /// Initialize DefaultOrder /// var dfltOrders = session.QueryOver() .Where(x => x.POName == "0000001") .And(x => (x.POState == (sbyte)OrderState.New || x.POState == (sbyte)OrderState.Active)) .List(); if (dfltOrders.Count > 0) DefaultOrder = dfltOrders[0]; #if !DEBUG /// /// Register this test bench in the tracing DB for approx. 2 weeks (RELEASE version only) /// SharedDatabase.TracingDB.RegisterWorkplaceObsolete(session, workplace, CurrentUser.UserName(), GetIPAddress(), "", WorkstepName, DateTime.Now + new TimeSpan(15, 0, 0, 0)); session.Flush(); #endif session.Close(); } } catch (Exception ex) { log.ErrorFormat("Cannot load a default order from a database or regster this workplace: {0}", ex); DefaultOrder = null; } } /// string GetIPAddress() { return (ipAddress != null) ? ipAddress.ToString() : "1.2.3.4"; } /// public void RunDeviceBefore() { } public void RunDeviceAfter() { } /// public void StopDevice() { if (tracingCfg.DebugLevel != DebugMode.Normal) return; using (ISession session = SessionFactory.OpenSession()) { SharedDatabase.TracingDB.UnregisterWorkplaceObsolete(session, workplace); session.Flush(); } } public void StopDevice2() {} public IList ReadOrders() { try { using (var session = SessionFactory.OpenSession()) { var result = session.QueryOver() .Where(x => (x.POState == (sbyte)OrderState.New || x.POState == (sbyte)OrderState.Active)) .List(); session.Close(); return result; } } catch (Exception ex) { log.ErrorFormat("ReadOrders() failed: {0}", ex.Message); return new List(); } } /// /// Read reference records belonging to a specified order. /// Used when retrieving housing S/N-s belonging to obtained eRegister numbers. /// /// DB sesson /// Order /// List of reference records-s public IList ReadRefRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null) { try { var refRecords = session.QueryOver() .Where(x => (x.POName == order.POName)) .List(); refRecords.Reverse(); if (dfltOrder != null && dfltWFlow != null) { var moreRecords = session.QueryOver() .Where(x => (x.POName == dfltOrder.POName)) .And(x => (x.Workflow == dfltWFlow.Name)) .List(); for (int i = moreRecords.Count - 1; i >= 0; i--) refRecords.Add(moreRecords[i]); } return refRecords; } catch (Exception exc) { log.ErrorFormat("Cannot read ref.records from the tracing DB: {0}", exc.Message); return new List(); } } /// /// Read records belonging to a specified order /// /// DB sesson /// Order /// List of records public IList ReadRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null) { try { var records = session.QueryOver() .JoinQueryOver(rec => rec.ReferenceRecord) .Where(rr => rr.POName == order.POName) .List(); records.Reverse(); if (dfltOrder != null && dfltWFlow != null) { var moreRecords = session.QueryOver() .JoinQueryOver(rec => rec.ReferenceRecord) .Where(rr => (rr.POName == dfltOrder.POName)) .And(rr => (rr.Workflow == dfltWFlow.Name)) .List(); for (int i = moreRecords.Count - 1; i >= 0; i--) records.Add(moreRecords[i]); } return records; } catch (Exception exc) { log.ErrorFormat("Cannot read records from the tracing DB: {0}", exc.Message); return new List(); } } /// /// Reads information on start of a cycle, Events: Event.InfoRead /// /// Results of water meters /// Reference to the operation public IOperation ReadStartInfoOp(IList waterMeters) { if (!tracingCfg.CheckPreviousRecords) { return null; } else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning)) { throw new Exception("Sequence error"); } else { this.waterMeters = waterMeters; currentOpState = OpState.CheckPreviousRecordsScheduled; return this; } } /// /// Writes the test cycle results into a file, Events: Event.ResultsWritten /// /// Procedure to print the results of /// Results to write into the file /// Reference to the operation public IOperation ProcessResultsOp(Results.Entities.Batch batch) { if (!tracingCfg.SaveTracingRecords) { return null; } else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning)) { throw new Exception("Sequence error"); } else { this.batch = batch; currentOpState = OpState.SaveTracingRecordsScheduled; return this; } } /// Start this operation public void Start() { if (currentOpState == OpState.CheckPreviousRecordsScheduled) { currentOpState = OpState.CheckPreviousRecordsRunning; } else if (currentOpState == OpState.SaveTracingRecordsScheduled) { currentOpState = OpState.SaveTracingRecordsRunning; } opCompleted = false; anyError = false; } /// Run this operation /// Event.ResultsWritten or Event.Error public Event Run() { log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState); if (currentOpState == OpState.CheckPreviousRecordsRunning) { if (tracingCfg.DebugLevel == DebugMode.Simulate) return Event.InfoRead; if (opCompleted) return anyError ? Event.InfoNotRead : Event.InfoRead; /// Run once opCompleted = true; if (CheckPreviousRecords(waterMeters) != Retv.OK) anyError = true; return anyError ? Event.InfoNotRead : Event.InfoRead; } if (currentOpState == OpState.SaveTracingRecordsRunning) { if (tracingCfg.DebugLevel == DebugMode.Simulate || batch.WaterMeters.Count == 0) return Event.ResultsWritten; if (opCompleted) return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten; /// Run once opCompleted = true; if (SaveTracingRecords(batch) != Retv.OK) anyError = true; if (anyError && !string.IsNullOrEmpty(SharedDatabase.EventsDB.ConnectionString)) { try { NHibernate.ISession session = SharedDatabase.EventsDB.CreateSession(); SharedDatabase.EventsDB.LoadSubscribers(session); TBF.UiBridge.Bridge.TriggerEvent(session, Name, EventClass.ComputerResources, Severity.Error, string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB: Číslo dávky={0}", batch.BatchNr), string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB\r\nČíslo dávky = {0}", batch.BatchNr), SubscriberGroup.Metrology | SubscriberGroup.Maintenance | SubscriberGroup.Production); } catch (Exception exc) { log.ErrorFormat("Triggering event(s) failed: {0}", exc.Message); } } return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten; } return Event.None; } /// Stop this operation public void Stop() { currentOpState = OpState.None; } Retv CheckPreviousRecords(IList waterMeters) { Retv retVal = Retv.Error; var sampleWM = waterMeters.FirstOrDefault(x => x != null && x.Disabled == false); if (sampleWM != null) { using (ISession session = SessionFactory.OpenSession()) { for (int i = 0; i < waterMeters.Count; i++) { Results.Entities.WaterMeter wm = waterMeters[i]; if ((wm != null) && !wm.Disabled) { CheckPreviousRecordOfSingleWM(session, wm); } } retVal = Retv.OK; /// Successfully completed (regardless of wm.LastRecordIsNok) } } return retVal; /// Returns Retv.Error if checking not completed successfully } Retv CheckPreviousRecordOfSingleWM(ISession session, Results.Entities.WaterMeter wm) { if (!string.IsNullOrEmpty(wm.SerialNr) && ProcessData.WorkflowSummary != null) { /// Get all reference records with Code == SerialNr from all worksteps different from 'Test_bench' var refRecords = session.QueryOver() .Where(rr => (rr.Code1 == wm.SerialNr)) .List(); if (refRecords.Count == 1) { ReferenceRecord refRecord = refRecords[0]; StepRecord previousStep = string.IsNullOrEmpty(ProcessData.WorkflowSummary.PreviousWorkstepName) ? null : refRecord.StepRecords.FirstOrDefault(x => x.Workstep == ProcessData.WorkflowSummary.PreviousWorkstepName); if (refRecord.Name1 == ProcessData.WorkflowSummary.Part1Name && refRecord.Name2 == ProcessData.WorkflowSummary.Part2Name && (ProcessData.WorkflowSummary.PreviousWorkstepName == null || (previousStep != null && previousStep.Workstep == ProcessData.WorkflowSummary.PreviousWorkstepName))) { wm.Workflow = ProcessData.WorkflowSummary.Workflow.Name; wm.LastRecordIsNok = false; /// OK return Retv.OK; } } } /// S/N is missing OR no records found OR workflows do not match OR previous StepRecord is missing wm.Workflow = string.Empty; wm.LastRecordIsNok = true; /// NOK return Retv.OK; } /// /// Write results of a batch of water meters to the DB /// /// DB session /// Batch entity Retv SaveTracingRecords(Results.Entities.Batch batch) { ITransaction transaction = null; ISession session = null; var order = ProcessData.OrderInfo as SharedDatabase.Entities.OrderInfo; if (order != null && ProcessData.WorkflowSummary != null && !string.IsNullOrEmpty(ProcessData.WorkflowSummary.WorkstepName)) { /// /// Save to regular tracing DB /// try { session = SessionFactory.OpenSession(); transaction = session.BeginTransaction(); string workplace = (ProcessData.BenchInfo != null) ? ProcessData.BenchInfo.TestBenchName : "TestBench"; int writtenRecordsCount = 0; foreach (var wm in batch.WaterMeters) { if (!wm.Disabled && !string.IsNullOrEmpty(wm.SerialNr)) { writtenRecordsCount += SaveSingleWM2DB(session, wm, order.POName, ProcessData.WorkflowSummary, workplace); } } transaction.Commit(); session.Flush(); log.WarnFormat("Batch {0}: {1} of {2} watermeters written to the regular production tracing DB", batch.BatchNr, writtenRecordsCount, batch.WaterMeters.Count); } catch (Exception exc) { if (transaction != null && !transaction.WasCommitted) transaction.Rollback(); log.ErrorFormat("Failed to write results of batch {0} to production tracing DB: {1}", batch.BatchNr, exc.Message); return Retv.Error; } finally { if (session != null) { session.Close(); session.Dispose(); } } } return Retv.OK; } /// /// Write one meter results to the DB /// /// DB session /// Watermeter entity int SaveSingleWM2DB(ISession session, Results.Entities.WaterMeter wm, string poName, WorkflowSummary wflowSummary, string worplace) { if (string.IsNullOrEmpty(wflowSummary.WorkstepName)) return 0; IList existingRecords = session.QueryOver() .Where(x => (x.Code1 == wm.SerialNr)) .List(); if (existingRecords.Count > 1) { /// Unexpected error log.ErrorFormat("Multiple reference records exist: Code1 = {0}", wm.SerialNr); return 0; } /// /// Reference record /// ReferenceRecord refRecord = null; if (existingRecords.Count == 1) { /// TODO: Check if the selected worflow is the same as in the found record refRecord = existingRecords[0]; refRecord.POName = poName; refRecord.Workflow = wflowSummary.Workflow.Name; refRecord.Code2 = wm.SerialNrAux; refRecord.Code3 = wm.CompleteSerialNr; refRecord.Code4 = wm.RadioAddress; } else { refRecord = new ReferenceRecord(poName, wflowSummary.Workflow.Name, wflowSummary.Part1Name, /// PCB (iPERL) or housing (620/640) SAP number wm.SerialNr, /// PcbNumber (iPERL) or housing S/N (620/640) wflowSummary.Part2Name, /// Flowtube SAP nummber (iPERL) or "eRegister#" (640) or empty wm.SerialNrAux, /// Flowtube S/N (iPERL) or eRegister number (640) or empty wm.CompleteSerialNr, /// Complete assigned S/N wm.RadioAddress); /// Radio address (iPERL and 640) } /// /// Step record /// StepRecord stepRecord = new StepRecord(refRecord, wflowSummary.WorkstepName, workplace, Common.CurrentUser.UserName(), wm.PassedFromTests() ? 0 : 1); if (refRecord.StepRecords == null) { refRecord.StepRecords = new List { stepRecord }; } else { refRecord.StepRecords.Add(stepRecord); } session.SaveOrUpdate(refRecord); session.SaveOrUpdate(stepRecord); return 1; } } }