diff --git a/TBF/Rig/Output/DB/ResultsWriter/Factory.cs b/TBF/Rig/Output/DB/ResultsWriter/Factory.cs
new file mode 100644
index 000000000..af590a324
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/Factory.cs
@@ -0,0 +1,39 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+using System.Collections.Generic;
+using TBF.Rig.Generic;
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ ///
+ /// Factory component 'ResultsWriter'.
+ /// Writes selected result items to database using parent UniDataStorageWriter.
+ ///
+ public class Factory : IComponentFactory
+ {
+ public string ClassName { get { return GetType().Namespace.Substring(8); } }
+ public override string ToString() { return ClassName; }
+
+ public IComponent DummyComponent()
+ {
+ return new ResultsWriter();
+ }
+
+ public IComponent GetComponent(IComponentCfg cfg, IList components)
+ {
+ return new ResultsWriter(cfg, components);
+ }
+
+ public IComponentCfg DefaultConfig()
+ {
+ return new ResultsWriterCfg("ResultsWriter", this);
+ }
+
+ public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
+ {
+ return ComponentCfgBase.CreateFromDbEntity(ResultsWriterCfg.Serializer, component, this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriter.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriter.cs
new file mode 100644
index 000000000..fb3dc3b3c
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriter.cs
@@ -0,0 +1,279 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+using Common;
+using log4net;
+using Results.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TBF.Rig.Generic;
+using TBF.Rig.Output.DataStorage.UniDataStorageWriter;
+using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
+using TBF.Rig.Sequences;
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ public class ResultsWriter : ComponentBase, IOperation, GenericDevices.IResultsWriter, Generic.IDevice
+ {
+ private static readonly ILog log = LogManager.GetLogger(typeof(ResultsWriter));
+
+ public override string ToString()
+ {
+ return string.Format("{0}({1})", ClassName, Cfg.ToString(-1));
+ }
+
+ ResultsWriterCfg resultsWriterCfg;
+
+ Batch batch;
+
+ bool opCompleted;
+ bool anyError;
+
+ enum OpState
+ {
+ None,
+ WriteResultsScheduled,
+ WriteResultsRunning,
+ }
+
+ OpState currentOpState;
+
+ TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer dataStorageWriter;
+
+ public ResultsWriter()
+ {
+ }
+
+ public ResultsWriter(IComponentCfg cfg, IList components) : base(cfg)
+ {
+ resultsWriterCfg = cfg as ResultsWriterCfg;
+ if (resultsWriterCfg == null)
+ throw new ArgumentException("resultsWriterCfg");
+
+ currentOpState = OpState.None;
+
+ log.Warn(this.ToString());
+
+ IComponent parent =
+ components.FirstOrDefault(c => c.Name == resultsWriterCfg.ParentName);
+
+ if (parent == null)
+ {
+ throw new Exception(
+ string.Format("Parent '{0}' was not found.",
+ resultsWriterCfg.ParentName));
+ }
+
+ log.WarnFormat(
+ "ResultsWriter parent found: Name={0}, Type={1}",
+ parent.Name,
+ parent.GetType().FullName);
+
+ dataStorageWriter = parent as TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
+
+ if (dataStorageWriter == null)
+ {
+ throw new Exception(
+ string.Format(
+ "Parent '{0}' of type '{1}' is not UniDataStorageWriter.Writer.",
+ resultsWriterCfg.ParentName,
+ parent.GetType().FullName));
+ }
+ }
+
+ public ResultsWriter(IComponentCfg cfg) : base(cfg)
+ {
+ resultsWriterCfg = cfg as ResultsWriterCfg;
+ if (resultsWriterCfg == null)
+ throw new ArgumentException("resultsWriterCfg");
+
+ currentOpState = OpState.None;
+
+ log.Warn(this.ToString());
+ }
+
+ ///
+ /// IDevice interface implementation
+ ///
+ public override void Initialize()
+ {
+ }
+
+ public void RunDeviceBefore()
+ {
+ }
+
+ public void RunDeviceAfter()
+ {
+ }
+
+ public void StopDevice()
+ {
+ }
+
+ public void StopDevice2()
+ {
+ }
+
+ ///
+ /// GenericDevices.IResultsWriter
+ ///
+ public IOperation ProcessResultsOp(Batch batch)
+ {
+ if (!resultsWriterCfg.Enabled)
+ {
+ return null;
+ }
+
+ if (currentOpState == OpState.WriteResultsRunning)
+ {
+ throw new Exception("Sequence error");
+ }
+
+ this.batch = batch;
+ currentOpState = OpState.WriteResultsScheduled;
+ return this;
+ }
+
+ ///
+ /// IOperation
+ ///
+ public void Start()
+ {
+ if (currentOpState == OpState.WriteResultsScheduled)
+ {
+ currentOpState = OpState.WriteResultsRunning;
+ }
+
+ opCompleted = false;
+ anyError = false;
+ }
+
+ public Event Run()
+ {
+ log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState);
+
+ if (currentOpState == OpState.WriteResultsRunning)
+ {
+ if (resultsWriterCfg.DebugLevel == DebugMode.Simulate)
+ {
+ return Event.ResultsWritten;
+ }
+
+ if (batch == null || batch.WaterMeters == null || batch.WaterMeters.Count == 0)
+ {
+ return Event.ResultsWritten;
+ }
+
+ if (opCompleted)
+ {
+ return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
+ }
+
+ opCompleted = true;
+
+ try
+ {
+ WriteBatchResults(batch);
+ }
+ catch (Exception exc)
+ {
+ anyError = true;
+ log.ErrorFormat("Failed to write results by ResultsWriter: {0}", exc);
+ }
+
+ return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
+ }
+
+ return Event.None;
+ }
+
+ public void Stop()
+ {
+ currentOpState = OpState.None;
+ }
+
+ public void WriteBatchResults(Batch batch)
+ {
+ resultsWriterCfg.UpdateRuntimeModel();
+
+ foreach (var wm in batch.WaterMeters)
+ {
+ if (wm == null || wm.Disabled)
+ continue;
+
+ DataWriteRequest request = BuildWriteRequest(wm);
+
+ if (request.InsertItems.Count == 0)
+ {
+ log.WarnFormat("No values to write for WM position {0}", wm.WMPosition);
+ continue;
+ }
+
+ var result = dataStorageWriter.SetData(request);
+
+ if (!result.Success)
+ {
+ throw new Exception(result.Message);
+ }
+
+ log.WarnFormat(
+ "ResultsWriter wrote {0} value(s) for WM position {1}",
+ request.InsertItems.Count,
+ wm.WMPosition);
+ }
+ }
+
+ DataWriteRequest BuildWriteRequest(WaterMeter wm)
+ {
+ var request = new DataWriteRequest();
+ request.Mode = WriteMode.Insert;
+
+ if (resultsWriterCfg.SelectedItems == null)
+ return request;
+
+ foreach (var item in resultsWriterCfg.SelectedItems)
+ {
+ if (item == null)
+ continue;
+
+ string columnName = item.Caption;
+
+ if (string.IsNullOrWhiteSpace(columnName))
+ {
+ log.WarnFormat("Result item with empty Caption skipped: {0}", item);
+ continue;
+ }
+
+ request.InsertItems.Add(new InsertWriteItem()
+ {
+ ColumnName = columnName,
+ Value = item.Print(wm)
+ });
+ }
+
+ return request;
+ }
+
+ public void InitializeParent()
+ {
+ IComponent parent = TbfComponents.FindComponent(resultsWriterCfg.ParentName);
+
+ if (parent == null)
+ throw new Exception(
+ string.Format("Parent '{0}' was not found.", resultsWriterCfg.ParentName));
+
+ dataStorageWriter =
+ parent as TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
+
+ if (dataStorageWriter == null)
+ throw new Exception(
+ string.Format(
+ "Parent '{0}' of type '{1}' is not UniDataStorageWriter.Writer.",
+ resultsWriterCfg.ParentName,
+ parent.GetType().FullName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfg.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfg.cs
new file mode 100644
index 000000000..fe240d6bd
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfg.cs
@@ -0,0 +1,88 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+using System.Collections.Generic;
+using System.Xml.Serialization;
+using Results;
+using TBF.Rig.Generic;
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ public class ResultsWriterCfg : ComponentCfgBase, IComponentCfg
+ {
+ public static XmlSerializer Serializer =
+ XmlSerializer.FromTypes(new[] { typeof(ResultsWriterCfg) })[0];
+
+ public override XmlSerializer GetSerializer()
+ {
+ return Serializer;
+ }
+
+ public IComponentCfgCtrl GetControl(IList cmpntEntities)
+ {
+ return new ResultsWriterCfgCtrl(cmpntEntities);
+ }
+
+ ///
+ /// Enables or disables writing.
+ ///
+ public bool Enabled;
+
+ ///
+ /// Target table or logical storage name for UniDataStorageWriter.
+ ///
+ public string StorageName;
+
+ /// Runtime model používaný ResultsConfigCtrl
+ [XmlIgnore]
+ public List SelectedItems;
+
+ public string[] Items;
+
+ /// Serializable model
+ public List SelectedItemsCfg;
+
+ ResultsWriterCfg()
+ {
+ ParentName = string.Empty; // here should be UniDataStorageWriter component name
+ SelectedItems = new List();
+ SelectedItemsCfg = new List();
+ Enabled = true;
+ StorageName = "Results";
+ }
+
+ public ResultsWriterCfg(string name, IComponentFactory factory)
+ : this()
+ {
+ Name = name;
+ Factory = factory;
+ }
+
+ public string ToString(int i)
+ {
+ return string.Format(
+ "Name={0}, Parent={1}, Enabled={2}, StorageName={3}, Items={4}",
+ Name,
+ ParentName,
+ Enabled,
+ StorageName,
+ SelectedItems != null ? SelectedItems.Count : 0);
+ }
+
+ public void UpdateSerializableModel()
+ {
+ Items = WMeterRsltItemSpec.ToStrArray(SelectedItems);
+ }
+
+ public void UpdateRuntimeModel()
+ {
+ SelectedItems = new List();
+
+ if (Items == null)
+ return;
+
+ SelectedItems.AddRange(WMeterRsltItemSpec.FromStrArray(Items));
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.Designer.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.Designer.cs
new file mode 100644
index 000000000..d01e59c1a
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.Designer.cs
@@ -0,0 +1,142 @@
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ partial class ResultsWriterCfgCtrl
+ {
+ private System.ComponentModel.IContainer components = null;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null)) components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ this.classNameLabel = new System.Windows.Forms.Label();
+ this.nameTextBox = new System.Windows.Forms.TextBox();
+ this.nameLabel = new System.Windows.Forms.Label();
+ this.parentLabel = new System.Windows.Forms.Label();
+ this.parentComboBox = new System.Windows.Forms.ComboBox();
+ this.enabledCheckBox = new System.Windows.Forms.CheckBox();
+ this.storageNameLabel = new System.Windows.Forms.Label();
+ this.storageNameTextBox = new System.Windows.Forms.TextBox();
+ this.configureResultsButton = new System.Windows.Forms.Button();
+ this.selectedItemsLabel = new System.Windows.Forms.Label();
+ this.previewRequestButton = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+
+ this.previewRequestButton.Enabled = false;
+ this.previewRequestButton.Location = new System.Drawing.Point(150, 204);
+ this.previewRequestButton.Name = "previewRequestButton";
+ this.previewRequestButton.Size = new System.Drawing.Size(160, 30);
+ this.previewRequestButton.TabIndex = 10;
+ this.previewRequestButton.Text = "Preview request...";
+ this.previewRequestButton.UseVisualStyleBackColor = true;
+ this.previewRequestButton.Click += new System.EventHandler(this.previewRequestButton_Click);
+
+ this.classNameLabel.AutoSize = true;
+ this.classNameLabel.Location = new System.Drawing.Point(14, 12);
+ this.classNameLabel.Name = "classNameLabel";
+ this.classNameLabel.Size = new System.Drawing.Size(75, 13);
+ this.classNameLabel.TabIndex = 0;
+ this.classNameLabel.Text = "ResultsWriter";
+
+ this.nameLabel.AutoSize = true;
+ this.nameLabel.Location = new System.Drawing.Point(14, 45);
+ this.nameLabel.Name = "nameLabel";
+ this.nameLabel.Size = new System.Drawing.Size(38, 13);
+ this.nameLabel.TabIndex = 1;
+ this.nameLabel.Text = "Name:";
+
+ this.nameTextBox.Enabled = false;
+ this.nameTextBox.Location = new System.Drawing.Point(150, 42);
+ this.nameTextBox.Name = "nameTextBox";
+ this.nameTextBox.Size = new System.Drawing.Size(260, 20);
+ this.nameTextBox.TabIndex = 2;
+
+ this.parentLabel.AutoSize = true;
+ this.parentLabel.Location = new System.Drawing.Point(14, 75);
+ this.parentLabel.Name = "parentLabel";
+ this.parentLabel.Size = new System.Drawing.Size(123, 13);
+ this.parentLabel.TabIndex = 3;
+ this.parentLabel.Text = "UniDataStorageWriter:";
+
+ this.parentComboBox.Enabled = false;
+ this.parentComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.parentComboBox.FormattingEnabled = true;
+ this.parentComboBox.Location = new System.Drawing.Point(150, 72);
+ this.parentComboBox.Name = "parentComboBox";
+ this.parentComboBox.Size = new System.Drawing.Size(260, 21);
+ this.parentComboBox.TabIndex = 4;
+
+ this.enabledCheckBox.AutoSize = true;
+ this.enabledCheckBox.Enabled = false;
+ this.enabledCheckBox.Location = new System.Drawing.Point(150, 103);
+ this.enabledCheckBox.Name = "enabledCheckBox";
+ this.enabledCheckBox.Size = new System.Drawing.Size(65, 17);
+ this.enabledCheckBox.TabIndex = 5;
+ this.enabledCheckBox.Text = "Enabled";
+ this.enabledCheckBox.UseVisualStyleBackColor = true;
+
+ this.storageNameLabel.AutoSize = true;
+ this.storageNameLabel.Location = new System.Drawing.Point(14, 133);
+ this.storageNameLabel.Name = "storageNameLabel";
+ this.storageNameLabel.Size = new System.Drawing.Size(77, 13);
+ this.storageNameLabel.TabIndex = 6;
+ this.storageNameLabel.Text = "Storage name:";
+
+ this.storageNameTextBox.Enabled = false;
+ this.storageNameTextBox.Location = new System.Drawing.Point(150, 130);
+ this.storageNameTextBox.Name = "storageNameTextBox";
+ this.storageNameTextBox.Size = new System.Drawing.Size(260, 20);
+ this.storageNameTextBox.TabIndex = 7;
+
+ this.configureResultsButton.Enabled = false;
+ this.configureResultsButton.Location = new System.Drawing.Point(150, 168);
+ this.configureResultsButton.Name = "configureResultsButton";
+ this.configureResultsButton.Size = new System.Drawing.Size(160, 30);
+ this.configureResultsButton.TabIndex = 8;
+ this.configureResultsButton.Text = "Configure results...";
+ this.configureResultsButton.UseVisualStyleBackColor = true;
+ this.configureResultsButton.Click += new System.EventHandler(this.configureResultsButton_Click);
+
+ this.selectedItemsLabel.AutoSize = true;
+ this.selectedItemsLabel.Location = new System.Drawing.Point(325, 176);
+ this.selectedItemsLabel.Name = "selectedItemsLabel";
+ this.selectedItemsLabel.Size = new System.Drawing.Size(92, 13);
+ this.selectedItemsLabel.TabIndex = 9;
+ this.selectedItemsLabel.Text = "0 selected item(s)";
+
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.Controls.Add(this.selectedItemsLabel);
+ this.Controls.Add(this.configureResultsButton);
+ this.Controls.Add(this.storageNameTextBox);
+ this.Controls.Add(this.storageNameLabel);
+ this.Controls.Add(this.enabledCheckBox);
+ this.Controls.Add(this.parentComboBox);
+ this.Controls.Add(this.parentLabel);
+ this.Controls.Add(this.nameTextBox);
+ this.Controls.Add(this.nameLabel);
+ this.Controls.Add(this.classNameLabel);
+ this.Controls.Add(this.previewRequestButton);
+ this.Name = "ResultsWriterCfgCtrl";
+ this.Size = new System.Drawing.Size(620, 260);
+ this.Load += new System.EventHandler(this.ResultsWriterCfgCtrl_Load);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+ }
+
+ private System.Windows.Forms.Label classNameLabel;
+ private System.Windows.Forms.TextBox nameTextBox;
+ private System.Windows.Forms.Label nameLabel;
+ private System.Windows.Forms.Label parentLabel;
+ private System.Windows.Forms.ComboBox parentComboBox;
+ private System.Windows.Forms.CheckBox enabledCheckBox;
+ private System.Windows.Forms.Label storageNameLabel;
+ private System.Windows.Forms.TextBox storageNameTextBox;
+ private System.Windows.Forms.Button configureResultsButton;
+ private System.Windows.Forms.Label selectedItemsLabel;
+ private System.Windows.Forms.Button previewRequestButton;
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.cs
new file mode 100644
index 000000000..f8c9a9f00
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterCfgCtrl.cs
@@ -0,0 +1,243 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using Common;
+using Config.Entities;
+using TBF.Rig.Generic;
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ public partial class ResultsWriterCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
+ {
+ public bool ShowMore { get { return false; } }
+
+ ResultsWriterCfg config;
+ IList cmpntEntities;
+
+ bool resultsConfigChanged;
+
+ public IComponentCfg Config
+ {
+ get { return config as IComponentCfg; }
+ set
+ {
+ config = value as ResultsWriterCfg;
+ Redraw();
+ }
+ }
+
+ public ResultsWriterCfgCtrl(IList cmpntEntities)
+ {
+ InitializeComponent();
+ this.cmpntEntities = cmpntEntities;
+ }
+
+ private void ResultsWriterCfgCtrl_Load(object sender, EventArgs e)
+ {
+ if (config == null) return;
+ Redraw();
+ }
+
+ public void Closing()
+ {
+ }
+
+ void Redraw()
+ {
+ if (config == null) return;
+
+ config.UpdateRuntimeModel();
+
+ classNameLabel.Text = config.Factory.ClassName;
+ nameTextBox.Text = config.Name;
+ enabledCheckBox.Checked = config.Enabled;
+ storageNameTextBox.Text = config.StorageName;
+
+ parentComboBox.Items.Clear();
+ parentComboBox.Items.Add(string.Empty);
+
+ if (cmpntEntities != null)
+ {
+ foreach (Component cmpnt in cmpntEntities)
+ {
+ if (cmpnt == null) continue;
+
+ // zatiaľ jednoduchý filter podľa názvu/classname
+ if (cmpnt.ClassName != null &&
+ cmpnt.ClassName.IndexOf("UniDataStorageWriter") >= 0)
+ {
+ parentComboBox.Items.Add(cmpnt.Name);
+ }
+ }
+ }
+
+ parentComboBox.Text = config.ParentName;
+
+ selectedItemsLabel.Text = string.Format(
+ "{0} selected item(s)",
+ config.SelectedItems != null ? config.SelectedItems.Count : 0);
+
+ resultsConfigChanged = false;
+ }
+
+ public void Unlock()
+ {
+ nameTextBox.Enabled = true;
+ enabledCheckBox.Enabled = true;
+ parentComboBox.Enabled = true;
+ storageNameTextBox.Enabled = true;
+ configureResultsButton.Enabled = true;
+ previewRequestButton.Enabled = true;
+ }
+
+ public CfgUpdateFlags VerifyCfg(ref string message)
+ {
+ if (string.IsNullOrEmpty(nameTextBox.Text))
+ {
+ message = "Component name is empty.";
+ return CfgUpdateFlags.Error;
+ }
+
+ if (enabledCheckBox.Checked && string.IsNullOrEmpty(parentComboBox.Text))
+ {
+ message = "Parent UniDataStorageWriter is not selected.";
+ return CfgUpdateFlags.Error;
+ }
+
+ if (enabledCheckBox.Checked && string.IsNullOrEmpty(storageNameTextBox.Text))
+ {
+ message = "Storage name is empty.";
+ return CfgUpdateFlags.Error;
+ }
+
+ return CfgUpdateFlags.None;
+ }
+
+ public CfgUpdateFlags UpdateCfg()
+ {
+ CfgUpdateFlags flags = CfgUpdateFlags.None;
+
+ if (config == null) return CfgUpdateFlags.Error;
+
+ if (config.Name != nameTextBox.Text)
+ {
+ config.Name = nameTextBox.Text;
+ flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
+ }
+
+ if (config.ParentName != parentComboBox.Text)
+ {
+ config.ParentName = parentComboBox.Text;
+ flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
+ }
+
+ flags |= UpdateDifferent(
+ ref config.Enabled,
+ enabledCheckBox.Checked,
+ CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
+
+ flags |= UpdateDifferent(
+ ref config.StorageName,
+ storageNameTextBox.Text,
+ CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
+
+ if (resultsConfigChanged)
+ {
+ flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd;
+ resultsConfigChanged = false;
+ }
+
+ return flags;
+ }
+
+ private void configureResultsButton_Click(object sender, EventArgs e)
+ {
+ if (config == null) return;
+
+ using (ResultsWriterResultsDlg dlg = new ResultsWriterResultsDlg())
+ {
+ dlg.SelectedItems = config.SelectedItems;
+
+ if (dlg.ShowDialog(this) == DialogResult.OK)
+ {
+ config.SelectedItems = new List(dlg.SelectedItems);
+ config.UpdateSerializableModel();
+ resultsConfigChanged = true;
+
+ selectedItemsLabel.Text = string.Format(
+ "{0} selected item(s)",
+ config.SelectedItems != null ? config.SelectedItems.Count : 0);
+ }
+ }
+ }
+
+ private void previewRequestButton_Click(object sender, EventArgs e)
+ {
+ if (config == null) return;
+
+ Results.Entities.Batch batch = CreateSimulationBatch();
+
+ if (batch == null || batch.WaterMeters == null || batch.WaterMeters.Count == 0)
+ {
+ MessageBox.Show(
+ "No current batch results are available.",
+ "ResultsWriter",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Information);
+ return;
+ }
+
+ try
+ {
+ config.UpdateRuntimeModel();
+
+ ResultsWriter writer = new ResultsWriter(config);
+ writer.InitializeParent();
+ writer.WriteBatchResults(batch);
+
+ MessageBox.Show(
+ "Current batch was written by ResultsWriter.",
+ "ResultsWriter",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ ex.Message,
+ "ResultsWriter write failed",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Error);
+ }
+ }
+
+ private Results.Entities.Batch CreateSimulationBatch()
+ {
+ Results.Entities.Batch batch = new Results.Entities.Batch();
+
+ batch.BatchNr = 999999;
+ batch.ProcedureName = "ResultsWriter simulation";
+ batch.StartTime = DateTime.Now;
+ batch.EndTime = DateTime.Now;
+ batch.TestBenchName = "Mexico";
+
+ Results.Entities.WaterMeter wm1 = new Results.Entities.WaterMeter();
+ wm1.Batch = batch;
+ wm1.WMPosition = 1;
+ wm1.SerialNr = "SN000001";
+ batch.WaterMeters.Add(wm1);
+
+ Results.Entities.WaterMeter wm2 = new Results.Entities.WaterMeter();
+ wm2.Batch = batch;
+ wm2.WMPosition = 2;
+ wm2.SerialNr = "SN000002";
+ batch.WaterMeters.Add(wm2);
+
+ return batch;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterItemCfg.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterItemCfg.cs
new file mode 100644
index 000000000..e74f763ab
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterItemCfg.cs
@@ -0,0 +1,44 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ ///
+ /// Serializable representation of one configured Results item.
+ /// Source identifies the original TBF variable, Caption represents
+ /// the destination database column name.
+ ///
+ public class ResultsWriterItemCfg
+ {
+ ///
+ /// Original Results expression (for rebuilding the model).
+ /// Example: "Conduct.ME()"
+ ///
+ public string Source;
+
+ ///
+ /// Destination database column name.
+ ///
+ public string Caption;
+
+ public string Units;
+ public string Format;
+
+ public int Precision;
+ public int Width;
+
+ public string Alignment;
+ public bool Merge;
+
+ public ResultsWriterItemCfg()
+ {
+ Source = string.Empty;
+ Caption = string.Empty;
+ Units = string.Empty;
+ Format = string.Empty;
+ Alignment = string.Empty;
+ Merge = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.Designer.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.Designer.cs
new file mode 100644
index 000000000..4616194cc
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.Designer.cs
@@ -0,0 +1,72 @@
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ partial class ResultsWriterResultsDlg
+ {
+ private System.ComponentModel.IContainer components = null;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null)) components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ this.resultsConfigCtrl = new Results.Forms.ResultsConfigCtrl();
+ this.okButton = new System.Windows.Forms.Button();
+ this.cancelButton = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+
+ this.resultsConfigCtrl.Anchor =
+ ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top |
+ System.Windows.Forms.AnchorStyles.Bottom) |
+ System.Windows.Forms.AnchorStyles.Left) |
+ System.Windows.Forms.AnchorStyles.Right)));
+ this.resultsConfigCtrl.Location = new System.Drawing.Point(3, 3);
+ this.resultsConfigCtrl.Name = "resultsConfigCtrl";
+ this.resultsConfigCtrl.Size = new System.Drawing.Size(925, 496);
+ this.resultsConfigCtrl.TabIndex = 0;
+ this.resultsConfigCtrl.Unlocked = true;
+
+ this.okButton.Anchor =
+ ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
+ System.Windows.Forms.AnchorStyles.Right)));
+ this.okButton.Location = new System.Drawing.Point(714, 512);
+ this.okButton.Name = "okButton";
+ this.okButton.Size = new System.Drawing.Size(104, 30);
+ this.okButton.TabIndex = 1;
+ this.okButton.Text = "OK";
+ this.okButton.UseVisualStyleBackColor = true;
+ this.okButton.Click += new System.EventHandler(this.okButton_Click);
+
+ this.cancelButton.Anchor =
+ ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
+ System.Windows.Forms.AnchorStyles.Right)));
+ this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.cancelButton.Location = new System.Drawing.Point(824, 512);
+ this.cancelButton.Name = "cancelButton";
+ this.cancelButton.Size = new System.Drawing.Size(104, 30);
+ this.cancelButton.TabIndex = 2;
+ this.cancelButton.Text = "Cancel";
+ this.cancelButton.UseVisualStyleBackColor = true;
+
+ this.AcceptButton = this.okButton;
+ this.CancelButton = this.cancelButton;
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(940, 554);
+ this.Controls.Add(this.cancelButton);
+ this.Controls.Add(this.okButton);
+ this.Controls.Add(this.resultsConfigCtrl);
+ this.Name = "ResultsWriterResultsDlg";
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "ResultsWriter configuration";
+ this.Load += new System.EventHandler(this.ResultsWriterResultsDlg_Load);
+ this.ResumeLayout(false);
+ }
+
+ private Results.Forms.ResultsConfigCtrl resultsConfigCtrl;
+ private System.Windows.Forms.Button okButton;
+ private System.Windows.Forms.Button cancelButton;
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.cs b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.cs
new file mode 100644
index 000000000..ae5021d82
--- /dev/null
+++ b/TBF/Rig/Output/DB/ResultsWriter/ResultsWriterResultsDlg.cs
@@ -0,0 +1,45 @@
+///
+/// Copyright (c) 2026 Sensus Slovensko a.s.
+///
+
+using Results;
+using Results.Forms;
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+using TBF.Resources;
+
+namespace TBF.Rig.Output.DB.ResultsWriter
+{
+ public partial class ResultsWriterResultsDlg : Form
+ {
+ public IList SelectedItems
+ {
+ set { resultsConfigCtrl.SelectedItems = value; }
+ get { return resultsConfigCtrl.SelectedItems; }
+ }
+
+ public ResultsWriterResultsDlg()
+ {
+ InitializeComponent();
+
+ this.Icon = Properties.Resources.TBF_icon;
+ resultsConfigCtrl.SupressTestIDColumn = true;
+ }
+
+ private void ResultsWriterResultsDlg_Load(object sender, EventArgs e)
+ {
+ Text = "ResultsWriter configuration";
+ okButton.Text = Strings.OkBtnText;
+ cancelButton.Text = Strings.CancelBtnText;
+
+ resultsConfigCtrl.Unlocked = true;
+ }
+
+ private void okButton_Click(object sender, EventArgs e)
+ {
+ DialogResult = DialogResult.OK;
+ Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TBF/Rig/TbfComponents.cs b/TBF/Rig/TbfComponents.cs
index 677fc5fb7..c8993abaa 100644
--- a/TBF/Rig/TbfComponents.cs
+++ b/TBF/Rig/TbfComponents.cs
@@ -99,6 +99,7 @@ namespace TBF.Rig
new Output.DataStorage.UniDataStorageWriter.Factory(),
new Output.DB.DatabaseWriter.Factory(),
new Output.DB.ProductionTracing.Factory(),
+ new Output.DB.ResultsWriter.Factory(),
new Output.DB.SaveDiverterCorrections.Factory(),
new Output.DB.SaveFlowmeterCorrections.Factory(),
new Output.DB.SensusOracle.Factory(),
diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj
index 35f912781..8e4c09a81 100644
--- a/TBF/TBF.csproj
+++ b/TBF/TBF.csproj
@@ -1236,6 +1236,22 @@
+
+
+
+
+ UserControl
+
+
+ ResultsWriterCfgCtrl.cs
+
+
+
+ Form
+
+
+ ResultsWriterResultsDlg.cs
+