619 lines
24 KiB
C#
619 lines
24 KiB
C#
///
|
|
/// Copyright (c) 2018-2019 Sensus Slovensko a.s.
|
|
///
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using log4net;
|
|
using NHibernate;
|
|
using NHibernate.Criterion;
|
|
using Common;
|
|
using Config.Entities;
|
|
using SharedDatabase;
|
|
using SharedDatabase.Entities;
|
|
using TBF.Rig.Sequences;
|
|
|
|
namespace TBF.Rig.Output.DB.ProductionTracing
|
|
{
|
|
public enum Retv
|
|
{
|
|
OK,
|
|
Error,
|
|
}
|
|
|
|
class DateTimeComparer : IComparer<DateTime>
|
|
{
|
|
public int Compare(DateTime x, DateTime y)
|
|
{
|
|
return DateTime.Compare(x, y);
|
|
}
|
|
}
|
|
|
|
public class Tracing : ComponentBase, IOperation, GenericDevices.IResultsWriter, GenericDevices.IStartInfoReader, Generic.IDevice
|
|
{
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(Tracing));
|
|
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
|
|
|
|
public const string WorkstepName = "Test_bench";
|
|
|
|
public OrderInfo DefaultOrder;
|
|
|
|
MonitoringCfg 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;
|
|
|
|
|
|
/// <summary>
|
|
/// Watermeters to check at the beginning of the cycle
|
|
/// </summary>
|
|
IList<Results.Entities.WaterMeter> waterMeters;
|
|
|
|
/// <summary>
|
|
/// Data (tracing records) to write at the end of cycle
|
|
/// </summary>
|
|
Results.Entities.Batch batch;
|
|
|
|
public ISessionFactory SessionFactory; /// Factory to create database sessions that is initialized in the constructor
|
|
|
|
|
|
public Tracing() {}
|
|
|
|
public Tracing(Generic.IComponentCfg cfg)
|
|
: base(cfg)
|
|
{
|
|
tracingCfg = cfg as MonitoringCfg;
|
|
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
|
|
///
|
|
SessionFactory = FluentNHibernate.Cfg.Fluently.Configure()
|
|
.Database(FluentNHibernate.Cfg.Db.MySQLConfiguration.Standard.ConnectionString(tracingCfg.ConnStr))
|
|
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Process>())
|
|
.ExposeConfiguration(SharedDatabase.TracingDB.BuildSchema)
|
|
.BuildSessionFactory();
|
|
|
|
ISession session = SessionFactory.OpenSession();
|
|
|
|
///
|
|
/// Initialize DefaultOrder
|
|
///
|
|
var dfltOrders = session.QueryOver<OrderInfo>()
|
|
.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,
|
|
GlobalData.GetCurrentUserName(),
|
|
GetIPAddress(),
|
|
"<multiple>",
|
|
WorkstepName,
|
|
DateTime.Now + new TimeSpan(15, 0, 0, 0));
|
|
#endif
|
|
session.Flush();
|
|
session.Close();
|
|
SharedDatabase.TracingDB.SessionFactory = SessionFactory;
|
|
if (session != null) session.Dispose();
|
|
}
|
|
///
|
|
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<OrderInfo> ReadOrders(ISession session = null)
|
|
{
|
|
bool openAndCloseSession = (session == null) || !session.IsOpen;
|
|
|
|
try
|
|
{
|
|
if (openAndCloseSession) session = SessionFactory.OpenSession();
|
|
|
|
var result = session.QueryOver<OrderInfo>()
|
|
.Where(x => (x.POState == (sbyte)OrderState.New || x.POState == (sbyte)OrderState.Active))
|
|
.List();
|
|
|
|
if (openAndCloseSession) session.Close();
|
|
|
|
return result;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.ErrorFormat("Cannot read orders from the tracing DB: {0}", exc.Message);
|
|
return new List<OrderInfo>();
|
|
}
|
|
}
|
|
|
|
public OrderInfo ReadOrder(string poName, ISession session = null)
|
|
{
|
|
bool openAndCloseSession = (session == null) || !session.IsOpen;
|
|
|
|
try
|
|
{
|
|
if (openAndCloseSession) session = SessionFactory.OpenSession();
|
|
|
|
var list = session.QueryOver<OrderInfo>()
|
|
.Where(x => (x.POName == poName))
|
|
.List();
|
|
|
|
if (openAndCloseSession) session.Close();
|
|
|
|
return (list.Count == 1) ? list[0] : null;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.ErrorFormat("Cannot read order {0} from the tracing DB: {1}", poName, exc.Message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Read reference records belonging to a specified order.
|
|
/// Used when retrieving housing S/N-s belonging to obtained eRegister numbers.
|
|
/// </summary>
|
|
/// <param name="session">DB sesson</param>
|
|
/// <param name="order">Order</param>
|
|
/// <returns>List of reference records-s</returns>
|
|
public IList<ReferenceRecord> ReadRefRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null)
|
|
{
|
|
try
|
|
{
|
|
var refRecords = session.QueryOver<ReferenceRecord>()
|
|
.Where(x => (x.POName == order.POName))
|
|
.List();
|
|
refRecords.Reverse();
|
|
|
|
if (dfltOrder != null && dfltWFlow != null)
|
|
{
|
|
var moreRecords = session.QueryOver<ReferenceRecord>()
|
|
.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<ReferenceRecord>();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Read records belonging to a specified order
|
|
/// </summary>
|
|
/// <param name="session">DB sesson</param>
|
|
/// <param name="order">Order</param>
|
|
/// <returns>List of records</returns>
|
|
public IList<Record> ReadRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null)
|
|
{
|
|
try
|
|
{
|
|
var records = session.QueryOver<Record>()
|
|
.JoinQueryOver<ReferenceRecord>(rec => rec.ReferenceRecord)
|
|
.Where(rr => rr.POName == order.POName)
|
|
.List();
|
|
records.Reverse();
|
|
|
|
if (dfltOrder != null && dfltWFlow != null)
|
|
{
|
|
var moreRecords = session.QueryOver<Record>()
|
|
.JoinQueryOver<ReferenceRecord>(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<Record>();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Reads information on start of a cycle, Events: Event.InfoRead
|
|
/// </summary>
|
|
/// <param name="waterMeters">Results of water meters</param>
|
|
/// <returns>Reference to the operation</returns>
|
|
public IOperation ReadStartInfoOp(IList<Results.Entities.WaterMeter> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the test cycle results into a file, Events: Event.ResultsWritten
|
|
/// </summary>
|
|
/// <param name="procedure">Procedure to print the results of</param>
|
|
/// <param name="unsortedResults">Results to write into the file</param>
|
|
/// <returns>Reference to the operation</returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>Start this operation</summary>
|
|
public void Start()
|
|
{
|
|
if (currentOpState == OpState.CheckPreviousRecordsScheduled)
|
|
{
|
|
currentOpState = OpState.CheckPreviousRecordsRunning;
|
|
}
|
|
else if (currentOpState == OpState.SaveTracingRecordsScheduled)
|
|
{
|
|
currentOpState = OpState.SaveTracingRecordsRunning;
|
|
}
|
|
|
|
opCompleted = false;
|
|
anyError = false;
|
|
}
|
|
|
|
/// <summary>Run this operation</summary>
|
|
/// <returns>Event.ResultsWritten or Event.Error</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Stop this operation</summary>
|
|
public void Stop()
|
|
{
|
|
currentOpState = OpState.None;
|
|
}
|
|
|
|
|
|
Retv CheckPreviousRecords(IList<Results.Entities.WaterMeter> waterMeters)
|
|
{
|
|
Retv retVal = Retv.Error;
|
|
|
|
var sampleWM = waterMeters.FirstOrDefault<Results.Entities.WaterMeter>(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<ReferenceRecord>()
|
|
.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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Write results of a batch of water meters to the DB
|
|
/// </summary>
|
|
/// <param name="session">DB session</param>
|
|
/// <param name="batch">Batch entity</param>
|
|
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();
|
|
|
|
var reloadedOrder = session.QueryOver<OrderInfo>()
|
|
.Where(x => x.POName == order.POName)
|
|
.List();
|
|
if (reloadedOrder.Count == 1)
|
|
{
|
|
reloadedOrder[0].CurrentPcsCount = order.CurrentPcsCount;
|
|
reloadedOrder[0].CurrentSN = order.CurrentSN;
|
|
reloadedOrder[0].CurrentRA = order.CurrentRA;
|
|
if (order.CurrentPcsCount > 0)
|
|
{
|
|
reloadedOrder[0].POState = (sbyte)OrderState.Active;
|
|
}
|
|
session.SaveOrUpdate(reloadedOrder[0]);
|
|
log.WarnFormat("Order={0} CurrentPcsCount={1} CurrentSN={2} CurrentRA={3}",
|
|
order.POName, order.CurrentPcsCount, order.CurrentSN, order.CurrentRA);
|
|
}
|
|
else
|
|
{
|
|
log.ErrorFormat("Unexpected reloadedOrder.Count == {0} in SaveTracingRecords()", reloadedOrder.Count);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write one meter results to the DB
|
|
/// </summary>
|
|
/// <param name="session">DB session</param>
|
|
/// <param name="wm">Watermeter entity</param>
|
|
int SaveSingleWM2DB(ISession session, Results.Entities.WaterMeter wm, string poName, WorkflowSummary wflowSummary, string worplace)
|
|
{
|
|
if (string.IsNullOrEmpty(wflowSummary.WorkstepName)) return 0;
|
|
|
|
IList<ReferenceRecord> existingRecords = session.QueryOver<ReferenceRecord>()
|
|
.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> { stepRecord };
|
|
}
|
|
else
|
|
{
|
|
refRecord.StepRecords.Add(stepRecord);
|
|
}
|
|
|
|
session.SaveOrUpdate(refRecord);
|
|
session.SaveOrUpdate(stepRecord);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|